???????????????????????????????????????? >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ???????????????????????????????????????? >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ???????????????????????????????????????? >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ????????????????????????????????????????? >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ???????????????????????????????????????? ??????????????????????????????????????? $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ PNG \x49\x44\x41\x54?\x89\x50 \x4E\x47\x0D\x0A\x1A\x0A JFIF    ?? C    !"$"$?? C  ?? p " ??     ??   ?   ???? (% aA*?XYD?(J??E RE,P XYae?)(E 2 B R BQ X?)X ? @ adadasdasdasasdasdas .....................................................................................................................................?????????????????????? ??? ???????????????????????????????????????............................... JFIF    ?? C    !"$"$?? C  ?? p " ??     ??   ?   ???? (% aA*?XYD?(J??E RE,P XYae?)(E 2 B R BQ X?)X ? @ adadasdasdasasdasdas ..................................................................................................................................... ???????????????????????????????????????? >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ???????????????????????????????????????? >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ???????????????????????????????????????? >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ????????????????????????????????????????? >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ???????????????????????????????????????? ??????????????????????????????????????? PNG \x49\x44\x41\x54?\x89\x50 \x4E\x47\x0D\x0A\x1A\x0A JFIF    ?? C    !"$"$?? C  ?? p " ??     ??   ?   ???? (% aA*?XYD?(J??E RE,P XYae?)(E 2 B R BQ X?)X ? @ adadasdasdasasdasdas .....................................................................................................................................?????????????????????? ??? ???????????????????????????????????????............................... JFIF    ?? C    !"$"$?? C  ?? p " ??     ??   ?   ???? (% aA*?XYD?(J??E RE,P XYae?)(E 2 B R BQ X?)X ? @ adadasdasdasasdasdas .....................................................................................................................................???????????????????????????????? ??????????????????????????????? ??????????????????????????????? >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.
Warning: Undefined variable $auth in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 695

Warning: Trying to access array offset on value of type null in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 695

Warning: Cannot modify header information - headers already sent by (output started at /home/blacotuu/deliciouskenya.com/d94fc6/index.php:1) in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 332

Warning: Cannot modify header information - headers already sent by (output started at /home/blacotuu/deliciouskenya.com/d94fc6/index.php:1) in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 333

Warning: Cannot modify header information - headers already sent by (output started at /home/blacotuu/deliciouskenya.com/d94fc6/index.php:1) in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 334

Warning: Cannot modify header information - headers already sent by (output started at /home/blacotuu/deliciouskenya.com/d94fc6/index.php:1) in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 335

Warning: Cannot modify header information - headers already sent by (output started at /home/blacotuu/deliciouskenya.com/d94fc6/index.php:1) in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 336

Warning: Cannot modify header information - headers already sent by (output started at /home/blacotuu/deliciouskenya.com/d94fc6/index.php:1) in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 337
lang/litespeed-cache.pot000064400000460544152075713260011255 0ustar00# Copyright (C) 2025 LiteSpeed Technologies # This file is distributed under the GPLv3. msgid "" msgstr "" "Project-Id-Version: LiteSpeed Cache 7.7\n" "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/litespeed-cache\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "POT-Creation-Date: 2025-12-16T16:53:20+00:00\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "X-Generator: WP-CLI 2.11.0\n" "X-Domain: litespeed-cache\n" #. Plugin Name of the plugin #: litespeed-cache.php #: tpl/banner/new_version.php:57 #: tpl/banner/new_version_dev.tpl.php:21 #: tpl/cache/more_settings_tip.tpl.php:28 #: tpl/esi_widget_edit.php:41 #: tpl/inc/admin_footer.php:17 msgid "LiteSpeed Cache" msgstr "" #. Plugin URI of the plugin #: litespeed-cache.php msgid "https://www.litespeedtech.com/products/cache-plugins/wordpress-acceleration" msgstr "" #. Description of the plugin #: litespeed-cache.php msgid "High-performance page caching and site optimization from LiteSpeed" msgstr "" #. Author of the plugin #: litespeed-cache.php msgid "LiteSpeed Technologies" msgstr "" #. Author URI of the plugin #: litespeed-cache.php msgid "https://www.litespeedtech.com" msgstr "" #: cli/crawler.cls.php:89 #: tpl/crawler/summary.tpl.php:39 msgid "%d hours" msgstr "" #: cli/crawler.cls.php:91 #: tpl/crawler/summary.tpl.php:39 msgid "%d hour" msgstr "" #: cli/crawler.cls.php:98 #: tpl/crawler/summary.tpl.php:47 msgid "%d minutes" msgstr "" #: cli/crawler.cls.php:100 #: tpl/crawler/summary.tpl.php:47 msgid "%d minute" msgstr "" #: cli/purge.cls.php:86 msgid "Purged All!" msgstr "" #: cli/purge.cls.php:133 msgid "Purged the blog!" msgstr "" #: cli/purge.cls.php:182 msgid "Purged the URL!" msgstr "" #: cli/purge.cls.php:234 msgid "Purged!" msgstr "" #: src/activation.cls.php:561 #: src/activation.cls.php:566 msgid "Failed to upgrade." msgstr "" #: src/activation.cls.php:570 msgid "Upgraded successfully." msgstr "" #: src/admin-display.cls.php:251 #: tpl/dash/entry.tpl.php:16 msgid "Dashboard" msgstr "" #: src/admin-display.cls.php:252 #: src/lang.cls.php:287 msgid "OptimaX" msgstr "" #: src/admin-display.cls.php:253 msgid "Presets" msgstr "" #: src/admin-display.cls.php:254 msgid "General" msgstr "" #: src/admin-display.cls.php:255 #: tpl/cache/entry.tpl.php:17 #: tpl/cache/entry.tpl.php:66 msgid "Cache" msgstr "" #: src/admin-display.cls.php:256 msgid "CDN" msgstr "" #: src/admin-display.cls.php:257 #: src/gui.cls.php:895 #: tpl/dash/dashboard.tpl.php:204 #: tpl/dash/network_dash.tpl.php:36 #: tpl/general/online.tpl.php:75 #: tpl/general/online.tpl.php:134 #: tpl/general/online.tpl.php:149 #: tpl/presets/standard.tpl.php:32 msgid "Image Optimization" msgstr "" #: src/admin-display.cls.php:258 #: tpl/dash/dashboard.tpl.php:205 #: tpl/dash/network_dash.tpl.php:37 #: tpl/general/online.tpl.php:83 #: tpl/general/online.tpl.php:133 #: tpl/general/online.tpl.php:148 msgid "Page Optimization" msgstr "" #: src/admin-display.cls.php:259 msgid "Database" msgstr "" #: src/admin-display.cls.php:260 #: src/lang.cls.php:267 msgid "Crawler" msgstr "" #: src/admin-display.cls.php:261 msgid "Toolbox" msgstr "" #: src/admin-display.cls.php:455 msgid "Cookie Name" msgstr "" #: src/admin-display.cls.php:456 #: tpl/crawler/settings.tpl.php:179 msgid "Cookie Values" msgstr "" #: src/admin-display.cls.php:458 msgid "Remove cookie simulation" msgstr "" #: src/admin-display.cls.php:459 msgid "Add new cookie to simulate" msgstr "" #: src/admin-display.cls.php:482 msgid "CDN URL to be used. For example, %s" msgstr "" #: src/admin-display.cls.php:484 msgid "Remove CDN URL" msgstr "" #: src/admin-display.cls.php:485 msgid "Add new CDN URL" msgstr "" #: src/admin-display.cls.php:486 #: src/admin-display.cls.php:1167 #: src/admin-display.cls.php:1197 #: src/admin-display.cls.php:1284 #: src/doc.cls.php:41 #: tpl/cache/settings-cache.tpl.php:28 #: tpl/cache/settings_inc.cache_mobile.tpl.php:91 #: tpl/cdn/other.tpl.php:45 #: tpl/crawler/settings.tpl.php:138 #: tpl/dash/dashboard.tpl.php:67 #: tpl/dash/dashboard.tpl.php:460 #: tpl/dash/dashboard.tpl.php:582 #: tpl/dash/dashboard.tpl.php:611 #: tpl/page_optm/settings_css.tpl.php:220 #: tpl/page_optm/settings_media.tpl.php:176 #: tpl/toolbox/settings-debug.tpl.php:80 msgid "ON" msgstr "" #: src/admin-display.cls.php:487 #: src/admin-display.cls.php:1168 #: src/admin-display.cls.php:1197 #: src/admin-display.cls.php:1284 #: tpl/cache/settings-cache.tpl.php:28 #: tpl/cache/settings_inc.object.tpl.php:280 #: tpl/cdn/other.tpl.php:53 #: tpl/dash/dashboard.tpl.php:69 #: tpl/dash/dashboard.tpl.php:462 #: tpl/dash/dashboard.tpl.php:584 #: tpl/dash/dashboard.tpl.php:613 #: tpl/img_optm/settings.media_webp.tpl.php:22 #: tpl/page_optm/settings_css.tpl.php:93 #: tpl/page_optm/settings_js.tpl.php:77 #: tpl/page_optm/settings_media.tpl.php:180 #: tpl/toolbox/settings-debug.tpl.php:80 msgid "OFF" msgstr "" #: src/admin-display.cls.php:548 #: src/gui.cls.php:884 #: tpl/crawler/entry.tpl.php:17 msgid "Settings" msgstr "" #: src/admin-display.cls.php:805 #: tpl/banner/new_version.php:114 #: tpl/banner/score.php:142 #: tpl/banner/slack.php:49 msgid "Dismiss" msgstr "" #: src/admin-display.cls.php:998 #: src/admin-display.cls.php:1003 msgid "Save Changes" msgstr "" #: src/admin-display.cls.php:1298 msgid "This value is overwritten by the %s variable." msgstr "" #: src/admin-display.cls.php:1302 #: src/admin-display.cls.php:1315 msgid "This value is overwritten by the filter." msgstr "" #: src/admin-display.cls.php:1305 msgid "This value is overwritten by the PHP constant %s." msgstr "" #: src/admin-display.cls.php:1309 msgid "This value is overwritten by the primary site setting." msgstr "" #: src/admin-display.cls.php:1311 msgid "This value is overwritten by the Network setting." msgstr "" #: src/admin-display.cls.php:1318 msgid "Currently set to %s" msgstr "" #: src/admin-display.cls.php:1331 msgid "Value from filter applied" msgstr "" #: src/admin-display.cls.php:1345 #: tpl/cache/settings_inc.object.tpl.php:162 #: tpl/crawler/settings.tpl.php:43 #: tpl/esi_widget_edit.php:78 msgid "seconds" msgstr "" #: src/admin-display.cls.php:1371 #: src/admin-display.cls.php:1390 #: tpl/cdn/other.tpl.php:108 msgid "Default value" msgstr "" #: src/admin-display.cls.php:1418 msgid "Invalid rewrite rule" msgstr "" #: src/admin-display.cls.php:1438 msgid "Path must end with %s" msgstr "" #: src/admin-display.cls.php:1458 msgid "Minimum value" msgstr "" #: src/admin-display.cls.php:1461 msgid "Maximum value" msgstr "" #: src/admin-display.cls.php:1473 msgid "Zero, or" msgstr "" #: src/admin-display.cls.php:1479 msgid "Larger than" msgstr "" #: src/admin-display.cls.php:1481 msgid "Smaller than" msgstr "" #: src/admin-display.cls.php:1484 msgid "Value range" msgstr "" #: src/admin-display.cls.php:1512 msgid "Invalid IP" msgstr "" #: src/admin-display.cls.php:1532 #: tpl/cache/settings-esi.tpl.php:103 #: tpl/page_optm/settings_css.tpl.php:87 #: tpl/page_optm/settings_css.tpl.php:223 #: tpl/page_optm/settings_html.tpl.php:131 #: tpl/page_optm/settings_media.tpl.php:258 #: tpl/page_optm/settings_media_exc.tpl.php:36 #: tpl/page_optm/settings_tuning.tpl.php:48 #: tpl/page_optm/settings_tuning.tpl.php:68 #: tpl/page_optm/settings_tuning.tpl.php:89 #: tpl/page_optm/settings_tuning.tpl.php:110 #: tpl/page_optm/settings_tuning.tpl.php:129 #: tpl/page_optm/settings_tuning_css.tpl.php:35 #: tpl/page_optm/settings_tuning_css.tpl.php:96 #: tpl/page_optm/settings_tuning_css.tpl.php:99 #: tpl/page_optm/settings_tuning_css.tpl.php:100 #: tpl/toolbox/edit_htaccess.tpl.php:61 #: tpl/toolbox/edit_htaccess.tpl.php:79 msgid "API" msgstr "" #. translators: %s: list of server variables in tags #: src/admin-display.cls.php:1535 msgid "Server variable(s) %s available to override this setting." msgstr "" #: src/admin-display.cls.php:1551 msgid "The URLs will be compared to the REQUEST_URI server variable." msgstr "" #. translators: 1: example URL, 2: pattern example #: src/admin-display.cls.php:1553 msgid "For example, for %1$s, %2$s can be used here." msgstr "" #. translators: %s: caret symbol #: src/admin-display.cls.php:1556 msgid "To match the beginning, add %s to the beginning of the item." msgstr "" #. translators: %s: dollar symbol #: src/admin-display.cls.php:1558 msgid "To do an exact match, add %s to the end of the URL." msgstr "" #: src/admin-display.cls.php:1559 #: src/doc.cls.php:128 msgid "One per line." msgstr "" #: src/admin-display.cls.php:1576 msgid "%s groups" msgstr "" #: src/admin-display.cls.php:1579 msgid "%s images" msgstr "" #: src/admin-display.cls.php:1588 msgid "%s group" msgstr "" #: src/admin-display.cls.php:1591 msgid "%s image" msgstr "" #: src/admin-settings.cls.php:40 #: src/admin-settings.cls.php:313 msgid "No fields" msgstr "" #: src/admin-settings.cls.php:104 msgid "The user with id %s has editor access, which is not allowed for the role simulator." msgstr "" #: src/admin-settings.cls.php:297 #: src/admin-settings.cls.php:333 msgid "Options saved." msgstr "" #: src/cdn/cloudflare.cls.php:121 msgid "Notified Cloudflare to set development mode to %s successfully." msgstr "" #: src/cdn/cloudflare.cls.php:151 msgid "Cloudflare API is set to off." msgstr "" #: src/cdn/cloudflare.cls.php:167 msgid "Notified Cloudflare to purge all successfully." msgstr "" #: src/cdn/cloudflare.cls.php:181 msgid "No available Cloudflare zone" msgstr "" #: src/cdn/cloudflare.cls.php:275 #: src/cdn/cloudflare.cls.php:297 msgid "Failed to communicate with Cloudflare" msgstr "" #: src/cdn/cloudflare.cls.php:288 msgid "Communicated with Cloudflare successfully." msgstr "" #: src/cloud.cls.php:246 #: src/cloud.cls.php:331 msgid "QUIC.cloud's access to your WP REST API seems to be blocked." msgstr "" #: src/cloud.cls.php:256 #: src/cloud.cls.php:341 msgid "Failed to get echo data from WPAPI" msgstr "" #: src/cloud.cls.php:319 #: src/cloud.cls.php:376 msgid "You need to set the %1$s first. Please use the command %2$s to set." msgstr "" #: src/cloud.cls.php:320 #: src/cloud.cls.php:377 #: src/lang.cls.php:106 msgid "Server IP" msgstr "" #: src/cloud.cls.php:368 #: src/cloud.cls.php:414 #: src/cloud.cls.php:441 #: src/cloud.cls.php:460 #: src/cloud.cls.php:481 #: src/cloud.cls.php:499 msgid "You need to activate QC first." msgstr "" #: src/cloud.cls.php:386 msgid "Cert or key file does not exist." msgstr "" #: src/cloud.cls.php:676 msgid "Failed to validate %s activation data." msgstr "" #: src/cloud.cls.php:683 msgid "Failed to parse %s activation status." msgstr "" #: src/cloud.cls.php:690 msgid "%s activation data expired." msgstr "" #: src/cloud.cls.php:718 msgid "Congratulations, %s successfully set this domain up for the anonymous online services." msgstr "" #: src/cloud.cls.php:720 msgid "Congratulations, %s successfully set this domain up for the online services." msgstr "" #: src/cloud.cls.php:725 #: src/cloud.cls.php:775 #: src/cloud.cls.php:818 msgid "Congratulations, %s successfully set this domain up for the online services with CDN service." msgstr "" #: src/cloud.cls.php:846 msgid "Reset %s activation successfully." msgstr "" #: src/cloud.cls.php:1131 #: src/cloud.cls.php:1144 #: src/cloud.cls.php:1182 #: src/cloud.cls.php:1250 #: src/cloud.cls.php:1408 msgid "Cloud Error" msgstr "" #: src/cloud.cls.php:1182 msgid "No available Cloud Node after checked server load." msgstr "" #: src/cloud.cls.php:1250 msgid "No available Cloud Node." msgstr "" #: src/cloud.cls.php:1362 msgid "In order to use QC services, need a real domain name, cannot use an IP." msgstr "" #: src/cloud.cls.php:1411 msgid "Please try after %1$s for service %2$s." msgstr "" #: src/cloud.cls.php:1633 #: src/cloud.cls.php:1656 msgid "Failed to request via WordPress" msgstr "" #: src/cloud.cls.php:1688 msgid "Cloud server refused the current request due to unpulled images. Please pull the images first." msgstr "" #: src/cloud.cls.php:1693 msgid "Your domain_key has been temporarily blocklisted to prevent abuse. You may contact support at QUIC.cloud to learn more." msgstr "" #: src/cloud.cls.php:1700 msgid "Cloud server refused the current request due to rate limiting. Please try again later." msgstr "" #: src/cloud.cls.php:1708 msgid "Redetected node" msgstr "" #: src/cloud.cls.php:1716 msgid "We are working hard to improve your online service experience. The service will be unavailable while we work. We apologize for any inconvenience." msgstr "" #: src/cloud.cls.php:1767 #: src/cloud.cls.php:1775 msgid "Message from QUIC.cloud server" msgstr "" #: src/cloud.cls.php:1783 msgid "Good news from QUIC.cloud server" msgstr "" #: src/cloud.cls.php:1792 msgid "%1$s plugin version %2$s required for this action." msgstr "" #: src/cloud.cls.php:1859 msgid "Failed to communicate with QUIC.cloud server" msgstr "" #: src/cloud.cls.php:1918 msgid "Site not recognized. QUIC.cloud deactivated automatically. Please reactivate your QUIC.cloud account." msgstr "" #: src/cloud.cls.php:1919 msgid "Click here to proceed." msgstr "" #: src/cloud.cls.php:2220 msgid "Linked to QUIC.cloud preview environment, for testing purpose only." msgstr "" #: src/cloud.cls.php:2276 msgid "Sync QUIC.cloud status successfully." msgstr "" #: src/cloud.cls.php:2283 msgid "Sync credit allowance with Cloud Server successfully." msgstr "" #: src/conf.cls.php:551 msgid "Saving option failed. IPv4 only for %s." msgstr "" #: src/conf.cls.php:742 msgid "Changed setting successfully." msgstr "" #: src/core.cls.php:333 msgid "Notified LiteSpeed Web Server to purge everything." msgstr "" #: src/core.cls.php:338 msgid "Notified LiteSpeed Web Server to purge the list." msgstr "" #: src/crawler-map.cls.php:347 msgid "Sitemap cleaned successfully" msgstr "" #: src/crawler-map.cls.php:451 msgid "No valid sitemap parsed for crawler." msgstr "" #: src/crawler-map.cls.php:456 msgid "Sitemap created successfully: %d items" msgstr "" #: src/crawler.cls.php:229 msgid "Crawler disabled list is cleared! All crawlers are set to active! " msgstr "" #: src/crawler.cls.php:324 msgid "Started async crawling" msgstr "" #: src/crawler.cls.php:1310 msgid "Guest" msgstr "" #: src/crawler.cls.php:1487 msgid "Manually added to blocklist" msgstr "" #: src/crawler.cls.php:1490 msgid "Previously existed in blocklist" msgstr "" #. translators: %s: time string #: src/data.cls.php:239 msgid "The database has been upgrading in the background since %s. This message will disappear once upgrade is complete." msgstr "" #: src/db-optm.cls.php:190 msgid "Clean all successfully." msgstr "" #: src/db-optm.cls.php:252 msgid "Clean post revisions successfully." msgstr "" #: src/db-optm.cls.php:257 msgid "Clean orphaned post meta successfully." msgstr "" #: src/db-optm.cls.php:262 msgid "Clean auto drafts successfully." msgstr "" #: src/db-optm.cls.php:267 msgid "Clean trashed posts and pages successfully." msgstr "" #: src/db-optm.cls.php:272 msgid "Clean spam comments successfully." msgstr "" #: src/db-optm.cls.php:277 msgid "Clean trashed comments successfully." msgstr "" #: src/db-optm.cls.php:282 msgid "Clean trackbacks and pingbacks successfully." msgstr "" #: src/db-optm.cls.php:310 msgid "Clean expired transients successfully." msgstr "" #: src/db-optm.cls.php:320 msgid "Clean all transients successfully." msgstr "" #: src/db-optm.cls.php:336 msgid "Optimized all tables." msgstr "" #: src/db-optm.cls.php:399 msgid "Converted to InnoDB successfully." msgstr "" #: src/doc.cls.php:40 msgid "This setting is %1$s for certain qualifying requests due to %2$s!" msgstr "" #: src/doc.cls.php:57 msgid "This setting will regenerate crawler list and clear the disabled list!" msgstr "" #: src/doc.cls.php:69 msgid "This site utilizes caching in order to facilitate a faster response time and better user experience. Caching potentially stores a duplicate copy of every web page that is on display on this site. All cache files are temporary, and are never accessed by any third party, except as necessary to obtain technical support from the cache plugin vendor. Cache files expire on a schedule set by the site administrator, but may easily be purged by the admin before their natural expiration, if necessary. We may use QUIC.cloud services to process & cache your data temporarily." msgstr "" #. translators: %s: QUIC.cloud privacy policy URL #: src/doc.cls.php:76 msgid "Please see %s for more details." msgstr "" #: src/doc.cls.php:101 #: tpl/dash/dashboard.tpl.php:187 #: tpl/dash/dashboard.tpl.php:846 #: tpl/general/online.tpl.php:81 #: tpl/general/online.tpl.php:93 #: tpl/general/online.tpl.php:109 #: tpl/general/online.tpl.php:114 #: tpl/img_optm/summary.tpl.php:59 #: tpl/inc/check_cache_disabled.php:46 #: tpl/page_optm/settings_media.tpl.php:301 msgid "Learn More" msgstr "" #: src/doc.cls.php:145 msgid "Both full and partial strings can be used." msgstr "" #: src/doc.cls.php:147 msgid "Both full URLs and partial strings can be used." msgstr "" #: src/doc.cls.php:159 msgid "This setting will edit the .htaccess file." msgstr "" #: src/doc.cls.php:177 msgid "The queue is processed asynchronously. It may take time." msgstr "" #: src/error.cls.php:68 msgid "You will need to finish %s setup to use the online services." msgstr "" #: src/error.cls.php:73 #: tpl/crawler/settings.tpl.php:123 #: tpl/crawler/settings.tpl.php:144 #: tpl/crawler/summary.tpl.php:218 msgid "Click here to set." msgstr "" #: src/error.cls.php:81 msgid "You have used all of your daily quota for today." msgstr "" #: src/error.cls.php:86 #: src/error.cls.php:99 msgid "Learn more or purchase additional quota." msgstr "" #: src/error.cls.php:94 msgid "You have used all of your quota left for current service this month." msgstr "" #: src/error.cls.php:107 msgid "You have too many requested images, please try again in a few minutes." msgstr "" #: src/error.cls.php:111 msgid "You have images waiting to be pulled. Please wait for the automatic pull to complete, or pull them down manually now." msgstr "" #: src/error.cls.php:115 msgid "The image list is empty." msgstr "" #: src/error.cls.php:119 msgid "Not enough parameters. Please check if the QUIC.cloud connection is set correctly" msgstr "" #: src/error.cls.php:123 msgid "There is proceeding queue not pulled yet." msgstr "" #: src/error.cls.php:128 msgid "There is proceeding queue not pulled yet. Queue info: %s." msgstr "" #: src/error.cls.php:134 msgid "The site is not a valid alias on QUIC.cloud." msgstr "" #: src/error.cls.php:138 msgid "The site is not registered on QUIC.cloud." msgstr "" #: src/error.cls.php:142 msgid "The QUIC.cloud connection is not correct. Please try to sync your QUIC.cloud connection again." msgstr "" #: src/error.cls.php:146 msgid "The current server is under heavy load." msgstr "" #: src/error.cls.php:150 msgid "Online node needs to be redetected." msgstr "" #: src/error.cls.php:154 msgid "Credits are not enough to proceed the current request." msgstr "" #: src/error.cls.php:158 #: src/error.cls.php:182 msgid "%s file not writable." msgstr "" #: src/error.cls.php:166 msgid "Could not find %1$s in %2$s." msgstr "" #: src/error.cls.php:170 msgid "Invalid login cookie. Please check the %s file." msgstr "" #: src/error.cls.php:174 msgid "Failed to back up %s file, aborted changes." msgstr "" #: src/error.cls.php:178 msgid "%s file not readable." msgstr "" #: src/error.cls.php:186 msgid "Failed to get %s file contents." msgstr "" #: src/error.cls.php:190 msgid "Failed to create table %1$s! SQL: %2$s." msgstr "" #: src/error.cls.php:194 msgid "Crawler disabled by the server admin." msgstr "" #: src/error.cls.php:198 msgid "Previous request too recent. Please try again later." msgstr "" #: src/error.cls.php:203 msgid "Previous request too recent. Please try again after %s." msgstr "" #: src/error.cls.php:209 msgid "Your application is waiting for approval." msgstr "" #: src/error.cls.php:213 msgid "The callback validation to your domain failed due to hash mismatch." msgstr "" #: src/error.cls.php:217 msgid "The callback validation to your domain failed. Please make sure there is no firewall blocking our servers." msgstr "" #: src/error.cls.php:222 msgid "The callback validation to your domain failed. Please make sure there is no firewall blocking our servers. Response code: " msgstr "" #: src/error.cls.php:227 msgid "Your domain has been forbidden from using our services due to a previous policy violation." msgstr "" #: src/error.cls.php:231 msgid "You cannot remove this DNS zone, because it is still in use. Please update the domain's nameservers, then try to delete this zone again, otherwise your site will become inaccessible." msgstr "" #: src/error.cls.php:238 msgid "Unknown error" msgstr "" #: src/file.cls.php:133 msgid "Filename is empty!" msgstr "" #: src/file.cls.php:142 msgid "Folder does not exist: %s" msgstr "" #: src/file.cls.php:154 msgid "Can not create folder: %1$s. Error: %2$s" msgstr "" #: src/file.cls.php:162 msgid "Folder is not writable: %s." msgstr "" #: src/file.cls.php:168 #: src/file.cls.php:172 msgid "File %s is not writable." msgstr "" #: src/file.cls.php:179 msgid "Failed to write to %s." msgstr "" #: src/guest.cls.php:65 msgid "Guest Mode lists synced successfully." msgstr "" #: src/guest.cls.php:66 msgid "Failed to sync Guest Mode lists." msgstr "" #. translators: 1: number, 2: text #: src/gui.cls.php:129 msgid "%1$s %2$s files left in queue" msgstr "" #: src/gui.cls.php:133 #: tpl/inc/modal.deactivation.php:77 msgid "Cancel" msgstr "" #: src/gui.cls.php:584 #: src/gui.cls.php:601 msgid "Purge this page" msgstr "" #: src/gui.cls.php:612 msgid "Mark this page as " msgstr "" #: src/gui.cls.php:628 msgid "Forced cacheable" msgstr "" #: src/gui.cls.php:641 msgid "Non cacheable" msgstr "" #: src/gui.cls.php:654 msgid "Private cache" msgstr "" #: src/gui.cls.php:667 msgid "No optimization" msgstr "" #: src/gui.cls.php:677 msgid "More settings" msgstr "" #: src/gui.cls.php:686 #: src/gui.cls.php:696 #: src/gui.cls.php:706 #: src/gui.cls.php:717 #: src/gui.cls.php:729 #: src/gui.cls.php:741 #: src/gui.cls.php:753 #: src/gui.cls.php:765 #: src/gui.cls.php:776 #: src/gui.cls.php:788 #: src/gui.cls.php:800 #: src/gui.cls.php:812 #: src/gui.cls.php:906 #: src/gui.cls.php:916 #: src/gui.cls.php:926 #: src/gui.cls.php:937 #: src/gui.cls.php:949 #: src/gui.cls.php:961 #: src/gui.cls.php:973 #: src/gui.cls.php:985 #: src/gui.cls.php:996 #: src/gui.cls.php:1008 #: src/gui.cls.php:1020 #: src/gui.cls.php:1032 #: tpl/page_optm/settings_media.tpl.php:141 #: tpl/toolbox/purge.tpl.php:40 #: tpl/toolbox/purge.tpl.php:47 #: tpl/toolbox/purge.tpl.php:55 #: tpl/toolbox/purge.tpl.php:64 #: tpl/toolbox/purge.tpl.php:73 #: tpl/toolbox/purge.tpl.php:82 #: tpl/toolbox/purge.tpl.php:91 #: tpl/toolbox/purge.tpl.php:100 #: tpl/toolbox/purge.tpl.php:109 #: tpl/toolbox/purge.tpl.php:118 #: tpl/toolbox/purge.tpl.php:126 msgid "Purge All" msgstr "" #: src/gui.cls.php:696 #: src/gui.cls.php:861 #: src/gui.cls.php:916 msgid "LSCache" msgstr "" #: src/gui.cls.php:706 #: src/gui.cls.php:926 #: tpl/toolbox/purge.tpl.php:47 msgid "CSS/JS Cache" msgstr "" #: src/gui.cls.php:717 #: src/gui.cls.php:937 #: tpl/cdn/cf.tpl.php:96 #: tpl/cdn/entry.tpl.php:15 msgid "Cloudflare" msgstr "" #: src/gui.cls.php:729 #: src/gui.cls.php:949 #: src/lang.cls.php:131 #: tpl/dash/dashboard.tpl.php:60 #: tpl/dash/dashboard.tpl.php:604 #: tpl/toolbox/purge.tpl.php:55 msgid "Object Cache" msgstr "" #: src/gui.cls.php:741 #: src/gui.cls.php:961 #: tpl/toolbox/purge.tpl.php:64 msgid "Opcode Cache" msgstr "" #: src/gui.cls.php:776 #: src/gui.cls.php:996 #: tpl/toolbox/purge.tpl.php:91 msgid "Localized Resources" msgstr "" #: src/gui.cls.php:788 #: src/gui.cls.php:1008 #: tpl/page_optm/settings_media.tpl.php:141 #: tpl/toolbox/purge.tpl.php:100 msgid "LQIP Cache" msgstr "" #: src/gui.cls.php:812 #: src/gui.cls.php:1032 #: src/lang.cls.php:198 #: tpl/presets/standard.tpl.php:49 #: tpl/toolbox/purge.tpl.php:118 msgid "Gravatar Cache" msgstr "" #: src/gui.cls.php:850 msgid "Enable All Features" msgstr "" #: src/gui.cls.php:861 msgid "LiteSpeed Cache Purge All" msgstr "" #: src/gui.cls.php:874 #: tpl/db_optm/entry.tpl.php:13 msgid "Manage" msgstr "" #: src/gui.cls.php:1055 #: tpl/img_optm/summary.tpl.php:176 msgid "Remove all previous unfinished image optimization requests." msgstr "" #: src/gui.cls.php:1056 #: tpl/img_optm/summary.tpl.php:178 msgid "Clean Up Unfinished Data" msgstr "" #: src/gui.cls.php:1079 msgid "Install %s" msgstr "" #: src/gui.cls.php:1080 msgid "Install Now" msgstr "" #. translators: 1: details URL, 2: class/aria, 3: version, 4: update URL, 5: class/aria #: src/gui.cls.php:1103 msgid "View version %3$s details or update now." msgstr "" #. translators: 1: plugin title, 2: version #: src/gui.cls.php:1110 msgid "View %1$s version %2$s details" msgstr "" #. translators: %s: plugin title #: src/gui.cls.php:1123 msgid "Update %s now" msgstr "" #: src/htaccess.cls.php:325 msgid "Mobile Agent Rules" msgstr "" #: src/htaccess.cls.php:784 msgid "

Please add/replace the following codes into the beginning of %1$s:

%2$s" msgstr "" #: src/img-optm.cls.php:350 msgid "Pushed %1$s to Cloud server, accepted %2$s." msgstr "" #: src/img-optm.cls.php:618 msgid "Cleared %1$s invalid images." msgstr "" #: src/img-optm.cls.php:675 msgid "No valid image found in the current request." msgstr "" #: src/img-optm.cls.php:700 msgid "No valid image found by Cloud server in the current request." msgstr "" #: src/img-optm.cls.php:890 msgid "Started async image optimization request" msgstr "" #: src/img-optm.cls.php:976 msgid "Pull Cron is running" msgstr "" #: src/img-optm.cls.php:1087 msgid "Some optimized image file(s) has expired and was cleared." msgstr "" #: src/img-optm.cls.php:1102 msgid "Pulled WebP image md5 does not match the notified WebP image md5." msgstr "" #: src/img-optm.cls.php:1131 msgid "Pulled AVIF image md5 does not match the notified AVIF image md5." msgstr "" #: src/img-optm.cls.php:1166 msgid "One or more pulled images does not match with the notified image md5" msgstr "" #: src/img-optm.cls.php:1361 msgid "Cleaned up unfinished data successfully." msgstr "" #: src/img-optm.cls.php:1378 msgid "Reset image optimization counter successfully." msgstr "" #: src/img-optm.cls.php:1462 msgid "Destroy all optimization data successfully." msgstr "" #: src/img-optm.cls.php:1527 #: src/img-optm.cls.php:1591 msgid "Rescanned successfully." msgstr "" #: src/img-optm.cls.php:1591 msgid "Rescanned %d images successfully." msgstr "" #: src/img-optm.cls.php:1657 msgid "Calculated backups successfully." msgstr "" #: src/img-optm.cls.php:1749 msgid "Removed backups successfully." msgstr "" #: src/img-optm.cls.php:1896 msgid "Switched images successfully." msgstr "" #: src/img-optm.cls.php:1993 #: src/img-optm.cls.php:2053 msgid "Switched to optimized file successfully." msgstr "" #: src/img-optm.cls.php:2012 msgid "Disabled WebP file successfully." msgstr "" #: src/img-optm.cls.php:2017 msgid "Enabled WebP file successfully." msgstr "" #: src/img-optm.cls.php:2026 msgid "Disabled AVIF file successfully." msgstr "" #: src/img-optm.cls.php:2031 msgid "Enabled AVIF file successfully." msgstr "" #: src/img-optm.cls.php:2047 msgid "Restored original file successfully." msgstr "" #: src/img-optm.cls.php:2103 msgid "Reset the optimized data successfully." msgstr "" #: src/import.cls.php:81 msgid "Import failed due to file error." msgstr "" #: src/import.cls.php:134 msgid "Imported setting file %s successfully." msgstr "" #: src/import.cls.php:156 msgid "Reset successfully." msgstr "" #: src/lang.cls.php:30 msgid "Images not requested" msgstr "" #: src/lang.cls.php:31 msgid "Images ready to request" msgstr "" #: src/lang.cls.php:32 #: tpl/dash/dashboard.tpl.php:552 msgid "Images requested" msgstr "" #: src/lang.cls.php:33 #: tpl/dash/dashboard.tpl.php:561 msgid "Images notified to pull" msgstr "" #: src/lang.cls.php:34 msgid "Images optimized and pulled" msgstr "" #: src/lang.cls.php:58 msgid "Unable to automatically add %1$s as a Domain Alias for main %2$s domain, due to potential CDN conflict." msgstr "" #: src/lang.cls.php:66 msgid "Unable to automatically add %1$s as a Domain Alias for main %2$s domain." msgstr "" #: src/lang.cls.php:71 msgid "Alias is in use by another QUIC.cloud account." msgstr "" #: src/lang.cls.php:108 msgid "Enable Cache" msgstr "" #: src/lang.cls.php:109 #: tpl/dash/dashboard.tpl.php:61 #: tpl/dash/dashboard.tpl.php:605 #: tpl/presets/standard.tpl.php:21 msgid "Browser Cache" msgstr "" #: src/lang.cls.php:110 msgid "Default Public Cache TTL" msgstr "" #: src/lang.cls.php:111 msgid "Default Private Cache TTL" msgstr "" #: src/lang.cls.php:112 msgid "Default Front Page TTL" msgstr "" #: src/lang.cls.php:113 msgid "Default Feed TTL" msgstr "" #: src/lang.cls.php:114 msgid "Default REST TTL" msgstr "" #: src/lang.cls.php:115 msgid "Default HTTP Status Code Page TTL" msgstr "" #: src/lang.cls.php:116 msgid "Browser Cache TTL" msgstr "" #: src/lang.cls.php:117 msgid "AJAX Cache TTL" msgstr "" #: src/lang.cls.php:118 msgid "Automatically Upgrade" msgstr "" #: src/lang.cls.php:119 msgid "Guest Mode" msgstr "" #: src/lang.cls.php:120 msgid "Guest Optimization" msgstr "" #: src/lang.cls.php:121 msgid "Notifications" msgstr "" #: src/lang.cls.php:122 msgid "Cache Logged-in Users" msgstr "" #: src/lang.cls.php:123 msgid "Cache Commenters" msgstr "" #: src/lang.cls.php:124 msgid "Cache REST API" msgstr "" #: src/lang.cls.php:125 msgid "Cache Login Page" msgstr "" #: src/lang.cls.php:126 #: tpl/cache/settings_inc.cache_mobile.tpl.php:90 msgid "Cache Mobile" msgstr "" #: src/lang.cls.php:127 #: tpl/cache/settings_inc.cache_mobile.tpl.php:92 msgid "List of Mobile User Agents" msgstr "" #: src/lang.cls.php:128 msgid "Private Cached URIs" msgstr "" #: src/lang.cls.php:129 msgid "Drop Query String" msgstr "" #: src/lang.cls.php:132 msgid "Method" msgstr "" #: src/lang.cls.php:133 msgid "Host" msgstr "" #: src/lang.cls.php:134 msgid "Port" msgstr "" #: src/lang.cls.php:135 msgid "Default Object Lifetime" msgstr "" #: src/lang.cls.php:136 msgid "Username" msgstr "" #: src/lang.cls.php:137 msgid "Password" msgstr "" #: src/lang.cls.php:138 msgid "Redis Database ID" msgstr "" #: src/lang.cls.php:139 msgid "Global Groups" msgstr "" #: src/lang.cls.php:140 msgid "Do Not Cache Groups" msgstr "" #: src/lang.cls.php:141 msgid "Persistent Connection" msgstr "" #: src/lang.cls.php:142 msgid "Cache WP-Admin" msgstr "" #: src/lang.cls.php:143 msgid "Store Transients" msgstr "" #: src/lang.cls.php:145 msgid "Purge All On Upgrade" msgstr "" #: src/lang.cls.php:146 msgid "Serve Stale" msgstr "" #: src/lang.cls.php:147 #: tpl/cache/settings-purge.tpl.php:131 msgid "Scheduled Purge URLs" msgstr "" #: src/lang.cls.php:148 #: tpl/cache/settings-purge.tpl.php:106 msgid "Scheduled Purge Time" msgstr "" #: src/lang.cls.php:149 msgid "Force Cache URIs" msgstr "" #: src/lang.cls.php:150 msgid "Force Public Cache URIs" msgstr "" #: src/lang.cls.php:151 msgid "Do Not Cache URIs" msgstr "" #: src/lang.cls.php:152 msgid "Do Not Cache Query Strings" msgstr "" #: src/lang.cls.php:153 msgid "Do Not Cache Categories" msgstr "" #: src/lang.cls.php:154 msgid "Do Not Cache Tags" msgstr "" #: src/lang.cls.php:155 msgid "Do Not Cache Roles" msgstr "" #: src/lang.cls.php:156 msgid "CSS Minify" msgstr "" #: src/lang.cls.php:157 msgid "CSS Combine" msgstr "" #: src/lang.cls.php:158 msgid "CSS Combine External and Inline" msgstr "" #: src/lang.cls.php:159 msgid "Generate UCSS" msgstr "" #: src/lang.cls.php:160 msgid "UCSS Inline" msgstr "" #: src/lang.cls.php:161 msgid "UCSS Selector Allowlist" msgstr "" #: src/lang.cls.php:162 msgid "UCSS Inline Excluded Files" msgstr "" #: src/lang.cls.php:163 msgid "UCSS URI Excludes" msgstr "" #: src/lang.cls.php:164 msgid "JS Minify" msgstr "" #: src/lang.cls.php:165 msgid "JS Combine" msgstr "" #: src/lang.cls.php:166 msgid "JS Combine External and Inline" msgstr "" #: src/lang.cls.php:167 msgid "HTML Minify" msgstr "" #: src/lang.cls.php:168 msgid "HTML Lazy Load Selectors" msgstr "" #: src/lang.cls.php:169 msgid "HTML Keep Comments" msgstr "" #: src/lang.cls.php:170 #: tpl/page_optm/settings_tuning_css.tpl.php:167 msgid "Load CSS Asynchronously" msgstr "" #: src/lang.cls.php:171 msgid "CCSS Per URL" msgstr "" #: src/lang.cls.php:172 msgid "Inline CSS Async Lib" msgstr "" #: src/lang.cls.php:173 #: tpl/presets/standard.tpl.php:46 msgid "Font Display Optimization" msgstr "" #: src/lang.cls.php:174 msgid "Load JS Deferred" msgstr "" #: src/lang.cls.php:175 msgid "Localize Resources" msgstr "" #: src/lang.cls.php:176 msgid "Localization Files" msgstr "" #: src/lang.cls.php:177 msgid "DNS Prefetch" msgstr "" #: src/lang.cls.php:178 msgid "DNS Prefetch Control" msgstr "" #: src/lang.cls.php:179 msgid "DNS Preconnect" msgstr "" #: src/lang.cls.php:180 msgid "CSS Excludes" msgstr "" #: src/lang.cls.php:181 msgid "JS Delayed Includes" msgstr "" #: src/lang.cls.php:182 msgid "JS Excludes" msgstr "" #: src/lang.cls.php:183 msgid "Remove Query Strings" msgstr "" #: src/lang.cls.php:184 msgid "Load Google Fonts Asynchronously" msgstr "" #: src/lang.cls.php:185 msgid "Remove Google Fonts" msgstr "" #: src/lang.cls.php:186 msgid "Critical CSS Rules" msgstr "" #: src/lang.cls.php:187 msgid "Separate CCSS Cache Post Types" msgstr "" #: src/lang.cls.php:188 msgid "Separate CCSS Cache URIs" msgstr "" #: src/lang.cls.php:189 msgid "CCSS Selector Allowlist" msgstr "" #: src/lang.cls.php:190 msgid "JS Deferred / Delayed Excludes" msgstr "" #: src/lang.cls.php:191 msgid "Guest Mode JS Excludes" msgstr "" #: src/lang.cls.php:192 #: tpl/presets/standard.tpl.php:51 msgid "Remove WordPress Emoji" msgstr "" #: src/lang.cls.php:193 #: tpl/presets/standard.tpl.php:52 msgid "Remove Noscript Tags" msgstr "" #: src/lang.cls.php:194 msgid "URI Excludes" msgstr "" #: src/lang.cls.php:195 msgid "Optimize for Guests Only" msgstr "" #: src/lang.cls.php:196 msgid "Role Excludes" msgstr "" #: src/lang.cls.php:199 msgid "Gravatar Cache Cron" msgstr "" #: src/lang.cls.php:200 msgid "Gravatar Cache TTL" msgstr "" #: src/lang.cls.php:202 msgid "Lazy Load Images" msgstr "" #: src/lang.cls.php:203 msgid "Lazy Load Image Excludes" msgstr "" #: src/lang.cls.php:204 msgid "Lazy Load Image Class Name Excludes" msgstr "" #: src/lang.cls.php:205 msgid "Lazy Load Image Parent Class Name Excludes" msgstr "" #: src/lang.cls.php:206 msgid "Lazy Load Iframe Class Name Excludes" msgstr "" #: src/lang.cls.php:207 msgid "Lazy Load Iframe Parent Class Name Excludes" msgstr "" #: src/lang.cls.php:208 msgid "Lazy Load URI Excludes" msgstr "" #: src/lang.cls.php:209 msgid "LQIP Excludes" msgstr "" #: src/lang.cls.php:210 msgid "Basic Image Placeholder" msgstr "" #: src/lang.cls.php:211 msgid "Responsive Placeholder" msgstr "" #: src/lang.cls.php:212 msgid "Responsive Placeholder Color" msgstr "" #: src/lang.cls.php:213 msgid "Responsive Placeholder SVG" msgstr "" #: src/lang.cls.php:214 msgid "LQIP Cloud Generator" msgstr "" #: src/lang.cls.php:215 msgid "LQIP Quality" msgstr "" #: src/lang.cls.php:216 msgid "LQIP Minimum Dimensions" msgstr "" #: src/lang.cls.php:217 msgid "Generate LQIP In Background" msgstr "" #: src/lang.cls.php:218 msgid "Lazy Load Iframes" msgstr "" #: src/lang.cls.php:219 msgid "Add Missing Sizes" msgstr "" #: src/lang.cls.php:220 #: src/metabox.cls.php:42 #: src/metabox.cls.php:43 #: tpl/page_optm/settings_vpi.tpl.php:23 msgid "Viewport Images" msgstr "" #: src/lang.cls.php:221 msgid "Viewport Images Cron" msgstr "" #: src/lang.cls.php:222 msgid "Auto Rescale Original Images" msgstr "" #: src/lang.cls.php:224 msgid "Auto Request Cron" msgstr "" #: src/lang.cls.php:225 msgid "Optimize Original Images" msgstr "" #: src/lang.cls.php:226 msgid "Remove Original Backups" msgstr "" #: src/lang.cls.php:227 msgid "Next-Gen Image Format" msgstr "" #: src/lang.cls.php:228 msgid "Optimize Losslessly" msgstr "" #: src/lang.cls.php:229 msgid "Optimize Image Sizes" msgstr "" #: src/lang.cls.php:230 msgid "Preserve EXIF/XMP data" msgstr "" #: src/lang.cls.php:231 msgid "WebP/AVIF Attribute To Replace" msgstr "" #: src/lang.cls.php:232 msgid "WebP/AVIF For Extra srcset" msgstr "" #: src/lang.cls.php:233 msgid "WordPress Image Quality Control" msgstr "" #: src/lang.cls.php:234 #: tpl/esi_widget_edit.php:43 msgid "Enable ESI" msgstr "" #: src/lang.cls.php:235 msgid "Cache Admin Bar" msgstr "" #: src/lang.cls.php:236 msgid "Cache Comment Form" msgstr "" #: src/lang.cls.php:237 msgid "ESI Nonces" msgstr "" #: src/lang.cls.php:238 #: tpl/page_optm/settings_css.tpl.php:140 #: tpl/page_optm/settings_css.tpl.php:277 #: tpl/page_optm/settings_vpi.tpl.php:88 msgid "Vary Group" msgstr "" #: src/lang.cls.php:239 msgid "Purge All Hooks" msgstr "" #: src/lang.cls.php:240 msgid "Improve HTTP/HTTPS Compatibility" msgstr "" #: src/lang.cls.php:241 msgid "Instant Click" msgstr "" #: src/lang.cls.php:242 msgid "Do Not Cache Cookies" msgstr "" #: src/lang.cls.php:243 msgid "Do Not Cache User Agents" msgstr "" #: src/lang.cls.php:244 msgid "Login Cookie" msgstr "" #: src/lang.cls.php:245 msgid "Vary Cookies" msgstr "" #: src/lang.cls.php:247 msgid "Frontend Heartbeat Control" msgstr "" #: src/lang.cls.php:248 msgid "Frontend Heartbeat TTL" msgstr "" #: src/lang.cls.php:249 msgid "Backend Heartbeat Control" msgstr "" #: src/lang.cls.php:250 msgid "Backend Heartbeat TTL" msgstr "" #: src/lang.cls.php:251 msgid "Editor Heartbeat" msgstr "" #: src/lang.cls.php:252 msgid "Editor Heartbeat TTL" msgstr "" #: src/lang.cls.php:254 msgid "Use CDN Mapping" msgstr "" #: src/lang.cls.php:255 msgid "CDN URL" msgstr "" #: src/lang.cls.php:256 msgid "Include Images" msgstr "" #: src/lang.cls.php:257 msgid "Include CSS" msgstr "" #: src/lang.cls.php:258 msgid "Include JS" msgstr "" #: src/lang.cls.php:259 #: tpl/cdn/other.tpl.php:113 msgid "Include File Types" msgstr "" #: src/lang.cls.php:260 msgid "HTML Attribute To Replace" msgstr "" #: src/lang.cls.php:261 msgid "Original URLs" msgstr "" #: src/lang.cls.php:262 msgid "Included Directories" msgstr "" #: src/lang.cls.php:263 msgid "Exclude Path" msgstr "" #: src/lang.cls.php:264 msgid "Cloudflare API" msgstr "" #: src/lang.cls.php:265 msgid "Clear Cloudflare cache" msgstr "" #: src/lang.cls.php:268 msgid "Crawl Interval" msgstr "" #: src/lang.cls.php:269 msgid "Server Load Limit" msgstr "" #: src/lang.cls.php:270 msgid "Role Simulation" msgstr "" #: src/lang.cls.php:271 msgid "Cookie Simulation" msgstr "" #: src/lang.cls.php:272 msgid "Custom Sitemap" msgstr "" #: src/lang.cls.php:274 msgid "Disable All Features" msgstr "" #: src/lang.cls.php:275 #: tpl/toolbox/log_viewer.tpl.php:18 msgid "Debug Log" msgstr "" #: src/lang.cls.php:276 msgid "Admin IPs" msgstr "" #: src/lang.cls.php:277 msgid "Debug Level" msgstr "" #: src/lang.cls.php:278 msgid "Log File Size Limit" msgstr "" #: src/lang.cls.php:279 msgid "Collapse Query Strings" msgstr "" #: src/lang.cls.php:280 msgid "Debug URI Includes" msgstr "" #: src/lang.cls.php:281 msgid "Debug URI Excludes" msgstr "" #: src/lang.cls.php:282 msgid "Debug String Excludes" msgstr "" #: src/lang.cls.php:284 msgid "Revisions Max Number" msgstr "" #: src/lang.cls.php:285 msgid "Revisions Max Age" msgstr "" #: src/media.cls.php:361 msgid "LiteSpeed Optimization" msgstr "" #: src/media.cls.php:416 #: src/media.cls.php:454 #: src/media.cls.php:483 #: src/media.cls.php:527 msgid "(optm)" msgstr "" #: src/media.cls.php:417 msgid "Currently using optimized version of file." msgstr "" #: src/media.cls.php:417 #: src/media.cls.php:487 msgid "Click to switch to original (unoptimized) version." msgstr "" #: src/media.cls.php:420 #: src/media.cls.php:490 msgid "(non-optm)" msgstr "" #: src/media.cls.php:421 msgid "Currently using original (unoptimized) version of file." msgstr "" #: src/media.cls.php:421 #: src/media.cls.php:494 msgid "Click to switch to optimized version." msgstr "" #: src/media.cls.php:429 msgid "Original file reduced by %1$s (%2$s)" msgstr "" #: src/media.cls.php:438 msgid "Orig saved %s" msgstr "" #: src/media.cls.php:453 #: src/media.cls.php:525 msgid "Using optimized version of file. " msgstr "" #: src/media.cls.php:453 msgid "No backup of original file exists." msgstr "" #: src/media.cls.php:458 msgid "Congratulation! Your file was already optimized" msgstr "" #: src/media.cls.php:460 msgid "Orig %s" msgstr "" #: src/media.cls.php:461 msgid "(no savings)" msgstr "" #: src/media.cls.php:464 msgid "Orig" msgstr "" #: src/media.cls.php:485 msgid "Currently using optimized version of AVIF file." msgstr "" #: src/media.cls.php:486 msgid "Currently using optimized version of WebP file." msgstr "" #: src/media.cls.php:492 msgid "Currently using original (unoptimized) version of AVIF file." msgstr "" #: src/media.cls.php:493 msgid "Currently using original (unoptimized) version of WebP file." msgstr "" #: src/media.cls.php:502 msgid "AVIF file reduced by %1$s (%2$s)" msgstr "" #: src/media.cls.php:502 msgid "WebP file reduced by %1$s (%2$s)" msgstr "" #: src/media.cls.php:510 msgid "AVIF saved %s" msgstr "" #: src/media.cls.php:510 msgid "WebP saved %s" msgstr "" #: src/media.cls.php:526 msgid "No backup of unoptimized AVIF file exists." msgstr "" #: src/media.cls.php:526 msgid "No backup of unoptimized WebP file exists." msgstr "" #: src/media.cls.php:541 msgid "Restore from backup" msgstr "" #: src/metabox.cls.php:39 msgid "Disable Cache" msgstr "" #: src/metabox.cls.php:40 msgid "Disable Image Lazyload" msgstr "" #: src/metabox.cls.php:41 msgid "Disable VPI" msgstr "" #: src/metabox.cls.php:43 msgid "Mobile" msgstr "" #: src/object-cache.cls.php:714 msgid "Redis encountered a fatal error: %1$s (code: %2$d)" msgstr "" #: src/placeholder.cls.php:169 msgid "LQIP" msgstr "" #: src/placeholder.cls.php:233 msgid "LQIP image preview for size %s" msgstr "" #: src/purge.cls.php:234 msgid "Purged all caches successfully." msgstr "" #: src/purge.cls.php:270 msgid "Notified LiteSpeed Web Server to purge all LSCache entries." msgstr "" #: src/purge.cls.php:291 msgid "Cleaned all Critical CSS files." msgstr "" #: src/purge.cls.php:312 msgid "Cleaned all Unique CSS files." msgstr "" #: src/purge.cls.php:359 msgid "Cleaned all LQIP files." msgstr "" #: src/purge.cls.php:393 msgid "Cleaned all VPI data." msgstr "" #: src/purge.cls.php:414 msgid "Cleaned all Gravatar files." msgstr "" #: src/purge.cls.php:434 msgid "Cleaned all localized resource entries." msgstr "" #: src/purge.cls.php:471 msgid "Notified LiteSpeed Web Server to purge CSS/JS entries." msgstr "" #: src/purge.cls.php:491 msgid "OPcache is not enabled." msgstr "" #: src/purge.cls.php:504 msgid "OPcache is restricted by %s setting." msgstr "" #: src/purge.cls.php:517 msgid "Reset the OPcache failed." msgstr "" #: src/purge.cls.php:531 msgid "Reset the entire OPcache successfully." msgstr "" #: src/purge.cls.php:563 msgid "Object cache is not enabled." msgstr "" #: src/purge.cls.php:576 msgid "Purge all object caches successfully." msgstr "" #: src/purge.cls.php:793 msgid "Notified LiteSpeed Web Server to purge the front page." msgstr "" #: src/purge.cls.php:809 msgid "Notified LiteSpeed Web Server to purge all pages." msgstr "" #: src/purge.cls.php:832 msgid "Notified LiteSpeed Web Server to purge error pages." msgstr "" #: src/purge.cls.php:863 msgid "Purge category %s" msgstr "" #: src/purge.cls.php:894 msgid "Purge tag %s" msgstr "" #: src/purge.cls.php:931 msgid "Purge url %s" msgstr "" #: src/root.cls.php:198 msgid "All QUIC.cloud service queues have been cleared." msgstr "" #: src/task.cls.php:268 msgid "Every 15 Minutes" msgstr "" #: src/task.cls.php:289 msgid "LiteSpeed Crawler Cron" msgstr "" #: src/tool.cls.php:43 #: src/tool.cls.php:54 msgid "Failed to detect IP" msgstr "" #: src/utility.cls.php:214 msgid "right now" msgstr "" #: src/utility.cls.php:214 msgid "just now" msgstr "" #: src/utility.cls.php:217 msgid " %s ago" msgstr "" #: thirdparty/litespeed-check.cls.php:100 #: thirdparty/litespeed-check.cls.php:165 msgid "Please consider disabling the following detected plugins, as they may conflict with LiteSpeed Cache:" msgstr "" #: thirdparty/woocommerce.content.tpl.php:19 msgid "WooCommerce Settings" msgstr "" #: thirdparty/woocommerce.content.tpl.php:24 #: tpl/cache/settings-advanced.tpl.php:21 #: tpl/cache/settings_inc.browser.tpl.php:23 #: tpl/toolbox/beta_test.tpl.php:42 #: tpl/toolbox/heartbeat.tpl.php:24 #: tpl/toolbox/report.tpl.php:46 msgid "NOTICE:" msgstr "" #: thirdparty/woocommerce.content.tpl.php:25 msgid "After verifying that the cache works in general, please test the cart." msgstr "" #. translators: %s: link attributes #: thirdparty/woocommerce.content.tpl.php:30 msgid "To test the cart, visit the %sFAQ%s." msgstr "" #: thirdparty/woocommerce.content.tpl.php:36 msgid "By default, the My Account, Checkout, and Cart pages are automatically excluded from caching. Misconfiguration of page associations in WooCommerce settings may cause some pages to be erroneously excluded." msgstr "" #: thirdparty/woocommerce.content.tpl.php:44 msgid "Product Update Interval" msgstr "" #: thirdparty/woocommerce.content.tpl.php:49 msgid "Purge product on changes to the quantity or stock status." msgstr "" #: thirdparty/woocommerce.content.tpl.php:49 msgid "Purge categories only when stock status changes." msgstr "" #: thirdparty/woocommerce.content.tpl.php:50 msgid "Purge product and categories only when the stock status changes." msgstr "" #: thirdparty/woocommerce.content.tpl.php:51 msgid "Purge product only when the stock status changes." msgstr "" #: thirdparty/woocommerce.content.tpl.php:51 msgid "Do not purge categories on changes to the quantity or stock status." msgstr "" #: thirdparty/woocommerce.content.tpl.php:52 msgid "Always purge both product and categories on changes to the quantity or stock status." msgstr "" #: thirdparty/woocommerce.content.tpl.php:72 msgid "Determines how changes in product quantity and product stock status affect product pages and their associated category pages." msgstr "" #: thirdparty/woocommerce.content.tpl.php:80 msgid "Vary for Mini Cart" msgstr "" #: thirdparty/woocommerce.content.tpl.php:88 msgid "Generate a separate vary cache copy for the mini cart when the cart is not empty." msgstr "" #: thirdparty/woocommerce.content.tpl.php:89 msgid "If your theme does not use JS to update the mini cart, you must enable this option to display the correct cart contents." msgstr "" #: tpl/banner/cloud_news.tpl.php:30 #: tpl/banner/cloud_news.tpl.php:41 msgid "Install" msgstr "" #: tpl/banner/cloud_news.tpl.php:51 #: tpl/banner/cloud_promo.tpl.php:73 msgid "Dismiss this notice" msgstr "" #: tpl/banner/cloud_promo.tpl.php:22 msgid "You just unlocked a promotion from QUIC.cloud!" msgstr "" #: tpl/banner/cloud_promo.tpl.php:26 msgid "Spread the love and earn %s credits to use in our QUIC.cloud online services." msgstr "" #: tpl/banner/cloud_promo.tpl.php:35 msgid "Send to twitter to get %s bonus" msgstr "" #: tpl/banner/cloud_promo.tpl.php:40 #: tpl/page_optm/settings_tuning_css.tpl.php:69 #: tpl/page_optm/settings_tuning_css.tpl.php:144 msgid "Learn more" msgstr "" #: tpl/banner/cloud_promo.tpl.php:45 msgid "Tweet preview" msgstr "" #: tpl/banner/cloud_promo.tpl.php:61 msgid "Tweet this" msgstr "" #: tpl/banner/new_version.php:58 msgid "New Version Available!" msgstr "" #: tpl/banner/new_version.php:66 msgid "New release %s is available now." msgstr "" #: tpl/banner/new_version.php:77 #: tpl/banner/new_version_dev.tpl.php:41 #: tpl/toolbox/beta_test.tpl.php:88 msgid "Upgrade" msgstr "" #: tpl/banner/new_version.php:87 msgid "Turn On Auto Upgrade" msgstr "" #: tpl/banner/new_version.php:93 msgid "Maybe Later" msgstr "" #: tpl/banner/new_version.php:113 #: tpl/banner/score.php:141 #: tpl/banner/slack.php:48 msgid "Dismiss this notice." msgstr "" #: tpl/banner/new_version_dev.tpl.php:22 msgid "New Developer Version Available!" msgstr "" #: tpl/banner/new_version_dev.tpl.php:30 msgid "New developer version %s is available now." msgstr "" #: tpl/banner/score.php:36 msgid "Thank You for Using the LiteSpeed Cache Plugin!" msgstr "" #: tpl/banner/score.php:40 #: tpl/dash/dashboard.tpl.php:375 msgid "Page Load Time" msgstr "" #: tpl/banner/score.php:45 #: tpl/banner/score.php:79 #: tpl/dash/dashboard.tpl.php:395 #: tpl/dash/dashboard.tpl.php:471 msgid "Before" msgstr "" #: tpl/banner/score.php:53 #: tpl/banner/score.php:87 #: tpl/dash/dashboard.tpl.php:403 #: tpl/dash/dashboard.tpl.php:479 msgid "After" msgstr "" #: tpl/banner/score.php:62 #: tpl/banner/score.php:96 #: tpl/dash/dashboard.tpl.php:411 #: tpl/dash/dashboard.tpl.php:487 msgid "Improved by" msgstr "" #: tpl/banner/score.php:74 #: tpl/dash/dashboard.tpl.php:456 msgid "PageSpeed Score" msgstr "" #: tpl/banner/score.php:112 msgid "Sure I'd love to review!" msgstr "" #: tpl/banner/score.php:116 msgid "I've already left a review" msgstr "" #: tpl/banner/score.php:117 msgid "Maybe later" msgstr "" #: tpl/banner/score.php:121 msgid "Created with ❤️ by LiteSpeed team." msgstr "" #: tpl/banner/score.php:122 msgid "Support forum" msgstr "" #: tpl/banner/score.php:122 msgid "Submit a ticket" msgstr "" #: tpl/banner/slack.php:20 msgid "Welcome to LiteSpeed" msgstr "" #: tpl/banner/slack.php:24 msgid "Want to connect with other LiteSpeed users?" msgstr "" #. translators: %s: Link to LiteSpeed Slack community #: tpl/banner/slack.php:28 msgid "Join the %s community." msgstr "" #: tpl/banner/slack.php:40 msgid "Join Us on Slack" msgstr "" #: tpl/cache/entry.tpl.php:18 #: tpl/cache/entry.tpl.php:68 #: tpl/toolbox/entry.tpl.php:16 #: tpl/toolbox/purge.tpl.php:150 msgid "Purge" msgstr "" #: tpl/cache/entry.tpl.php:19 #: tpl/cache/entry.tpl.php:69 msgid "Excludes" msgstr "" #: tpl/cache/entry.tpl.php:20 #: tpl/cache/entry.tpl.php:74 msgid "Object" msgstr "" #: tpl/cache/entry.tpl.php:21 #: tpl/cache/entry.tpl.php:75 msgid "Browser" msgstr "" #: tpl/cache/entry.tpl.php:22 #: tpl/cache/entry.tpl.php:78 #: tpl/toolbox/settings-debug.tpl.php:117 msgid "Advanced" msgstr "" #: tpl/cache/entry.tpl.php:28 msgid "LiteSpeed Cache Network Cache Settings" msgstr "" #: tpl/cache/entry.tpl.php:67 #: tpl/cache/settings-ttl.tpl.php:15 msgid "TTL" msgstr "" #: tpl/cache/entry.tpl.php:70 msgid "ESI" msgstr "" #: tpl/cache/entry.tpl.php:100 msgid "LiteSpeed Cache Settings" msgstr "" #: tpl/cache/more_settings_tip.tpl.php:22 #: tpl/cache/settings-excludes.tpl.php:71 #: tpl/cache/settings-excludes.tpl.php:104 #: tpl/cdn/other.tpl.php:79 #: tpl/crawler/settings.tpl.php:76 #: tpl/crawler/settings.tpl.php:86 msgid "NOTE" msgstr "" #. translators: %s: LiteSpeed Cache menu label #: tpl/cache/more_settings_tip.tpl.php:27 msgid "More settings available under %s menu" msgstr "" #: tpl/cache/network_settings-advanced.tpl.php:17 #: tpl/cache/settings-advanced.tpl.php:16 msgid "Advanced Settings" msgstr "" #: tpl/cache/network_settings-cache.tpl.php:17 #: tpl/cache/settings-cache.tpl.php:15 msgid "Cache Control Settings" msgstr "" #: tpl/cache/network_settings-cache.tpl.php:24 msgid "Network Enable Cache" msgstr "" #: tpl/cache/network_settings-cache.tpl.php:28 msgid "Enabling LiteSpeed Cache for WordPress here enables the cache for the network." msgstr "" #: tpl/cache/network_settings-cache.tpl.php:29 msgid "It is STRONGLY recommended that the compatibility with other plugins on a single/few sites is tested first." msgstr "" #: tpl/cache/network_settings-cache.tpl.php:30 msgid "This is to ensure compatibility prior to enabling the cache for all sites." msgstr "" #: tpl/cache/network_settings-excludes.tpl.php:17 #: tpl/cache/settings-excludes.tpl.php:15 msgid "Exclude Settings" msgstr "" #: tpl/cache/network_settings-purge.tpl.php:17 #: tpl/cache/settings-purge.tpl.php:15 msgid "Purge Settings" msgstr "" #: tpl/cache/settings-advanced.tpl.php:22 msgid "These settings are meant for ADVANCED USERS ONLY." msgstr "" #: tpl/cache/settings-advanced.tpl.php:39 msgid "Specify an AJAX action in POST/GET and the number of seconds to cache that request, separated by a space." msgstr "" #: tpl/cache/settings-advanced.tpl.php:57 msgid "Enable this option if you are using both HTTP and HTTPS in the same domain and are noticing cache irregularities." msgstr "" #: tpl/cache/settings-advanced.tpl.php:71 msgid "When a visitor hovers over a page link, preload that page. This will speed up the visit to that link." msgstr "" #: tpl/cache/settings-advanced.tpl.php:76 msgid "This will generate extra requests to the server, which will increase server load." msgstr "" #: tpl/cache/settings-cache.tpl.php:28 msgid "Use Network Admin Setting" msgstr "" #. translators: %s: Link tags #: tpl/cache/settings-cache.tpl.php:36 msgid "Please visit the %sInformation%s page on how to test the cache." msgstr "" #: tpl/cache/settings-cache.tpl.php:42 #: tpl/crawler/settings.tpl.php:113 #: tpl/crawler/settings.tpl.php:133 #: tpl/crawler/summary.tpl.php:208 #: tpl/page_optm/entry.tpl.php:42 #: tpl/toolbox/settings-debug.tpl.php:47 msgid "NOTICE" msgstr "" #: tpl/cache/settings-cache.tpl.php:42 msgid "When disabling the cache, all cached entries for this site will be purged." msgstr "" #: tpl/cache/settings-cache.tpl.php:45 msgid "The network admin setting can be overridden here." msgstr "" #: tpl/cache/settings-cache.tpl.php:49 msgid "With QUIC.cloud CDN enabled, you may still be seeing cache headers from your local server." msgstr "" #: tpl/cache/settings-cache.tpl.php:63 msgid "Privately cache frontend pages for logged-in users. (LSWS %s required)" msgstr "" #: tpl/cache/settings-cache.tpl.php:76 msgid "Privately cache commenters that have pending comments. Disabling this option will serve non-cacheable pages to commenters. (LSWS %s required)" msgstr "" #: tpl/cache/settings-cache.tpl.php:89 msgid "Cache requests made by WordPress REST API calls." msgstr "" #: tpl/cache/settings-cache.tpl.php:102 msgid "Disabling this option may negatively affect performance." msgstr "" #: tpl/cache/settings-cache.tpl.php:119 msgid "URI Paths containing these strings will NOT be cached as public." msgstr "" #: tpl/cache/settings-cache.tpl.php:133 msgid "Paths containing these strings will be cached regardless of no-cacheable settings." msgstr "" #: tpl/cache/settings-cache.tpl.php:136 #: tpl/cache/settings-cache.tpl.php:161 msgid "To define a custom TTL for a URI, add a space followed by the TTL value to the end of the URI." msgstr "" #: tpl/cache/settings-cache.tpl.php:139 #: tpl/cache/settings-cache.tpl.php:164 msgid "For example, %1$s defines a TTL of %2$s seconds for %3$s." msgstr "" #: tpl/cache/settings-cache.tpl.php:158 msgid "Paths containing these strings will be forced to public cached regardless of no-cacheable settings." msgstr "" #: tpl/cache/settings-esi.tpl.php:15 msgid "ESI Settings" msgstr "" #: tpl/cache/settings-esi.tpl.php:20 msgid "With ESI (Edge Side Includes), pages may be served from cache for logged-in users." msgstr "" #: tpl/cache/settings-esi.tpl.php:21 msgid "ESI allows you to designate parts of your dynamic page as separate fragments that are then assembled together to make the whole page. In other words, ESI lets you “punch holes” in a page, and then fill those holes with content that may be cached privately, cached publicly with its own TTL, or not cached at all." msgstr "" #: tpl/cache/settings-esi.tpl.php:22 msgid "WpW: Private Cache vs. Public Cache" msgstr "" #: tpl/cache/settings-esi.tpl.php:26 msgid "You can turn shortcodes into ESI blocks." msgstr "" #: tpl/cache/settings-esi.tpl.php:29 msgid "Replace %1$s with %2$s." msgstr "" #: tpl/cache/settings-esi.tpl.php:37 msgid "ESI sample for developers" msgstr "" #: tpl/cache/settings-esi.tpl.php:45 #: tpl/cdn/cf.tpl.php:100 #: tpl/crawler/summary.tpl.php:60 #: tpl/inc/check_cache_disabled.php:38 #: tpl/inc/check_if_network_disable_all.php:28 #: tpl/page_optm/settings_css.tpl.php:77 #: tpl/page_optm/settings_css.tpl.php:211 #: tpl/page_optm/settings_localization.tpl.php:21 msgid "WARNING" msgstr "" #: tpl/cache/settings-esi.tpl.php:46 msgid "These options are only available with LiteSpeed Enterprise Web Server or QUIC.cloud CDN." msgstr "" #: tpl/cache/settings-esi.tpl.php:59 msgid "Turn ON to cache public pages for logged in users, and serve the Admin Bar and Comment Form via ESI blocks. These two blocks will be uncached unless enabled below." msgstr "" #: tpl/cache/settings-esi.tpl.php:72 msgid "Cache the built-in Admin Bar ESI block." msgstr "" #: tpl/cache/settings-esi.tpl.php:85 msgid "Cache the built-in Comment Form ESI block." msgstr "" #: tpl/cache/settings-esi.tpl.php:100 msgid "The list will be merged with the predefined nonces in your local data file." msgstr "" #: tpl/cache/settings-esi.tpl.php:101 msgid "The latest data file is" msgstr "" #: tpl/cache/settings-esi.tpl.php:104 #: tpl/page_optm/settings_media_exc.tpl.php:37 #: tpl/page_optm/settings_tuning.tpl.php:49 #: tpl/page_optm/settings_tuning.tpl.php:69 #: tpl/page_optm/settings_tuning.tpl.php:90 #: tpl/page_optm/settings_tuning.tpl.php:111 #: tpl/page_optm/settings_tuning.tpl.php:130 #: tpl/page_optm/settings_tuning_css.tpl.php:36 #: tpl/page_optm/settings_tuning_css.tpl.php:97 msgid "Filter %s is supported." msgstr "" #: tpl/cache/settings-esi.tpl.php:108 msgid "The above nonces will be converted to ESI automatically." msgstr "" #: tpl/cache/settings-esi.tpl.php:110 msgid "An optional second parameter may be used to specify cache control. Use a space to separate" msgstr "" #: tpl/cache/settings-esi.tpl.php:113 #: tpl/cache/settings-purge.tpl.php:111 #: tpl/cdn/other.tpl.php:169 msgid "Wildcard %1$s supported (match zero or more characters). For example, to match %2$s and %3$s, use %4$s." msgstr "" #: tpl/cache/settings-esi.tpl.php:141 msgid "If your site contains public content that certain user roles can see but other roles cannot, you can specify a Vary Group for those user roles. For example, specifying an administrator vary group allows there to be a separate publicly-cached page tailored to administrators (with “edit” links, etc), while all other user roles see the default public page." msgstr "" #: tpl/cache/settings-excludes.tpl.php:30 msgid "Paths containing these strings will not be cached." msgstr "" #: tpl/cache/settings-excludes.tpl.php:32 #: tpl/page_optm/settings_tuning_css.tpl.php:78 #: tpl/page_optm/settings_tuning_css.tpl.php:153 msgid "Predefined list will also be combined w/ the above settings" msgstr "" #: tpl/cache/settings-excludes.tpl.php:45 msgid "Query strings containing these parameters will not be cached." msgstr "" #: tpl/cache/settings-excludes.tpl.php:46 msgid "For example, for %1$s, %2$s and %3$s can be used here." msgstr "" #: tpl/cache/settings-excludes.tpl.php:66 msgid "All categories are cached by default." msgstr "" #. translators: %s: "cookies" #. translators: %s: "user agents" #: tpl/cache/settings-excludes.tpl.php:67 #: tpl/cache/settings-excludes.tpl.php:100 #: tpl/cache/settings_inc.exclude_cookies.tpl.php:27 #: tpl/cache/settings_inc.exclude_useragent.tpl.php:27 msgid "To prevent %s from being cached, enter them here." msgstr "" #: tpl/cache/settings-excludes.tpl.php:67 msgid "categories" msgstr "" #: tpl/cache/settings-excludes.tpl.php:73 msgid "If the category name is not found, the category will be removed from the list on save." msgstr "" #: tpl/cache/settings-excludes.tpl.php:99 msgid "All tags are cached by default." msgstr "" #: tpl/cache/settings-excludes.tpl.php:100 msgid "tags" msgstr "" #: tpl/cache/settings-excludes.tpl.php:106 msgid "If the tag slug is not found, the tag will be removed from the list on save." msgstr "" #: tpl/cache/settings-excludes.tpl.php:110 msgid "To exclude %1$s, insert %2$s." msgstr "" #: tpl/cache/settings-excludes.tpl.php:135 msgid "Selected roles will be excluded from cache." msgstr "" #: tpl/cache/settings-purge.tpl.php:21 msgid "All pages" msgstr "" #: tpl/cache/settings-purge.tpl.php:22 msgid "Front page" msgstr "" #: tpl/cache/settings-purge.tpl.php:23 msgid "Home page" msgstr "" #: tpl/cache/settings-purge.tpl.php:24 msgid "Pages" msgstr "" #: tpl/cache/settings-purge.tpl.php:25 msgid "All pages with Recent Posts Widget" msgstr "" #: tpl/cache/settings-purge.tpl.php:26 msgid "Author archive" msgstr "" #: tpl/cache/settings-purge.tpl.php:27 msgid "Post type archive" msgstr "" #: tpl/cache/settings-purge.tpl.php:28 msgid "Yearly archive" msgstr "" #: tpl/cache/settings-purge.tpl.php:29 msgid "Monthly archive" msgstr "" #: tpl/cache/settings-purge.tpl.php:30 msgid "Daily archive" msgstr "" #: tpl/cache/settings-purge.tpl.php:31 msgid "Term archive (include category, tag, and tax)" msgstr "" #: tpl/cache/settings-purge.tpl.php:50 msgid "Auto Purge Rules For Publish/Update" msgstr "" #: tpl/cache/settings-purge.tpl.php:53 #: tpl/cache/settings-purge.tpl.php:90 #: tpl/cache/settings-purge.tpl.php:114 #: tpl/page_optm/settings_tuning_css.tpl.php:72 #: tpl/page_optm/settings_tuning_css.tpl.php:147 msgid "Note" msgstr "" #: tpl/cache/settings-purge.tpl.php:55 msgid "Select \"All\" if there are dynamic widgets linked to posts on pages other than the front or home pages." msgstr "" #: tpl/cache/settings-purge.tpl.php:56 msgid "Other checkboxes will be ignored." msgstr "" #: tpl/cache/settings-purge.tpl.php:57 msgid "Select only the archive types that are currently used, the others can be left unchecked." msgstr "" #: tpl/cache/settings-purge.tpl.php:73 msgid "Select which pages will be automatically purged when posts are published/updated." msgstr "" #: tpl/cache/settings-purge.tpl.php:86 msgid "If ON, the stale copy of a cached page will be shown to visitors until a new cache copy is available. Reduces the server load for following visits. If OFF, the page will be dynamically generated while visitors wait." msgstr "" #: tpl/cache/settings-purge.tpl.php:92 msgid "By design, this option may serve stale content. Do not enable this option, if that is not OK with you." msgstr "" #: tpl/cache/settings-purge.tpl.php:106 msgid "The URLs here (one per line) will be purged automatically at the time set in the option \"%s\"." msgstr "" #: tpl/cache/settings-purge.tpl.php:107 msgid "Both %1$s and %2$s are acceptable." msgstr "" #: tpl/cache/settings-purge.tpl.php:116 msgid "For URLs with wildcards, there may be a delay in initiating scheduled purge." msgstr "" #: tpl/cache/settings-purge.tpl.php:131 msgid "Specify the time to purge the \"%s\" list." msgstr "" #: tpl/cache/settings-purge.tpl.php:132 msgid "Current server time is %s." msgstr "" #: tpl/cache/settings-purge.tpl.php:152 msgid "A Purge All will be executed when WordPress runs these hooks." msgstr "" #: tpl/cache/settings-ttl.tpl.php:29 msgid "Specify how long, in seconds, public pages are cached." msgstr "" #: tpl/cache/settings-ttl.tpl.php:44 msgid "Specify how long, in seconds, private pages are cached." msgstr "" #: tpl/cache/settings-ttl.tpl.php:59 msgid "Specify how long, in seconds, the front page is cached." msgstr "" #: tpl/cache/settings-ttl.tpl.php:74 msgid "Specify how long, in seconds, feeds are cached." msgstr "" #: tpl/cache/settings-ttl.tpl.php:75 #: tpl/cache/settings-ttl.tpl.php:90 msgid "If this is set to a number less than 30, feeds will not be cached." msgstr "" #: tpl/cache/settings-ttl.tpl.php:89 msgid "Specify how long, in seconds, REST calls are cached." msgstr "" #: tpl/cache/settings-ttl.tpl.php:111 msgid "Specify an HTTP status code and the number of seconds to cache that page, separated by a space." msgstr "" #: tpl/cache/settings_inc.browser.tpl.php:17 msgid "Browser Cache Settings" msgstr "" #: tpl/cache/settings_inc.browser.tpl.php:25 msgid "OpenLiteSpeed users please check this" msgstr "" #: tpl/cache/settings_inc.browser.tpl.php:26 msgid "Setting Up Custom Headers" msgstr "" #: tpl/cache/settings_inc.browser.tpl.php:41 msgid "Browser caching stores static files locally in the user's browser. Turn on this setting to reduce repeated requests for static files." msgstr "" #. translators: %s: Link tags #: tpl/cache/settings_inc.browser.tpl.php:46 msgid "You can turn on browser caching in server admin too. %sLearn more about LiteSpeed browser cache settings%s." msgstr "" #: tpl/cache/settings_inc.browser.tpl.php:63 msgid "The amount of time, in seconds, that files will be stored in browser cache before expiring." msgstr "" #. translators: %s: LiteSpeed Web Server version #: tpl/cache/settings_inc.cache_dropquery.tpl.php:27 msgid "Ignore certain query strings when caching. (LSWS %s required)" msgstr "" #. translators: %1$s: Example query string, %2$s: Example wildcard #: tpl/cache/settings_inc.cache_dropquery.tpl.php:34 msgid "For example, to drop parameters beginning with %1$s, %2$s can be used here." msgstr "" #: tpl/cache/settings_inc.cache_mobile.tpl.php:24 msgid "Serve a separate cache copy for mobile visitors." msgstr "" #: tpl/cache/settings_inc.cache_mobile.tpl.php:25 msgid "Learn more about when this is needed" msgstr "" #: tpl/cache/settings_inc.cache_mobile.tpl.php:47 msgid "Htaccess did not match configuration option." msgstr "" #. translators: %s: Current mobile agents in htaccess #: tpl/cache/settings_inc.cache_mobile.tpl.php:51 msgid "Htaccess rule is: %s" msgstr "" #. translators: %1$s: Cache Mobile label, %2$s: ON status, %3$s: List of Mobile User Agents label #: tpl/cache/settings_inc.cache_mobile.tpl.php:89 msgid "If %1$s is %2$s, then %3$s must be populated!" msgstr "" #: tpl/cache/settings_inc.exclude_cookies.tpl.php:28 msgid "cookies" msgstr "" #: tpl/cache/settings_inc.exclude_useragent.tpl.php:28 msgid "user agents" msgstr "" #: tpl/cache/settings_inc.login_cookie.tpl.php:26 msgid "SYNTAX: alphanumeric and \"_\". No spaces and case sensitive. MUST BE UNIQUE FROM OTHER WEB APPLICATIONS." msgstr "" #. translators: %s: Default login cookie name #: tpl/cache/settings_inc.login_cookie.tpl.php:32 msgid "The default login cookie is %s." msgstr "" #: tpl/cache/settings_inc.login_cookie.tpl.php:36 msgid "The server will determine if the user is logged in based on the existence of this cookie." msgstr "" #: tpl/cache/settings_inc.login_cookie.tpl.php:37 msgid "This setting is useful for those that have multiple web applications for the same domain." msgstr "" #: tpl/cache/settings_inc.login_cookie.tpl.php:38 msgid "If every web application uses the same cookie, the server may confuse whether a user is logged in or not." msgstr "" #: tpl/cache/settings_inc.login_cookie.tpl.php:39 msgid "The cookie set here will be used for this WordPress installation." msgstr "" #: tpl/cache/settings_inc.login_cookie.tpl.php:41 msgid "Example use case:" msgstr "" #. translators: %s: Example domain #: tpl/cache/settings_inc.login_cookie.tpl.php:45 msgid "There is a WordPress installed for %s." msgstr "" #. translators: %s: Example subdomain #: tpl/cache/settings_inc.login_cookie.tpl.php:53 msgid "Then another WordPress is installed (NOT MULTISITE) at %s" msgstr "" #: tpl/cache/settings_inc.login_cookie.tpl.php:57 msgid "The cache needs to distinguish who is logged into which WordPress site in order to cache correctly." msgstr "" #: tpl/cache/settings_inc.login_cookie.tpl.php:63 msgid "Invalid login cookie. Invalid characters found." msgstr "" #: tpl/cache/settings_inc.login_cookie.tpl.php:84 msgid "WARNING: The .htaccess login cookie and Database login cookie do not match." msgstr "" #: tpl/cache/settings_inc.login_cookie.tpl.php:102 msgid "SYNTAX: alphanumeric and \"_\". No spaces and case sensitive." msgstr "" #: tpl/cache/settings_inc.login_cookie.tpl.php:104 msgid "You can list the 3rd party vary cookies here." msgstr "" #: tpl/cache/settings_inc.object.tpl.php:15 msgid "Enabled" msgstr "" #: tpl/cache/settings_inc.object.tpl.php:16 msgid "Disabled" msgstr "" #: tpl/cache/settings_inc.object.tpl.php:23 msgid "Not Available" msgstr "" #: tpl/cache/settings_inc.object.tpl.php:25 msgid "Passed" msgstr "" #: tpl/cache/settings_inc.object.tpl.php:28 msgid "Failed" msgstr "" #: tpl/cache/settings_inc.object.tpl.php:33 msgid "Object Cache Settings" msgstr "" #: tpl/cache/settings_inc.object.tpl.php:47 msgid "Use external object cache functionality." msgstr "" #: tpl/cache/settings_inc.object.tpl.php:52 #: tpl/crawler/blacklist.tpl.php:42 #: tpl/crawler/summary.tpl.php:153 msgid "Status" msgstr "" #. translators: %s: Object cache name #: tpl/cache/settings_inc.object.tpl.php:58 #: tpl/cache/settings_inc.object.tpl.php:66 msgid "%s Extension" msgstr "" #: tpl/cache/settings_inc.object.tpl.php:71 msgid "Connection Test" msgstr "" #. translators: %s: Object cache name #: tpl/cache/settings_inc.object.tpl.php:99 msgid "Your %s Hostname or IP address." msgstr "" #. translators: %1$s: Socket name, %2$s: Host field title, %3$s: Example socket path #. translators: %1$s: Socket name, %2$s: Port field title, %3$s: Port value #: tpl/cache/settings_inc.object.tpl.php:107 #: tpl/cache/settings_inc.object.tpl.php:146 msgid "If you are using a %1$s socket, %2$s should be set to %3$s" msgstr "" #. translators: %1$s: Object cache name, %2$s: Port number #: tpl/cache/settings_inc.object.tpl.php:128 #: tpl/cache/settings_inc.object.tpl.php:137 msgid "Default port for %1$s is %2$s." msgstr "" #: tpl/cache/settings_inc.object.tpl.php:164 msgid "Default TTL for cached objects." msgstr "" #. translators: %s: SASL #: tpl/cache/settings_inc.object.tpl.php:180 msgid "Only available when %s is installed." msgstr "" #: tpl/cache/settings_inc.object.tpl.php:196 msgid "Specify the password used when connecting." msgstr "" #: tpl/cache/settings_inc.object.tpl.php:209 msgid "Database to be used" msgstr "" #: tpl/cache/settings_inc.object.tpl.php:222 msgid "Groups cached at the network level." msgstr "" #: tpl/cache/settings_inc.object.tpl.php:249 msgid "Use keep-alive connections to speed up cache operations." msgstr "" #: tpl/cache/settings_inc.object.tpl.php:262 msgid "Improve wp-admin speed through caching. (May encounter expired data)" msgstr "" #. translators: %1$s: Object Cache Admin title, %2$s: OFF status #: tpl/cache/settings_inc.object.tpl.php:278 msgid "Save transients in database when %1$s is %2$s." msgstr "" #: tpl/cache/settings_inc.purge_on_upgrade.tpl.php:25 msgid "When enabled, the cache will automatically purge when any plugin, theme or the WordPress core is upgraded." msgstr "" #: tpl/cdn/cf.tpl.php:17 msgid "Cloudflare Settings" msgstr "" #: tpl/cdn/cf.tpl.php:31 msgid "Use %s API functionality." msgstr "" #: tpl/cdn/cf.tpl.php:35 msgid "Global API Key / API Token" msgstr "" #: tpl/cdn/cf.tpl.php:38 msgid "Your API key / token is used to access %s APIs." msgstr "" #: tpl/cdn/cf.tpl.php:39 msgid "Get it from %s." msgstr "" #: tpl/cdn/cf.tpl.php:40 msgid "Recommended to generate the token from Cloudflare API token template \"WordPress\"." msgstr "" #: tpl/cdn/cf.tpl.php:44 msgid "Email Address" msgstr "" #: tpl/cdn/cf.tpl.php:47 msgid "Your Email address on %s." msgstr "" #: tpl/cdn/cf.tpl.php:48 msgid "Optional when API token used." msgstr "" #: tpl/cdn/cf.tpl.php:52 msgid "Domain" msgstr "" #: tpl/cdn/cf.tpl.php:59 msgid "You can just type part of the domain." msgstr "" #: tpl/cdn/cf.tpl.php:60 msgid "Once saved, it will be matched with the current list and completed automatically." msgstr "" #: tpl/cdn/cf.tpl.php:74 msgid "Clear %s cache when \"Purge All\" is run." msgstr "" #: tpl/cdn/cf.tpl.php:102 msgid "To enable the following functionality, turn ON Cloudflare API in CDN Settings." msgstr "" #: tpl/cdn/cf.tpl.php:107 msgid "Cloudflare Domain" msgstr "" #: tpl/cdn/cf.tpl.php:108 msgid "Cloudflare Zone" msgstr "" #: tpl/cdn/cf.tpl.php:111 msgid "Development Mode" msgstr "" #: tpl/cdn/cf.tpl.php:113 msgid "Turn ON" msgstr "" #: tpl/cdn/cf.tpl.php:116 msgid "Turn OFF" msgstr "" #: tpl/cdn/cf.tpl.php:119 msgid "Check Status" msgstr "" #: tpl/cdn/cf.tpl.php:129 msgid "Current status is %1$s since %2$s." msgstr "" #: tpl/cdn/cf.tpl.php:137 msgid "Current status is %s." msgstr "" #: tpl/cdn/cf.tpl.php:141 msgid "Development mode will be automatically turned off in %s." msgstr "" #: tpl/cdn/cf.tpl.php:149 msgid "Temporarily bypass Cloudflare cache. This allows changes to the origin server to be seen in realtime." msgstr "" #: tpl/cdn/cf.tpl.php:151 msgid "Development Mode will be turned off automatically after three hours." msgstr "" #: tpl/cdn/cf.tpl.php:152 msgid "%1$sLearn More%2$s" msgstr "" #: tpl/cdn/cf.tpl.php:156 msgid "Cloudflare Cache" msgstr "" #: tpl/cdn/cf.tpl.php:162 msgid "Purge Everything" msgstr "" #: tpl/cdn/entry.tpl.php:14 msgid "QUIC.cloud" msgstr "" #: tpl/cdn/entry.tpl.php:16 msgid "Other Static CDN" msgstr "" #: tpl/cdn/entry.tpl.php:22 msgid "LiteSpeed Cache CDN" msgstr "" #: tpl/cdn/other.tpl.php:28 msgid "CDN Settings" msgstr "" #: tpl/cdn/other.tpl.php:44 msgid "Turn this setting %s if you are using a traditional Content Delivery Network (CDN) or a subdomain for static content with QUIC.cloud CDN." msgstr "" #: tpl/cdn/other.tpl.php:52 msgid "NOTE: QUIC.cloud CDN and Cloudflare do not use CDN Mapping. If you are only using QUIC.cloud or Cloudflare, leave this setting %s." msgstr "" #: tpl/cdn/other.tpl.php:80 msgid "To randomize CDN hostname, define multiple hostnames for the same resources." msgstr "" #: tpl/cdn/other.tpl.php:87 msgid "Serve all image files through the CDN. This will affect all attachments, HTML %1$s tags, and CSS %2$s attributes." msgstr "" #: tpl/cdn/other.tpl.php:94 msgid "Serve all CSS files through the CDN. This will affect all enqueued WP CSS files." msgstr "" #: tpl/cdn/other.tpl.php:97 msgid "Serve all JavaScript files through the CDN. This will affect all enqueued WP JavaScript files." msgstr "" #: tpl/cdn/other.tpl.php:100 msgid "Static file type links to be replaced by CDN links." msgstr "" #: tpl/cdn/other.tpl.php:104 msgid "This will affect all tags containing attributes: %s." msgstr "" #: tpl/cdn/other.tpl.php:112 msgid "If you turn any of the above settings OFF, please remove the related file types from the %s box." msgstr "" #: tpl/cdn/other.tpl.php:136 msgid "Specify which HTML element attributes will be replaced with CDN Mapping." msgstr "" #: tpl/cdn/other.tpl.php:137 #: tpl/img_optm/settings.tpl.php:150 msgid "Only attributes listed here will be replaced." msgstr "" #: tpl/cdn/other.tpl.php:141 #: tpl/img_optm/settings.tpl.php:151 msgid "Use the format %1$s or %2$s (element is optional)." msgstr "" #: tpl/cdn/other.tpl.php:161 msgid "Site URL to be served through the CDN. Beginning with %1$s. For example, %2$s." msgstr "" #: tpl/cdn/other.tpl.php:196 msgid "Only files within these directories will be pointed to the CDN." msgstr "" #: tpl/cdn/other.tpl.php:210 msgid "Paths containing these strings will not be served from the CDN." msgstr "" #: tpl/cdn/qc.tpl.php:24 #: tpl/dash/dashboard.tpl.php:886 msgid "Refresh Status" msgstr "" #: tpl/cdn/qc.tpl.php:27 msgid "QUIC.cloud CDN Status Overview" msgstr "" #: tpl/cdn/qc.tpl.php:29 msgid "Check the status of your most important settings and the health of your CDN setup here." msgstr "" #: tpl/cdn/qc.tpl.php:34 #: tpl/dash/dashboard.tpl.php:146 msgid "Accelerate, Optimize, Protect" msgstr "" #: tpl/cdn/qc.tpl.php:36 #: tpl/dash/dashboard.tpl.php:150 msgid "Speed up your WordPress site even further with QUIC.cloud Online Services and CDN." msgstr "" #: tpl/cdn/qc.tpl.php:38 #: tpl/general/online.tpl.php:61 #: tpl/general/online.tpl.php:145 msgid "Free monthly quota available." msgstr "" #: tpl/cdn/qc.tpl.php:41 #: tpl/dash/dashboard.tpl.php:158 #: tpl/general/online.tpl.php:64 #: tpl/general/online.tpl.php:119 msgid "Enable QUIC.cloud services" msgstr "" #: tpl/cdn/qc.tpl.php:45 #: tpl/dash/dashboard.tpl.php:167 #: tpl/general/online.tpl.php:26 msgid "QUIC.cloud provides CDN and online optimization services, and is not required. You may use many features of this plugin without QUIC.cloud." msgstr "" #: tpl/cdn/qc.tpl.php:46 #: tpl/dash/dashboard.tpl.php:169 msgid "Learn More about QUIC.cloud" msgstr "" #: tpl/cdn/qc.tpl.php:53 msgid "QUIC.cloud CDN is currently fully disabled." msgstr "" #: tpl/cdn/qc.tpl.php:55 msgid "QUIC.cloud CDN is not available for anonymous (unlinked) users." msgstr "" #: tpl/cdn/qc.tpl.php:59 msgid "Link & Enable QUIC.cloud CDN" msgstr "" #: tpl/cdn/qc.tpl.php:61 #: tpl/dash/dashboard.tpl.php:857 msgid "Enable QUIC.cloud CDN" msgstr "" #: tpl/cdn/qc.tpl.php:71 msgid "Content Delivery Network Service" msgstr "" #: tpl/cdn/qc.tpl.php:73 msgid "Serve your visitors fast" msgstr "" #: tpl/cdn/qc.tpl.php:73 msgid "no matter where they live." msgstr "" #. translators: %s: Link tags #: tpl/cdn/qc.tpl.php:79 msgid "Best available WordPress performance, globally fast TTFB, easy setup, and %smore%s!" msgstr "" #: tpl/cdn/qc.tpl.php:96 msgid "QUIC.cloud CDN Options" msgstr "" #: tpl/cdn/qc.tpl.php:117 msgid "To manage your QUIC.cloud options, go to your hosting provider's portal." msgstr "" #: tpl/cdn/qc.tpl.php:119 msgid "To manage your QUIC.cloud options, please contact your hosting provider." msgstr "" #: tpl/cdn/qc.tpl.php:123 #: tpl/cdn/qc.tpl.php:143 msgid "To manage your QUIC.cloud options, go to QUIC.cloud Dashboard." msgstr "" #: tpl/cdn/qc.tpl.php:126 #: tpl/cdn/qc.tpl.php:133 #: tpl/dash/dashboard.tpl.php:360 #: tpl/general/online.tpl.php:153 msgid "Link to QUIC.cloud" msgstr "" #: tpl/cdn/qc.tpl.php:130 msgid "You are currently using services as an anonymous user. To manage your QUIC.cloud options, use the button below to create an account and link to the QUIC.cloud Dashboard." msgstr "" #: tpl/cdn/qc.tpl.php:139 #: tpl/cdn/qc.tpl.php:146 msgid "My QUIC.cloud Dashboard" msgstr "" #: tpl/crawler/blacklist.tpl.php:22 msgid "Are you sure to delete all existing blocklist items?" msgstr "" #: tpl/crawler/blacklist.tpl.php:23 msgid "Empty blocklist" msgstr "" #: tpl/crawler/blacklist.tpl.php:28 #: tpl/crawler/entry.tpl.php:16 msgid "Blocklist" msgstr "" #: tpl/crawler/blacklist.tpl.php:32 #: tpl/img_optm/summary.tpl.php:201 msgid "Total" msgstr "" #: tpl/crawler/blacklist.tpl.php:41 #: tpl/crawler/map.tpl.php:76 #: tpl/toolbox/purge.tpl.php:209 msgid "URL" msgstr "" #: tpl/crawler/blacklist.tpl.php:43 #: tpl/crawler/map.tpl.php:78 msgid "Operation" msgstr "" #: tpl/crawler/blacklist.tpl.php:54 msgid "Remove from Blocklist" msgstr "" #: tpl/crawler/blacklist.tpl.php:69 msgid "API: PHP Constant %s available to disable blocklist." msgstr "" #: tpl/crawler/blacklist.tpl.php:79 msgid "API: Filter %s available to disable blocklist." msgstr "" #: tpl/crawler/blacklist.tpl.php:87 msgid "Not blocklisted" msgstr "" #: tpl/crawler/blacklist.tpl.php:88 #: tpl/crawler/map.tpl.php:103 msgid "Blocklisted due to not cacheable" msgstr "" #: tpl/crawler/blacklist.tpl.php:89 #: tpl/crawler/map.tpl.php:64 #: tpl/crawler/map.tpl.php:104 #: tpl/crawler/summary.tpl.php:199 #: tpl/crawler/summary.tpl.php:247 msgid "Blocklisted" msgstr "" #: tpl/crawler/entry.tpl.php:14 msgid "Summary" msgstr "" #: tpl/crawler/entry.tpl.php:15 msgid "Map" msgstr "" #: tpl/crawler/entry.tpl.php:23 msgid "LiteSpeed Cache Crawler" msgstr "" #: tpl/crawler/map.tpl.php:29 msgid "Clean Crawler Map" msgstr "" #: tpl/crawler/map.tpl.php:32 msgid "Refresh Crawler Map" msgstr "" #: tpl/crawler/map.tpl.php:40 msgid "Generated at %s" msgstr "" #: tpl/crawler/map.tpl.php:48 msgid "Sitemap List" msgstr "" #: tpl/crawler/map.tpl.php:52 msgid "Sitemap Total" msgstr "" #: tpl/crawler/map.tpl.php:58 msgid "URL Search" msgstr "" #: tpl/crawler/map.tpl.php:62 #: tpl/crawler/map.tpl.php:101 msgid "Cache Hit" msgstr "" #: tpl/crawler/map.tpl.php:63 #: tpl/crawler/map.tpl.php:102 msgid "Cache Miss" msgstr "" #: tpl/crawler/map.tpl.php:77 #: tpl/dash/dashboard.tpl.php:80 #: tpl/dash/dashboard.tpl.php:800 msgid "Crawler Status" msgstr "" #: tpl/crawler/map.tpl.php:89 msgid "Add to Blocklist" msgstr "" #: tpl/crawler/settings.tpl.php:17 msgid "Crawler General Settings" msgstr "" #: tpl/crawler/settings.tpl.php:31 msgid "This will enable crawler cron." msgstr "" #: tpl/crawler/settings.tpl.php:45 msgid "Specify how long in seconds before the crawler should initiate crawling the entire sitemap again." msgstr "" #: tpl/crawler/settings.tpl.php:59 msgid "The crawler will use your XML sitemap or sitemap index. Enter the full URL to your sitemap here." msgstr "" #: tpl/crawler/settings.tpl.php:73 msgid "The maximum average server load allowed while crawling. The number of crawler threads in use will be actively reduced until average server load falls under this limit. If this cannot be achieved with a single thread, the current crawler run will be terminated." msgstr "" #: tpl/crawler/settings.tpl.php:79 msgid "Server enforced value: %s" msgstr "" #: tpl/crawler/settings.tpl.php:89 msgid "Server allowed max value: %s" msgstr "" #: tpl/crawler/settings.tpl.php:109 msgid "To crawl the site as a logged-in user, enter the user ids to be simulated." msgstr "" #: tpl/crawler/settings.tpl.php:116 #: tpl/crawler/summary.tpl.php:211 msgid "You must set %s before using this feature." msgstr "" #: tpl/crawler/settings.tpl.php:136 msgid "You must set %1$s to %2$s before using this feature." msgstr "" #: tpl/crawler/settings.tpl.php:172 msgid "To crawl for a particular cookie, enter the cookie name, and the values you wish to crawl for. Values should be one per line. There will be one crawler created per cookie value, per simulated role." msgstr "" #: tpl/crawler/settings.tpl.php:177 msgid "Use %1$s in %2$s to indicate this cookie has not been set." msgstr "" #: tpl/crawler/summary.tpl.php:28 msgid "You need to set the %s in Settings first before using the crawler" msgstr "" #: tpl/crawler/summary.tpl.php:54 msgid "Crawler Cron" msgstr "" #: tpl/crawler/summary.tpl.php:61 msgid "The crawler feature is not enabled on the LiteSpeed server. Please consult your server admin or hosting provider." msgstr "" #. translators: %s: Link tags #: tpl/crawler/summary.tpl.php:66 msgid "See %sIntroduction for Enabling the Crawler%s for detailed information." msgstr "" #: tpl/crawler/summary.tpl.php:77 msgid "Current sitemap crawl started at" msgstr "" #: tpl/crawler/summary.tpl.php:82 msgid "The next complete sitemap crawl will start at" msgstr "" #: tpl/crawler/summary.tpl.php:90 msgid "Last complete run time for all crawlers" msgstr "" #: tpl/crawler/summary.tpl.php:91 #: tpl/crawler/summary.tpl.php:98 msgid "%d seconds" msgstr "" #: tpl/crawler/summary.tpl.php:97 msgid "Run time for previous crawler" msgstr "" #: tpl/crawler/summary.tpl.php:104 #: tpl/dash/dashboard.tpl.php:91 #: tpl/dash/dashboard.tpl.php:811 msgid "Current crawler started at" msgstr "" #: tpl/crawler/summary.tpl.php:110 msgid "Current server load" msgstr "" #: tpl/crawler/summary.tpl.php:116 #: tpl/dash/dashboard.tpl.php:97 #: tpl/dash/dashboard.tpl.php:817 msgid "Last interval" msgstr "" #: tpl/crawler/summary.tpl.php:123 #: tpl/dash/dashboard.tpl.php:103 #: tpl/dash/dashboard.tpl.php:823 msgid "Ended reason" msgstr "" #: tpl/crawler/summary.tpl.php:130 msgid "Last crawled" msgstr "" #: tpl/crawler/summary.tpl.php:133 msgid "%d item(s)" msgstr "" #: tpl/crawler/summary.tpl.php:141 msgid "Reset position" msgstr "" #: tpl/crawler/summary.tpl.php:142 msgid "Manually run" msgstr "" #: tpl/crawler/summary.tpl.php:151 msgid "Cron Name" msgstr "" #: tpl/crawler/summary.tpl.php:152 msgid "Run Frequency" msgstr "" #: tpl/crawler/summary.tpl.php:154 msgid "Activate" msgstr "" #: tpl/crawler/summary.tpl.php:155 msgid "Running" msgstr "" #: tpl/crawler/summary.tpl.php:184 msgid "Waiting" msgstr "" #: tpl/crawler/summary.tpl.php:189 msgid "Hit" msgstr "" #: tpl/crawler/summary.tpl.php:194 msgid "Miss" msgstr "" #: tpl/crawler/summary.tpl.php:230 msgid "Position: " msgstr "" #: tpl/crawler/summary.tpl.php:232 msgid "running" msgstr "" #: tpl/crawler/summary.tpl.php:244 msgid "Waiting to be Crawled" msgstr "" #: tpl/crawler/summary.tpl.php:245 msgid "Already Cached" msgstr "" #: tpl/crawler/summary.tpl.php:246 msgid "Successfully Crawled" msgstr "" #: tpl/crawler/summary.tpl.php:251 msgid "Run frequency is set by the Interval Between Runs setting." msgstr "" #: tpl/crawler/summary.tpl.php:254 msgid "Crawlers cannot run concurrently. If both the cron and a manual run start at similar times, the first to be started will take precedence." msgstr "" #. translators: %s: Link tags #: tpl/crawler/summary.tpl.php:261 msgid "Please see %sHooking WP-Cron Into the System Task Scheduler%s to learn how to create the system cron task." msgstr "" #: tpl/crawler/summary.tpl.php:272 msgid "Watch Crawler Status" msgstr "" #: tpl/crawler/summary.tpl.php:278 msgid "Show crawler status" msgstr "" #: tpl/crawler/summary.tpl.php:288 msgid "Start watching..." msgstr "" #: tpl/crawler/summary.tpl.php:293 msgid "No crawler meta file generated yet" msgstr "" #: tpl/dash/dashboard.tpl.php:53 #: tpl/dash/dashboard.tpl.php:597 msgid "Cache Status" msgstr "" #: tpl/dash/dashboard.tpl.php:54 #: tpl/dash/dashboard.tpl.php:81 #: tpl/dash/dashboard.tpl.php:521 #: tpl/dash/dashboard.tpl.php:598 #: tpl/dash/dashboard.tpl.php:625 #: tpl/dash/dashboard.tpl.php:669 #: tpl/dash/dashboard.tpl.php:713 #: tpl/dash/dashboard.tpl.php:757 #: tpl/dash/dashboard.tpl.php:801 #: tpl/dash/dashboard.tpl.php:848 msgid "More" msgstr "" #: tpl/dash/dashboard.tpl.php:58 #: tpl/dash/dashboard.tpl.php:602 msgid "Public Cache" msgstr "" #: tpl/dash/dashboard.tpl.php:59 #: tpl/dash/dashboard.tpl.php:603 msgid "Private Cache" msgstr "" #: tpl/dash/dashboard.tpl.php:84 #: tpl/dash/dashboard.tpl.php:804 msgid "Crawler(s)" msgstr "" #: tpl/dash/dashboard.tpl.php:87 #: tpl/dash/dashboard.tpl.php:807 msgid "Currently active crawler" msgstr "" #: tpl/dash/dashboard.tpl.php:111 #: tpl/dash/dashboard.tpl.php:831 msgid "%1$s %2$d item(s)" msgstr "" #: tpl/dash/dashboard.tpl.php:112 #: tpl/dash/dashboard.tpl.php:832 msgid "Last crawled:" msgstr "" #: tpl/dash/dashboard.tpl.php:128 #: tpl/dash/dashboard.tpl.php:908 msgid "News" msgstr "" #: tpl/dash/dashboard.tpl.php:153 msgid "Free monthly quota available. Can also be used anonymously (no email required)." msgstr "" #: tpl/dash/dashboard.tpl.php:163 msgid "Do not show this again" msgstr "" #: tpl/dash/dashboard.tpl.php:180 msgid "QUIC.cloud Service Usage Statistics" msgstr "" #: tpl/dash/dashboard.tpl.php:182 msgid "Refresh Usage" msgstr "" #: tpl/dash/dashboard.tpl.php:183 msgid "Sync data from Cloud" msgstr "" #: tpl/dash/dashboard.tpl.php:194 msgid "The features below are provided by %s" msgstr "" #: tpl/dash/dashboard.tpl.php:206 #: tpl/dash/network_dash.tpl.php:38 msgid "CDN Bandwidth" msgstr "" #: tpl/dash/dashboard.tpl.php:207 #: tpl/dash/dashboard.tpl.php:712 #: tpl/dash/network_dash.tpl.php:39 msgid "Low Quality Image Placeholder" msgstr "" #: tpl/dash/dashboard.tpl.php:259 #: tpl/dash/network_dash.tpl.php:95 msgid "Fast Queue Usage" msgstr "" #: tpl/dash/dashboard.tpl.php:259 #: tpl/dash/network_dash.tpl.php:95 msgid "Usage" msgstr "" #: tpl/dash/dashboard.tpl.php:271 #: tpl/dash/network_dash.tpl.php:108 msgid "PAYG Balance" msgstr "" #: tpl/dash/dashboard.tpl.php:272 msgid "PAYG used this month: %s. PAYG balance and usage not included in above quota calculation." msgstr "" #: tpl/dash/dashboard.tpl.php:274 #: tpl/dash/network_dash.tpl.php:111 msgid "Pay as You Go Usage Statistics" msgstr "" #: tpl/dash/dashboard.tpl.php:292 #: tpl/dash/network_dash.tpl.php:118 msgid "Total Usage" msgstr "" #: tpl/dash/dashboard.tpl.php:293 #: tpl/dash/network_dash.tpl.php:119 msgid "Total images optimized in this month" msgstr "" #: tpl/dash/dashboard.tpl.php:301 msgid "Remaining Daily Quota" msgstr "" #: tpl/dash/dashboard.tpl.php:311 msgid "Partner Benefits Provided by" msgstr "" #: tpl/dash/dashboard.tpl.php:347 msgid "Enable QUIC.cloud Services" msgstr "" #: tpl/dash/dashboard.tpl.php:354 #: tpl/general/online.tpl.php:128 msgid "Go to QUIC.cloud dashboard" msgstr "" #: tpl/dash/dashboard.tpl.php:382 #: tpl/img_optm/summary.tpl.php:54 #: tpl/page_optm/settings_css.tpl.php:111 #: tpl/page_optm/settings_css.tpl.php:248 #: tpl/page_optm/settings_media.tpl.php:194 #: tpl/page_optm/settings_vpi.tpl.php:59 msgid "Current closest Cloud server is %s. Click to redetect." msgstr "" #: tpl/dash/dashboard.tpl.php:383 #: tpl/img_optm/summary.tpl.php:54 #: tpl/page_optm/settings_css.tpl.php:111 #: tpl/page_optm/settings_css.tpl.php:248 #: tpl/page_optm/settings_media.tpl.php:194 #: tpl/page_optm/settings_vpi.tpl.php:59 msgid "Are you sure you want to redetect the closest cloud server for this service?" msgstr "" #: tpl/dash/dashboard.tpl.php:385 #: tpl/general/online.tpl.php:31 #: tpl/img_optm/summary.tpl.php:54 #: tpl/img_optm/summary.tpl.php:56 #: tpl/page_optm/settings_css.tpl.php:111 #: tpl/page_optm/settings_css.tpl.php:248 #: tpl/page_optm/settings_media.tpl.php:194 #: tpl/page_optm/settings_vpi.tpl.php:59 msgid "Redetect" msgstr "" #: tpl/dash/dashboard.tpl.php:419 msgid "You must be using one of the following products in order to measure Page Load Time:" msgstr "" #: tpl/dash/dashboard.tpl.php:420 msgid "LiteSpeed Web Server" msgstr "" #: tpl/dash/dashboard.tpl.php:422 msgid "OpenLiteSpeed Web Server" msgstr "" #: tpl/dash/dashboard.tpl.php:424 msgid "LiteSpeed Web ADC" msgstr "" #: tpl/dash/dashboard.tpl.php:426 #: tpl/dash/dashboard.tpl.php:844 msgid "QUIC.cloud CDN" msgstr "" #: tpl/dash/dashboard.tpl.php:438 #: tpl/dash/dashboard.tpl.php:503 msgid "Requested: %s ago" msgstr "" #: tpl/dash/dashboard.tpl.php:446 #: tpl/dash/dashboard.tpl.php:511 msgid "Refresh" msgstr "" #: tpl/dash/dashboard.tpl.php:447 msgid "Refresh page load time" msgstr "" #: tpl/dash/dashboard.tpl.php:512 msgid "Refresh page score" msgstr "" #: tpl/dash/dashboard.tpl.php:520 #: tpl/img_optm/entry.tpl.php:16 msgid "Image Optimization Summary" msgstr "" #: tpl/dash/dashboard.tpl.php:537 #: tpl/img_optm/summary.tpl.php:76 #: tpl/img_optm/summary.tpl.php:89 msgid "Send Optimization Request" msgstr "" #: tpl/dash/dashboard.tpl.php:543 #: tpl/img_optm/summary.tpl.php:316 msgid "Total Reduction" msgstr "" #: tpl/dash/dashboard.tpl.php:546 #: tpl/img_optm/summary.tpl.php:319 msgid "Images Pulled" msgstr "" #: tpl/dash/dashboard.tpl.php:569 #: tpl/img_optm/summary.tpl.php:322 msgid "Last Request" msgstr "" #: tpl/dash/dashboard.tpl.php:572 msgid "Last Pull" msgstr "" #: tpl/dash/dashboard.tpl.php:624 #: tpl/toolbox/purge.tpl.php:73 msgid "Critical CSS" msgstr "" #: tpl/dash/dashboard.tpl.php:631 #: tpl/dash/dashboard.tpl.php:675 #: tpl/dash/dashboard.tpl.php:719 #: tpl/dash/dashboard.tpl.php:763 msgid "Last generated: %s" msgstr "" #: tpl/dash/dashboard.tpl.php:639 #: tpl/dash/dashboard.tpl.php:683 #: tpl/dash/dashboard.tpl.php:727 #: tpl/dash/dashboard.tpl.php:771 msgid "Time to execute previous request: %s" msgstr "" #: tpl/dash/dashboard.tpl.php:646 #: tpl/dash/dashboard.tpl.php:690 #: tpl/dash/dashboard.tpl.php:734 #: tpl/dash/dashboard.tpl.php:778 msgid "Requests in queue" msgstr "" #: tpl/dash/dashboard.tpl.php:649 #: tpl/dash/dashboard.tpl.php:693 #: tpl/dash/dashboard.tpl.php:737 #: tpl/dash/dashboard.tpl.php:781 msgid "Force cron" msgstr "" #: tpl/dash/dashboard.tpl.php:657 #: tpl/dash/dashboard.tpl.php:701 #: tpl/dash/dashboard.tpl.php:745 #: tpl/dash/dashboard.tpl.php:789 msgid "Last requested: %s" msgstr "" #: tpl/dash/dashboard.tpl.php:668 #: tpl/toolbox/purge.tpl.php:82 msgid "Unique CSS" msgstr "" #: tpl/dash/dashboard.tpl.php:756 msgid "Viewport Image" msgstr "" #: tpl/dash/dashboard.tpl.php:864 msgid "Best available WordPress performance" msgstr "" #: tpl/dash/dashboard.tpl.php:869 msgid "Globally fast TTFB, easy setup, and %s!" msgstr "" #: tpl/dash/dashboard.tpl.php:870 msgid "more" msgstr "" #: tpl/dash/dashboard.tpl.php:887 msgid "Refresh QUIC.cloud status" msgstr "" #: tpl/dash/entry.tpl.php:21 msgid "Network Dashboard" msgstr "" #: tpl/dash/entry.tpl.php:29 msgid "LiteSpeed Cache Dashboard" msgstr "" #: tpl/dash/network_dash.tpl.php:28 msgid "Usage Statistics: %s" msgstr "" #: tpl/dash/network_dash.tpl.php:107 msgid "Pay as You Go" msgstr "" #: tpl/dash/network_dash.tpl.php:109 msgid "This Month Usage: %s" msgstr "" #: tpl/db_optm/entry.tpl.php:17 #: tpl/db_optm/settings.tpl.php:19 msgid "DB Optimization Settings" msgstr "" #: tpl/db_optm/entry.tpl.php:24 msgid "LiteSpeed Cache Database Optimization" msgstr "" #: tpl/db_optm/manage.tpl.php:17 msgid "Clean All" msgstr "" #: tpl/db_optm/manage.tpl.php:21 msgid "Post Revisions" msgstr "" #: tpl/db_optm/manage.tpl.php:22 msgid "Clean all post revisions" msgstr "" #: tpl/db_optm/manage.tpl.php:25 msgid "Orphaned Post Meta" msgstr "" #: tpl/db_optm/manage.tpl.php:26 msgid "Clean all orphaned post meta records" msgstr "" #: tpl/db_optm/manage.tpl.php:29 msgid "Auto Drafts" msgstr "" #: tpl/db_optm/manage.tpl.php:30 msgid "Clean all auto saved drafts" msgstr "" #: tpl/db_optm/manage.tpl.php:33 msgid "Trashed Posts" msgstr "" #: tpl/db_optm/manage.tpl.php:34 msgid "Clean all trashed posts and pages" msgstr "" #: tpl/db_optm/manage.tpl.php:37 msgid "Spam Comments" msgstr "" #: tpl/db_optm/manage.tpl.php:38 msgid "Clean all spam comments" msgstr "" #: tpl/db_optm/manage.tpl.php:41 msgid "Trashed Comments" msgstr "" #: tpl/db_optm/manage.tpl.php:42 msgid "Clean all trashed comments" msgstr "" #: tpl/db_optm/manage.tpl.php:45 msgid "Trackbacks/Pingbacks" msgstr "" #: tpl/db_optm/manage.tpl.php:46 msgid "Clean all trackbacks and pingbacks" msgstr "" #: tpl/db_optm/manage.tpl.php:49 msgid "Expired Transients" msgstr "" #: tpl/db_optm/manage.tpl.php:50 msgid "Clean expired transient options" msgstr "" #: tpl/db_optm/manage.tpl.php:53 msgid "All Transients" msgstr "" #: tpl/db_optm/manage.tpl.php:54 msgid "Clean all transient options" msgstr "" #: tpl/db_optm/manage.tpl.php:57 msgid "Optimize Tables" msgstr "" #: tpl/db_optm/manage.tpl.php:58 msgid "Optimize all tables in your database" msgstr "" #: tpl/db_optm/manage.tpl.php:66 msgid "Clean revisions older than %1$s day(s), excluding %2$s latest revisions" msgstr "" #: tpl/db_optm/manage.tpl.php:90 msgid "Database Optimizer" msgstr "" #: tpl/db_optm/manage.tpl.php:116 msgid "Database Table Engine Converter" msgstr "" #: tpl/db_optm/manage.tpl.php:124 msgid "Table" msgstr "" #: tpl/db_optm/manage.tpl.php:125 msgid "Engine" msgstr "" #: tpl/db_optm/manage.tpl.php:126 msgid "Tool" msgstr "" #: tpl/db_optm/manage.tpl.php:141 msgid "Convert to InnoDB" msgstr "" #: tpl/db_optm/manage.tpl.php:149 msgid "We are good. No table uses MyISAM engine." msgstr "" #: tpl/db_optm/manage.tpl.php:171 msgid "Database Summary" msgstr "" #: tpl/db_optm/manage.tpl.php:175 msgid "Autoload size" msgstr "" #: tpl/db_optm/manage.tpl.php:176 msgid "Autoload entries" msgstr "" #: tpl/db_optm/manage.tpl.php:180 msgid "Autoload top list" msgstr "" #: tpl/db_optm/manage.tpl.php:185 msgid "Option Name" msgstr "" #: tpl/db_optm/manage.tpl.php:186 msgid "Autoload" msgstr "" #: tpl/db_optm/manage.tpl.php:187 msgid "Size" msgstr "" #: tpl/db_optm/settings.tpl.php:32 msgid "Specify the number of most recent revisions to keep when cleaning revisions." msgstr "" #: tpl/db_optm/settings.tpl.php:44 msgid "Day(s)" msgstr "" #: tpl/db_optm/settings.tpl.php:46 msgid "Revisions newer than this many days will be kept when cleaning revisions." msgstr "" #: tpl/esi_widget_edit.php:52 msgid "Public" msgstr "" #: tpl/esi_widget_edit.php:53 msgid "Private" msgstr "" #: tpl/esi_widget_edit.php:54 msgid "Disable" msgstr "" #: tpl/esi_widget_edit.php:71 msgid "Widget Cache TTL" msgstr "" #: tpl/esi_widget_edit.php:81 msgid "Recommended value: 28800 seconds (8 hours)." msgstr "" #: tpl/esi_widget_edit.php:82 msgid "A TTL of 0 indicates do not cache." msgstr "" #: tpl/general/entry.tpl.php:16 #: tpl/general/online.tpl.php:68 msgid "Online Services" msgstr "" #: tpl/general/entry.tpl.php:17 #: tpl/general/entry.tpl.php:22 #: tpl/general/network_settings.tpl.php:19 #: tpl/general/settings.tpl.php:24 msgid "General Settings" msgstr "" #: tpl/general/entry.tpl.php:30 msgid "LiteSpeed Cache General Settings" msgstr "" #: tpl/general/network_settings.tpl.php:31 msgid "Use Primary Site Configuration" msgstr "" #: tpl/general/network_settings.tpl.php:35 msgid "Check this option to use the primary site's configuration for all subsites." msgstr "" #: tpl/general/network_settings.tpl.php:36 msgid "This will disable the settings page on all subsites." msgstr "" #: tpl/general/online.tpl.php:22 msgid "QUIC.cloud Online Services" msgstr "" #: tpl/general/online.tpl.php:30 msgid "Current Cloud Nodes in Service" msgstr "" #: tpl/general/online.tpl.php:31 msgid "Click to clear all nodes for further redetection." msgstr "" #: tpl/general/online.tpl.php:31 msgid "Are you sure you want to clear all cloud nodes?" msgstr "" #: tpl/general/online.tpl.php:41 msgid "Service:" msgstr "" #: tpl/general/online.tpl.php:43 msgid "Node:" msgstr "" #: tpl/general/online.tpl.php:45 msgid "Connected Date:" msgstr "" #: tpl/general/online.tpl.php:51 msgid "No cloud services currently in use" msgstr "" #: tpl/general/online.tpl.php:59 msgid "QUIC.cloud Integration Disabled" msgstr "" #: tpl/general/online.tpl.php:60 msgid "Speed up your WordPress site even further with QUIC.cloud Online Services and CDN." msgstr "" #: tpl/general/online.tpl.php:69 msgid "QUIC.cloud's Online Services improve your site in the following ways:" msgstr "" #: tpl/general/online.tpl.php:71 msgid "Image Optimization gives you smaller image file sizes that transmit faster." msgstr "" #: tpl/general/online.tpl.php:72 msgid "Page Optimization streamlines page styles and visual elements for faster loading." msgstr "" #: tpl/general/online.tpl.php:76 msgid "QUIC.cloud's Image Optimization service does the following:" msgstr "" #: tpl/general/online.tpl.php:78 msgid "Processes your uploaded PNG and JPG images to produce smaller versions that don't sacrifice quality." msgstr "" #: tpl/general/online.tpl.php:79 msgid "Optionally creates next-generation WebP or AVIF image files." msgstr "" #: tpl/general/online.tpl.php:81 msgid "Processing for PNG, JPG, and WebP image formats is free. AVIF is available for a fee." msgstr "" #: tpl/general/online.tpl.php:84 msgid "QUIC.cloud's Page Optimization services address CSS bloat, and improve the user experience during page load, which can lead to improved page speed scores." msgstr "" #: tpl/general/online.tpl.php:86 msgid "Critical CSS (CCSS) loads visible above-the-fold content faster and with full styling." msgstr "" #: tpl/general/online.tpl.php:87 msgid "Unique CSS (UCSS) removes unused style definitions for a speedier page load overall." msgstr "" #: tpl/general/online.tpl.php:88 msgid "Low Quality Image Placeholder (LQIP) gives your imagery a more pleasing look as it lazy loads." msgstr "" #: tpl/general/online.tpl.php:89 msgid "Viewport Images (VPI) provides a well-polished fully-loaded view above the fold." msgstr "" #: tpl/general/online.tpl.php:98 msgid "Content Delivery Network" msgstr "" #: tpl/general/online.tpl.php:100 msgid "QUIC.cloud CDN:" msgstr "" #: tpl/general/online.tpl.php:102 msgid "Caches your entire site, including dynamic content and ESI blocks." msgstr "" #: tpl/general/online.tpl.php:103 msgid "Delivers global coverage with a growing network of 80+ PoPs." msgstr "" #: tpl/general/online.tpl.php:104 msgid "Provides security at the CDN level, protecting your server from attack." msgstr "" #: tpl/general/online.tpl.php:105 msgid "Offers optional built-in DNS service to simplify CDN onboarding." msgstr "" #: tpl/general/online.tpl.php:114 msgid "In order to use most QUIC.cloud services, you need quota. QUIC.cloud gives you free quota every month, but if you need more, you can purchase it." msgstr "" #: tpl/general/online.tpl.php:125 msgid "QUIC.cloud Integration Enabled" msgstr "" #: tpl/general/online.tpl.php:126 msgid "Your site is connected and ready to use QUIC.cloud Online Services." msgstr "" #: tpl/general/online.tpl.php:136 msgid "CDN - Enabled" msgstr "" #: tpl/general/online.tpl.php:138 msgid "CDN - Disabled" msgstr "" #: tpl/general/online.tpl.php:143 msgid "QUIC.cloud Integration Enabled with limitations" msgstr "" #: tpl/general/online.tpl.php:144 msgid "Your site is connected and using QUIC.cloud Online Services as an anonymous user. The CDN function and certain features of optimization services are not available for anonymous users. Link to QUIC.cloud to use the CDN and all available Online Services features." msgstr "" #: tpl/general/online.tpl.php:150 msgid "CDN - not available for anonymous users" msgstr "" #: tpl/general/online.tpl.php:159 msgid "Are you sure you want to disconnect from QUIC.cloud? This will not remove any data from the QUIC.cloud dashboard." msgstr "" #: tpl/general/online.tpl.php:159 msgid "Disconnect from QUIC.cloud" msgstr "" #: tpl/general/online.tpl.php:160 msgid "Remove QUIC.cloud integration from this site. Note: QUIC.cloud data will be preserved so you can re-enable services at any time. If you want to fully remove your site from QUIC.cloud, delete the domain through the QUIC.cloud Dashboard first." msgstr "" #: tpl/general/settings.tpl.php:48 msgid "This option enables maximum optimization for Guest Mode visitors." msgstr "" #: tpl/general/settings.tpl.php:49 msgid "Please read all warnings before enabling this option." msgstr "" #: tpl/general/settings.tpl.php:64 msgid "Your %1$s quota on %2$s will still be in use." msgstr "" #: tpl/general/settings.tpl.php:72 #: tpl/general/settings.tpl.php:79 #: tpl/general/settings.tpl.php:86 #: tpl/general/settings.tpl.php:103 #: tpl/page_optm/settings_media.tpl.php:253 #: tpl/page_optm/settings_vpi.tpl.php:44 msgid "Notice" msgstr "" #: tpl/general/settings.tpl.php:72 #: tpl/page_optm/settings_media.tpl.php:253 #: tpl/page_optm/settings_vpi.tpl.php:44 msgid "%s must be turned ON for this setting to work." msgstr "" #: tpl/general/settings.tpl.php:79 msgid "You need to turn %s on to get maximum result." msgstr "" #: tpl/general/settings.tpl.php:86 msgid "You need to turn %s on and finish all WebP generation to get maximum result." msgstr "" #: tpl/general/settings.tpl.php:101 msgid "Enter this site's IP address to allow cloud services directly call IP instead of domain name. This eliminates the overhead of DNS and CDN lookups." msgstr "" #: tpl/general/settings.tpl.php:102 msgid "Your server IP" msgstr "" #: tpl/general/settings.tpl.php:102 msgid "Check my public IP from" msgstr "" #: tpl/general/settings.tpl.php:103 msgid "the auto-detected IP may not be accurate if you have an additional outgoing IP set, or you have multiple IPs configured on your server." msgstr "" #: tpl/general/settings.tpl.php:104 msgid "Please make sure this IP is the correct one for visiting your site." msgstr "" #: tpl/general/settings.tpl.php:119 msgid "Turn this option ON to show latest news automatically, including hotfixes, new releases, available beta versions, and promotions." msgstr "" #: tpl/general/settings_inc.auto_upgrade.tpl.php:25 msgid "Turn this option ON to have LiteSpeed Cache updated automatically, whenever a new version is released. If OFF, update manually as usual." msgstr "" #: tpl/general/settings_inc.guest.tpl.php:26 msgid "Guest Mode provides an always cacheable landing page for an automated guest's first time visit, and then attempts to update cache varies via AJAX." msgstr "" #: tpl/general/settings_inc.guest.tpl.php:27 msgid "This option can help to correct the cache vary for certain advanced mobile or tablet visitors." msgstr "" #: tpl/general/settings_inc.guest.tpl.php:34 msgid "Guest Mode testing result" msgstr "" #: tpl/general/settings_inc.guest.tpl.php:35 msgid "Testing" msgstr "" #: tpl/general/settings_inc.guest.tpl.php:38 msgid "Guest Mode IP/UA sync status" msgstr "" #: tpl/general/settings_inc.guest.tpl.php:39 msgid "Syncing" msgstr "" #: tpl/general/settings_inc.guest.tpl.php:46 msgid "Guest Mode passed testing." msgstr "" #: tpl/general/settings_inc.guest.tpl.php:49 #: tpl/general/settings_inc.guest.tpl.php:52 msgid "Guest Mode failed to test." msgstr "" #: tpl/general/settings_inc.guest.tpl.php:64 msgid "Synced successfully." msgstr "" #: tpl/general/settings_inc.guest.tpl.php:66 #: tpl/general/settings_inc.guest.tpl.php:70 msgid "Sync failed." msgstr "" #: tpl/img_optm/entry.tpl.php:17 #: tpl/img_optm/entry.tpl.php:22 #: tpl/img_optm/network_settings.tpl.php:19 #: tpl/img_optm/settings.tpl.php:19 msgid "Image Optimization Settings" msgstr "" #: tpl/img_optm/entry.tpl.php:30 msgid "LiteSpeed Cache Image Optimization" msgstr "" #: tpl/img_optm/settings.media_webp.tpl.php:25 msgid "Request WebP/AVIF versions of original images when doing optimization." msgstr "" #: tpl/img_optm/settings.media_webp.tpl.php:26 msgid "Significantly improve load time by replacing images with their optimized %s versions." msgstr "" #: tpl/img_optm/settings.media_webp.tpl.php:31 msgid "%1$s is a %2$s paid feature." msgstr "" #: tpl/img_optm/settings.media_webp.tpl.php:34 msgid "When switching formats, please %1$s or %2$s to apply this new choice to previously optimized images." msgstr "" #: tpl/img_optm/settings.media_webp.tpl.php:34 #: tpl/img_optm/summary.tpl.php:378 msgid "Destroy All Optimization Data" msgstr "" #: tpl/img_optm/settings.media_webp.tpl.php:34 #: tpl/img_optm/summary.tpl.php:368 msgid "Soft Reset Optimization Counter" msgstr "" #: tpl/img_optm/settings.tpl.php:34 msgid "Automatically request optimization via cron job." msgstr "" #: tpl/img_optm/settings.tpl.php:47 msgid "Optimize images and save backups of the originals in the same folder." msgstr "" #: tpl/img_optm/settings.tpl.php:60 msgid "Automatically remove the original image backups after fetching optimized images." msgstr "" #: tpl/img_optm/settings.tpl.php:65 #: tpl/img_optm/summary.tpl.php:244 #: tpl/page_optm/settings_media.tpl.php:308 msgid "This is irreversible." msgstr "" #: tpl/img_optm/settings.tpl.php:66 #: tpl/img_optm/summary.tpl.php:245 msgid "You will be unable to Revert Optimization once the backups are deleted!" msgstr "" #: tpl/img_optm/settings.tpl.php:80 msgid "Optimize images using lossless compression." msgstr "" #: tpl/img_optm/settings.tpl.php:81 msgid "This can improve quality but may result in larger images than lossy compression will." msgstr "" #: tpl/img_optm/settings.tpl.php:104 msgid "No sizes found." msgstr "" #: tpl/img_optm/settings.tpl.php:107 msgid "Choose which image sizes to optimize." msgstr "" #: tpl/img_optm/settings.tpl.php:120 msgid "Preserve EXIF data (copyright, GPS, comments, keywords, etc) when optimizing." msgstr "" #: tpl/img_optm/settings.tpl.php:121 msgid "This will increase the size of optimized files." msgstr "" #: tpl/img_optm/settings.tpl.php:149 msgid "Specify which element attributes will be replaced with WebP/AVIF." msgstr "" #: tpl/img_optm/settings.tpl.php:165 msgid "Enable replacement of WebP/AVIF in %s elements that were generated outside of WordPress logic." msgstr "" #: tpl/img_optm/summary.tpl.php:58 msgid "Optimize images with our QUIC.cloud server" msgstr "" #: tpl/img_optm/summary.tpl.php:63 msgid "You can request a maximum of %s images at once." msgstr "" #: tpl/img_optm/summary.tpl.php:68 msgid "To make sure our server can communicate with your server without any issues and everything works fine, for the few first requests the number of image groups allowed in a single request is limited." msgstr "" #: tpl/img_optm/summary.tpl.php:69 msgid "Current limit is" msgstr "" #: tpl/img_optm/summary.tpl.php:77 #: tpl/page_optm/settings_css.tpl.php:156 #: tpl/page_optm/settings_css.tpl.php:293 #: tpl/page_optm/settings_vpi.tpl.php:101 msgid "Available after %d second(s)" msgstr "" #: tpl/img_optm/summary.tpl.php:93 msgid "Only press the button if the pull cron job is disabled." msgstr "" #: tpl/img_optm/summary.tpl.php:93 msgid "Images will be pulled automatically if the cron job is running." msgstr "" #: tpl/img_optm/summary.tpl.php:102 msgid "Pull Images" msgstr "" #: tpl/img_optm/summary.tpl.php:108 msgid "Optimization Status" msgstr "" #: tpl/img_optm/summary.tpl.php:141 msgid "After the QUIC.cloud Image Optimization server finishes optimization, it will notify your site to pull the optimized images." msgstr "" #: tpl/img_optm/summary.tpl.php:142 msgid "This process is automatic." msgstr "" #: tpl/img_optm/summary.tpl.php:156 msgid "Last pull initiated by cron at %s." msgstr "" #: tpl/img_optm/summary.tpl.php:184 msgid "Storage Optimization" msgstr "" #: tpl/img_optm/summary.tpl.php:188 msgid "A backup of each image is saved before it is optimized." msgstr "" #: tpl/img_optm/summary.tpl.php:194 msgid "Last calculated" msgstr "" #: tpl/img_optm/summary.tpl.php:198 #: tpl/img_optm/summary.tpl.php:256 msgid "Files" msgstr "" #: tpl/img_optm/summary.tpl.php:208 msgid "Calculate Original Image Storage" msgstr "" #: tpl/img_optm/summary.tpl.php:217 msgid "Calculate Backups Disk Space" msgstr "" #: tpl/img_optm/summary.tpl.php:224 msgid "Image Thumbnail Group Sizes" msgstr "" #: tpl/img_optm/summary.tpl.php:241 msgid "Delete all backups of the original images" msgstr "" #: tpl/img_optm/summary.tpl.php:253 #: tpl/page_optm/settings_localization.tpl.php:70 msgid "Last ran" msgstr "" #: tpl/img_optm/summary.tpl.php:259 msgid "Saved" msgstr "" #: tpl/img_optm/summary.tpl.php:264 msgid "Are you sure you want to remove all image backups?" msgstr "" #: tpl/img_optm/summary.tpl.php:265 msgid "Remove Original Image Backups" msgstr "" #: tpl/img_optm/summary.tpl.php:276 msgid "Image Information" msgstr "" #: tpl/img_optm/summary.tpl.php:285 msgid "Image groups total" msgstr "" #: tpl/img_optm/summary.tpl.php:289 msgid "Congratulations, all gathered!" msgstr "" #: tpl/img_optm/summary.tpl.php:291 msgid "What is a group?" msgstr "" #: tpl/img_optm/summary.tpl.php:293 msgid "What is an image group?" msgstr "" #: tpl/img_optm/summary.tpl.php:297 #: tpl/img_optm/summary.tpl.php:372 msgid "Current image post id position" msgstr "" #: tpl/img_optm/summary.tpl.php:298 msgid "Maximum image post id" msgstr "" #: tpl/img_optm/summary.tpl.php:304 msgid "Scan for any new unoptimized image thumbnail sizes and resend necessary image optimization requests." msgstr "" #: tpl/img_optm/summary.tpl.php:305 msgid "Rescan New Thumbnails" msgstr "" #: tpl/img_optm/summary.tpl.php:313 msgid "Optimization Summary" msgstr "" #: tpl/img_optm/summary.tpl.php:325 msgid "Last Pulled" msgstr "" #. translators: %s: Link tags #: tpl/img_optm/summary.tpl.php:337 msgid "Results can be checked in %sMedia Library%s." msgstr "" #: tpl/img_optm/summary.tpl.php:347 msgid "Optimization Tools" msgstr "" #: tpl/img_optm/summary.tpl.php:350 msgid "You can quickly switch between using original (unoptimized versions) and optimized image files. It will affect all images on your website, both regular and webp versions if available." msgstr "" #: tpl/img_optm/summary.tpl.php:355 msgid "Use original images (unoptimized) on your site" msgstr "" #: tpl/img_optm/summary.tpl.php:356 msgid "Use Original Files" msgstr "" #: tpl/img_optm/summary.tpl.php:359 msgid "Switch back to using optimized images on your site" msgstr "" #: tpl/img_optm/summary.tpl.php:360 msgid "Use Optimized Files" msgstr "" #: tpl/img_optm/summary.tpl.php:372 msgid "This will reset the %1$s. If you changed WebP/AVIF settings and want to generate %2$s for the previously optimized images, use this action." msgstr "" #: tpl/img_optm/summary.tpl.php:377 msgid "Are you sure to destroy all optimized images?" msgstr "" #: tpl/img_optm/summary.tpl.php:382 msgid "Remove all previous image optimization requests/results, revert completed optimizations, and delete all optimization files." msgstr "" #: tpl/inc/admin_footer.php:17 msgid "Rate %1$s on %2$s" msgstr "" #: tpl/inc/admin_footer.php:19 msgid "Read LiteSpeed Documentation" msgstr "" #: tpl/inc/admin_footer.php:21 msgid "Visit LSCWP support forum" msgstr "" #: tpl/inc/admin_footer.php:23 msgid "Join LiteSpeed Slack community" msgstr "" #: tpl/inc/check_cache_disabled.php:20 msgid "To use the caching functions you must have a LiteSpeed web server or be using QUIC.cloud CDN." msgstr "" #: tpl/inc/check_cache_disabled.php:25 msgid "Please enable the LSCache Module at the server level, or ask your hosting provider." msgstr "" #: tpl/inc/check_cache_disabled.php:31 msgid "Please enable LiteSpeed Cache in the plugin settings." msgstr "" #: tpl/inc/check_cache_disabled.php:40 msgid "LSCache caching functions on this page are currently unavailable!" msgstr "" #: tpl/inc/check_if_network_disable_all.php:30 msgid "The network admin selected use primary site configs for all subsites." msgstr "" #: tpl/inc/check_if_network_disable_all.php:31 msgid "The following options are selected, but are not editable in this settings page." msgstr "" #: tpl/inc/in_upgrading.php:15 msgid "LiteSpeed cache plugin upgraded. Please refresh the page to complete the configuration data upgrade." msgstr "" #: tpl/inc/modal.deactivation.php:22 msgid "The deactivation is temporary" msgstr "" #: tpl/inc/modal.deactivation.php:28 msgid "Site performance is worse" msgstr "" #: tpl/inc/modal.deactivation.php:33 msgid "Plugin is too complicated" msgstr "" #: tpl/inc/modal.deactivation.php:38 msgid "Other" msgstr "" #: tpl/inc/modal.deactivation.php:47 msgid "Why are you deactivating the plugin?" msgstr "" #: tpl/inc/modal.deactivation.php:60 msgid "On uninstall, all plugin settings will be deleted." msgstr "" #: tpl/inc/modal.deactivation.php:68 msgid "If you have used Image Optimization, please %sDestroy All Optimization Data%s first. NOTE: this does not remove your optimized images." msgstr "" #: tpl/inc/modal.deactivation.php:76 msgid "Deactivate" msgstr "" #: tpl/inc/modal.deactivation.php:76 msgid "Deactivate plugin" msgstr "" #: tpl/inc/modal.deactivation.php:77 msgid "Close popup" msgstr "" #: tpl/inc/show_display_installed.php:26 msgid "LiteSpeed Cache plugin is installed!" msgstr "" #: tpl/inc/show_display_installed.php:27 msgid "This message indicates that the plugin was installed by the server admin." msgstr "" #: tpl/inc/show_display_installed.php:28 msgid "The LiteSpeed Cache plugin is used to cache pages - a simple way to improve the performance of the site." msgstr "" #: tpl/inc/show_display_installed.php:29 msgid "However, there is no way of knowing all the possible customizations that were implemented." msgstr "" #: tpl/inc/show_display_installed.php:30 msgid "For that reason, please test the site to make sure everything still functions properly." msgstr "" #: tpl/inc/show_display_installed.php:31 msgid "Examples of test cases include:" msgstr "" #: tpl/inc/show_display_installed.php:32 msgid "Visit the site while logged out." msgstr "" #: tpl/inc/show_display_installed.php:33 msgid "Create a post, make sure the front page is accurate." msgstr "" #. translators: %s: Link tags #: tpl/inc/show_display_installed.php:37 msgid "If there are any questions, the team is always happy to answer any questions on the %ssupport forum%s." msgstr "" #: tpl/inc/show_display_installed.php:41 msgid "If you would rather not move at litespeed, you can deactivate this plugin." msgstr "" #: tpl/inc/show_error_cookie.php:16 msgid "NOTICE: Database login cookie did not match your login cookie." msgstr "" #: tpl/inc/show_error_cookie.php:18 msgid "If the login cookie was recently changed in the settings, please log out and back in." msgstr "" #: tpl/inc/show_error_cookie.php:21 msgid "If not, please verify the setting in the %sAdvanced tab%s." msgstr "" #: tpl/inc/show_error_cookie.php:27 msgid "If using OpenLiteSpeed, the server must be restarted once for the changes to take effect." msgstr "" #: tpl/inc/show_rule_conflict.php:16 msgid "Unexpected cache rule %2$s found in %1$s file. This rule may cause visitors to see old versions of pages due to the browser caching HTML pages. If you are sure that HTML pages are not being browser cached, this message can be dismissed. (%3$sLearn More%4$s)" msgstr "" #: tpl/optimax/entry.tpl.php:16 msgid "OptimaX Summary" msgstr "" #: tpl/optimax/entry.tpl.php:17 #: tpl/optimax/entry.tpl.php:22 #: tpl/optimax/settings.tpl.php:19 msgid "OptimaX Settings" msgstr "" #: tpl/optimax/entry.tpl.php:30 msgid "LiteSpeed Cache OptimaX" msgstr "" #: tpl/optimax/settings.tpl.php:34 msgid "Turn on OptimaX. This will automatically request your pages OptimaX result via cron job." msgstr "" #: tpl/page_optm/entry.tpl.php:16 #: tpl/page_optm/settings_css.tpl.php:31 msgid "CSS Settings" msgstr "" #: tpl/page_optm/entry.tpl.php:17 #: tpl/page_optm/settings_js.tpl.php:17 msgid "JS Settings" msgstr "" #: tpl/page_optm/entry.tpl.php:18 #: tpl/page_optm/settings_html.tpl.php:17 msgid "HTML Settings" msgstr "" #: tpl/page_optm/entry.tpl.php:19 #: tpl/page_optm/settings_media.tpl.php:26 msgid "Media Settings" msgstr "" #: tpl/page_optm/entry.tpl.php:20 msgid "VPI" msgstr "" #: tpl/page_optm/entry.tpl.php:21 #: tpl/page_optm/settings_media_exc.tpl.php:17 msgid "Media Excludes" msgstr "" #: tpl/page_optm/entry.tpl.php:22 msgid "Localization" msgstr "" #: tpl/page_optm/entry.tpl.php:23 #: tpl/page_optm/entry.tpl.php:24 msgid "Tuning" msgstr "" #: tpl/page_optm/entry.tpl.php:31 msgid "LiteSpeed Cache Page Optimization" msgstr "" #: tpl/page_optm/entry.tpl.php:43 msgid "Please test thoroughly when enabling any option in this list. After changing Minify/Combine settings, please do a Purge All action." msgstr "" #: tpl/page_optm/settings_css.tpl.php:46 msgid "Minify CSS files and inline CSS code." msgstr "" #: tpl/page_optm/settings_css.tpl.php:60 msgid "Combine CSS files and inline CSS code." msgstr "" #: tpl/page_optm/settings_css.tpl.php:61 #: tpl/page_optm/settings_js.tpl.php:48 msgid "How to Fix Problems Caused by CSS/JS Optimization." msgstr "" #: tpl/page_optm/settings_css.tpl.php:82 msgid "Use QUIC.cloud online service to generate unique CSS." msgstr "" #: tpl/page_optm/settings_css.tpl.php:83 msgid "This will drop the unused CSS on each page from the combined file." msgstr "" #: tpl/page_optm/settings_css.tpl.php:85 msgid "Automatic generation of unique CSS is in the background via a cron-based queue." msgstr "" #: tpl/page_optm/settings_css.tpl.php:87 msgid "Filter %s available for UCSS per page type generation." msgstr "" #: tpl/page_optm/settings_css.tpl.php:93 msgid "This option is bypassed because %1$s option is %2$s." msgstr "" #: tpl/page_optm/settings_css.tpl.php:102 #: tpl/page_optm/settings_css.tpl.php:239 #: tpl/page_optm/settings_media.tpl.php:188 #: tpl/page_optm/settings_vpi.tpl.php:53 msgid "Last generated" msgstr "" #: tpl/page_optm/settings_css.tpl.php:105 #: tpl/page_optm/settings_css.tpl.php:242 msgid "Last requested cost" msgstr "" #: tpl/page_optm/settings_css.tpl.php:117 #: tpl/page_optm/settings_css.tpl.php:254 #: tpl/page_optm/settings_vpi.tpl.php:65 msgid "URL list in %s queue waiting for cron" msgstr "" #: tpl/page_optm/settings_css.tpl.php:118 #: tpl/page_optm/settings_css.tpl.php:255 #: tpl/page_optm/settings_media.tpl.php:201 #: tpl/page_optm/settings_vpi.tpl.php:66 msgid "Clear" msgstr "" #: tpl/page_optm/settings_css.tpl.php:155 #: tpl/page_optm/settings_css.tpl.php:160 #: tpl/page_optm/settings_css.tpl.php:292 #: tpl/page_optm/settings_css.tpl.php:297 #: tpl/page_optm/settings_vpi.tpl.php:100 #: tpl/page_optm/settings_vpi.tpl.php:105 msgid "Run %s Queue Manually" msgstr "" #: tpl/page_optm/settings_css.tpl.php:178 msgid "Inline UCSS to reduce the extra CSS file loading. This option will not be automatically turned on for %1$s pages. To use it on %1$s pages, please set it to ON." msgstr "" #: tpl/page_optm/settings_css.tpl.php:181 msgid "This option will automatically bypass %s option." msgstr "" #: tpl/page_optm/settings_css.tpl.php:195 msgid "Include external CSS and inline CSS in combined file when %1$s is also enabled. This option helps maintain the priorities of CSS, which should minimize potential errors caused by CSS Combine." msgstr "" #: tpl/page_optm/settings_css.tpl.php:215 msgid "Optimize CSS delivery." msgstr "" #: tpl/page_optm/settings_css.tpl.php:216 #: tpl/page_optm/settings_html.tpl.php:175 #: tpl/page_optm/settings_js.tpl.php:81 msgid "This can improve your speed score in services like Pingdom, GTmetrix and PageSpeed." msgstr "" #: tpl/page_optm/settings_css.tpl.php:217 msgid "Use QUIC.cloud online service to generate critical CSS and load remaining CSS asynchronously." msgstr "" #: tpl/page_optm/settings_css.tpl.php:219 msgid "Automatic generation of critical CSS is in the background via a cron-based queue." msgstr "" #: tpl/page_optm/settings_css.tpl.php:220 msgid "When this option is turned %s, it will also load Google Fonts asynchronously." msgstr "" #: tpl/page_optm/settings_css.tpl.php:224 msgid "Elements with attribute %s in HTML code will be excluded." msgstr "" #: tpl/page_optm/settings_css.tpl.php:230 msgid "This option is bypassed due to %s option." msgstr "" #: tpl/page_optm/settings_css.tpl.php:314 msgid "Disable this option to generate CCSS per Post Type instead of per page. This can save significant CCSS quota, however it may result in incorrect CSS styling if your site uses a page builder." msgstr "" #: tpl/page_optm/settings_css.tpl.php:327 msgid "This will inline the asynchronous CSS library to avoid render blocking." msgstr "" #: tpl/page_optm/settings_css.tpl.php:338 msgid "Default" msgstr "" #: tpl/page_optm/settings_css.tpl.php:340 msgid "Set this to append %1$s to all %2$s rules before caching CSS to specify how fonts should be displayed while being downloaded." msgstr "" #: tpl/page_optm/settings_css.tpl.php:341 msgid "%s is recommended." msgstr "" #: tpl/page_optm/settings_css.tpl.php:341 msgid "Swap" msgstr "" #: tpl/page_optm/settings_html.tpl.php:31 msgid "Minify HTML content." msgstr "" #: tpl/page_optm/settings_html.tpl.php:44 msgid "Prefetching DNS can reduce latency for visitors." msgstr "" #: tpl/page_optm/settings_html.tpl.php:45 #: tpl/page_optm/settings_html.tpl.php:76 msgid "For example" msgstr "" #: tpl/page_optm/settings_html.tpl.php:60 msgid "Automatically enable DNS prefetching for all URLs in the document, including images, CSS, JavaScript, and so forth." msgstr "" #: tpl/page_optm/settings_html.tpl.php:61 msgid "This can improve the page loading speed." msgstr "" #: tpl/page_optm/settings_html.tpl.php:75 msgid "Preconnecting speeds up future loads from a given origin." msgstr "" #: tpl/page_optm/settings_html.tpl.php:91 msgid "Delay rendering off-screen HTML elements by its selector." msgstr "" #: tpl/page_optm/settings_html.tpl.php:106 msgid "When minifying HTML do not discard comments that match a specified pattern." msgstr "" #: tpl/page_optm/settings_html.tpl.php:108 msgid "If comment to be kept is like: %1$s write: %2$s" msgstr "" #: tpl/page_optm/settings_html.tpl.php:123 msgid "Remove query strings from internal static resources." msgstr "" #: tpl/page_optm/settings_html.tpl.php:127 msgid "Google reCAPTCHA will be bypassed automatically." msgstr "" #: tpl/page_optm/settings_html.tpl.php:132 msgid "Append query string %s to the resources to bypass this action." msgstr "" #: tpl/page_optm/settings_html.tpl.php:146 msgid "Use Web Font Loader library to load Google Fonts asynchronously while leaving other CSS intact." msgstr "" #: tpl/page_optm/settings_html.tpl.php:147 msgid "This will also add a preconnect to Google Fonts to establish a connection earlier." msgstr "" #: tpl/page_optm/settings_html.tpl.php:161 msgid "Prevent Google Fonts from loading on all pages." msgstr "" #: tpl/page_optm/settings_html.tpl.php:174 msgid "Stop loading WordPress.org emoji. Browser default emoji will be displayed instead." msgstr "" #: tpl/page_optm/settings_html.tpl.php:188 msgid "This option will remove all %s tags from HTML." msgstr "" #: tpl/page_optm/settings_js.tpl.php:33 msgid "Minify JS files and inline JS codes." msgstr "" #: tpl/page_optm/settings_js.tpl.php:47 msgid "Combine all local JS files into a single file." msgstr "" #: tpl/page_optm/settings_js.tpl.php:51 #: tpl/page_optm/settings_js.tpl.php:85 msgid "This option may result in a JS error or layout issue on frontend pages with certain themes/plugins." msgstr "" #: tpl/page_optm/settings_js.tpl.php:52 msgid "JS error can be found from the developer console of browser by right clicking and choosing Inspect." msgstr "" #: tpl/page_optm/settings_js.tpl.php:66 msgid "Include external JS and inline JS in combined file when %1$s is also enabled. This option helps maintain the priorities of JS execution, which should minimize potential errors caused by JS Combine." msgstr "" #: tpl/page_optm/settings_js.tpl.php:77 msgid "Deferred" msgstr "" #: tpl/page_optm/settings_js.tpl.php:77 msgid "Delayed" msgstr "" #: tpl/page_optm/settings_js.tpl.php:79 msgid "Deferring until page is parsed or delaying till interaction can help reduce resource contention and improve performance causing a lower FID (Core Web Vitals metric)." msgstr "" #: tpl/page_optm/settings_localization.tpl.php:26 msgid "Failed to create Avatar table. Please follow Table Creation guidance from LiteSpeed Wiki to finish setup." msgstr "" #: tpl/page_optm/settings_localization.tpl.php:36 msgid "Localization Settings" msgstr "" #: tpl/page_optm/settings_localization.tpl.php:49 msgid "Store Gravatar locally." msgstr "" #: tpl/page_optm/settings_localization.tpl.php:50 msgid "Accelerates the speed by caching Gravatar (Globally Recognized Avatars)." msgstr "" #: tpl/page_optm/settings_localization.tpl.php:63 msgid "Refresh Gravatar cache by cron." msgstr "" #: tpl/page_optm/settings_localization.tpl.php:76 msgid "Avatar list in queue waiting for update" msgstr "" #: tpl/page_optm/settings_localization.tpl.php:81 #: tpl/page_optm/settings_media.tpl.php:218 msgid "Run Queue Manually" msgstr "" #: tpl/page_optm/settings_localization.tpl.php:98 msgid "Specify how long, in seconds, Gravatar files are cached." msgstr "" #: tpl/page_optm/settings_localization.tpl.php:113 msgid "Localize external resources." msgstr "" #: tpl/page_optm/settings_localization.tpl.php:117 msgid "Please thoroughly test all items in %s to ensure they function as expected." msgstr "" #: tpl/page_optm/settings_localization.tpl.php:139 msgid "Resources listed here will be copied and replaced with local URLs." msgstr "" #: tpl/page_optm/settings_localization.tpl.php:140 msgid "HTTPS sources only." msgstr "" #: tpl/page_optm/settings_localization.tpl.php:144 msgid "Comments are supported. Start a line with a %s to turn it into a comment line." msgstr "" #: tpl/page_optm/settings_localization.tpl.php:146 #: tpl/toolbox/beta_test.tpl.php:51 msgid "Example" msgstr "" #: tpl/page_optm/settings_localization.tpl.php:150 msgid "Please thoroughly test each JS file you add to ensure it functions as expected." msgstr "" #: tpl/page_optm/settings_media.tpl.php:40 msgid "Load images only when they enter the viewport." msgstr "" #: tpl/page_optm/settings_media.tpl.php:41 #: tpl/page_optm/settings_media.tpl.php:235 msgid "This can improve page loading time by reducing initial HTTP requests." msgstr "" #: tpl/page_optm/settings_media.tpl.php:45 msgid "Adding Style to Your Lazy-Loaded Images" msgstr "" #: tpl/page_optm/settings_media.tpl.php:59 msgid "Specify a base64 image to be used as a simple placeholder while images finish loading." msgstr "" #: tpl/page_optm/settings_media.tpl.php:60 msgid "This can be predefined in %2$s as well using constant %1$s, with this setting taking priority." msgstr "" #: tpl/page_optm/settings_media.tpl.php:61 msgid "By default a gray image placeholder %s will be used." msgstr "" #: tpl/page_optm/settings_media.tpl.php:62 msgid "For example, %s can be used for a transparent placeholder." msgstr "" #: tpl/page_optm/settings_media.tpl.php:76 msgid "Responsive image placeholders can help to reduce layout reshuffle when images are loaded." msgstr "" #: tpl/page_optm/settings_media.tpl.php:77 msgid "This will generate the placeholder with same dimensions as the image if it has the width and height attributes." msgstr "" #: tpl/page_optm/settings_media.tpl.php:90 msgid "Specify an SVG to be used as a placeholder when generating locally." msgstr "" #: tpl/page_optm/settings_media.tpl.php:91 msgid "It will be converted to a base64 SVG placeholder on-the-fly." msgstr "" #: tpl/page_optm/settings_media.tpl.php:92 msgid "Variables %s will be replaced with the corresponding image properties." msgstr "" #: tpl/page_optm/settings_media.tpl.php:93 msgid "Variables %s will be replaced with the configured background color." msgstr "" #: tpl/page_optm/settings_media.tpl.php:107 msgid "Specify the responsive placeholder SVG color." msgstr "" #: tpl/page_optm/settings_media.tpl.php:122 msgid "Use QUIC.cloud LQIP (Low Quality Image Placeholder) generator service for responsive image previews while loading." msgstr "" #: tpl/page_optm/settings_media.tpl.php:123 msgid "Keep this off to use plain color placeholders." msgstr "" #: tpl/page_optm/settings_media.tpl.php:137 msgid "Specify the quality when generating LQIP." msgstr "" #: tpl/page_optm/settings_media.tpl.php:138 msgid "Larger number will generate higher resolution quality placeholder, but will result in larger files which will increase page size and consume more points." msgstr "" #: tpl/page_optm/settings_media.tpl.php:141 msgid "Changes to this setting do not apply to already-generated LQIPs. To regenerate existing LQIPs, please %s first from the admin bar menu." msgstr "" #: tpl/page_optm/settings_media.tpl.php:154 msgid "pixels" msgstr "" #: tpl/page_optm/settings_media.tpl.php:156 msgid "LQIP requests will not be sent for images where both width and height are smaller than these dimensions." msgstr "" #: tpl/page_optm/settings_media.tpl.php:172 msgid "Automatically generate LQIP in the background via a cron-based queue." msgstr "" #: tpl/page_optm/settings_media.tpl.php:175 msgid "If set to %1$s, before the placeholder is localized, the %2$s configuration will be used." msgstr "" #: tpl/page_optm/settings_media.tpl.php:180 msgid "If set to %s this is done in the foreground, which may slow down page load." msgstr "" #: tpl/page_optm/settings_media.tpl.php:200 msgid "Size list in queue waiting for cron" msgstr "" #: tpl/page_optm/settings_media.tpl.php:234 msgid "Load iframes only when they enter the viewport." msgstr "" #: tpl/page_optm/settings_media.tpl.php:248 msgid "Set an explicit width and height on image elements to reduce layout shifts and improve CLS (a Core Web Vitals metric)." msgstr "" #: tpl/page_optm/settings_media.tpl.php:259 msgid "Use %1$s to bypass remote image dimension check when %2$s is ON." msgstr "" #: tpl/page_optm/settings_media.tpl.php:274 msgid "The image compression quality setting of WordPress out of 100." msgstr "" #: tpl/page_optm/settings_media.tpl.php:289 msgid "Automatically replace large images with scaled versions." msgstr "" #: tpl/page_optm/settings_media.tpl.php:290 msgid "Scaled size threshold" msgstr "" #: tpl/page_optm/settings_media.tpl.php:296 msgid "Filter %s available to change threshold." msgstr "" #: tpl/page_optm/settings_media_exc.tpl.php:31 msgid "Listed images will not be lazy loaded." msgstr "" #: tpl/page_optm/settings_media_exc.tpl.php:34 msgid "Useful for above-the-fold images causing CLS (a Core Web Vitals metric)." msgstr "" #: tpl/page_optm/settings_media_exc.tpl.php:38 #: tpl/page_optm/settings_tuning.tpl.php:70 #: tpl/page_optm/settings_tuning.tpl.php:91 #: tpl/page_optm/settings_tuning.tpl.php:112 #: tpl/page_optm/settings_tuning_css.tpl.php:37 msgid "Elements with attribute %s in html code will be excluded." msgstr "" #: tpl/page_optm/settings_media_exc.tpl.php:60 msgid "Images containing these class names will not be lazy loaded." msgstr "" #: tpl/page_optm/settings_media_exc.tpl.php:75 msgid "Images having these parent class names will not be lazy loaded." msgstr "" #: tpl/page_optm/settings_media_exc.tpl.php:89 msgid "Iframes containing these class names will not be lazy loaded." msgstr "" #: tpl/page_optm/settings_media_exc.tpl.php:104 msgid "Iframes having these parent class names will not be lazy loaded." msgstr "" #: tpl/page_optm/settings_media_exc.tpl.php:118 msgid "Prevent any lazy load of listed pages." msgstr "" #: tpl/page_optm/settings_media_exc.tpl.php:132 msgid "These images will not generate LQIP." msgstr "" #: tpl/page_optm/settings_tuning.tpl.php:29 msgid "Tuning Settings" msgstr "" #: tpl/page_optm/settings_tuning.tpl.php:43 msgid "Listed JS files or inline JS code will be delayed." msgstr "" #: tpl/page_optm/settings_tuning.tpl.php:63 msgid "Listed JS files or inline JS code will not be minified or combined." msgstr "" #: tpl/page_optm/settings_tuning.tpl.php:71 #: tpl/page_optm/settings_tuning.tpl.php:92 msgid "Predefined list will also be combined with the above settings." msgstr "" #: tpl/page_optm/settings_tuning.tpl.php:85 msgid "Listed JS files or inline JS code will not be deferred or delayed." msgstr "" #: tpl/page_optm/settings_tuning.tpl.php:106 msgid "Listed JS files or inline JS code will not be optimized by %s." msgstr "" #: tpl/page_optm/settings_tuning.tpl.php:126 msgid "Prevent any optimization of listed pages." msgstr "" #: tpl/page_optm/settings_tuning.tpl.php:144 msgid "Only optimize pages for guest (not logged in) visitors. If turned this OFF, CSS/JS/CCSS files will be doubled by each user group." msgstr "" #: tpl/page_optm/settings_tuning.tpl.php:156 msgid "Selected roles will be excluded from all optimizations." msgstr "" #: tpl/page_optm/settings_tuning_css.tpl.php:17 msgid "Tuning CSS Settings" msgstr "" #: tpl/page_optm/settings_tuning_css.tpl.php:31 msgid "Listed CSS files or inline CSS code will not be minified or combined." msgstr "" #: tpl/page_optm/settings_tuning_css.tpl.php:38 msgid "Predefined list will also be combined with the above settings" msgstr "" #: tpl/page_optm/settings_tuning_css.tpl.php:52 msgid "Listed CSS files will be excluded from UCSS and saved to inline." msgstr "" #: tpl/page_optm/settings_tuning_css.tpl.php:67 msgid "List the CSS selectors whose styles should always be included in UCSS." msgstr "" #: tpl/page_optm/settings_tuning_css.tpl.php:70 #: tpl/page_optm/settings_tuning_css.tpl.php:145 msgid "Wildcard %s supported." msgstr "" #: tpl/page_optm/settings_tuning_css.tpl.php:74 msgid "The selector must exist in the CSS. Parent classes in the HTML will not work." msgstr "" #: tpl/page_optm/settings_tuning_css.tpl.php:92 msgid "Listed URI will not generate UCSS." msgstr "" #: tpl/page_optm/settings_tuning_css.tpl.php:99 msgid "Use %1$s to generate one single UCSS for the pages which page type is %2$s while other page types still per URL." msgstr "" #: tpl/page_optm/settings_tuning_css.tpl.php:100 msgid "Use %1$s to bypass UCSS for the pages which page type is %2$s." msgstr "" #: tpl/page_optm/settings_tuning_css.tpl.php:113 msgid "List post types where each item of that type should have its own CCSS generated." msgstr "" #: tpl/page_optm/settings_tuning_css.tpl.php:114 msgid "For example, if every Page on the site has different formatting, enter %s in the box. Separate critical CSS files will be stored for every Page on the site." msgstr "" #: tpl/page_optm/settings_tuning_css.tpl.php:128 msgid "Separate critical CSS files will be generated for paths containing these strings." msgstr "" #: tpl/page_optm/settings_tuning_css.tpl.php:142 msgid "List the CSS selectors whose styles should always be included in CCSS." msgstr "" #: tpl/page_optm/settings_tuning_css.tpl.php:149 msgid "Selectors must exist in the CSS. Parent classes in the HTML will not work." msgstr "" #: tpl/page_optm/settings_tuning_css.tpl.php:167 msgid "Specify critical CSS rules for above-the-fold content when enabling %s." msgstr "" #: tpl/page_optm/settings_vpi.tpl.php:37 msgid "When you use Lazy Load, it will delay the loading of all images on a page." msgstr "" #: tpl/page_optm/settings_vpi.tpl.php:38 msgid "The Viewport Images service detects which images appear above the fold, and excludes them from lazy load." msgstr "" #: tpl/page_optm/settings_vpi.tpl.php:39 msgid "This enables the page's initial screenful of imagery to be fully displayed without delay." msgstr "" #: tpl/page_optm/settings_vpi.tpl.php:122 msgid "Enable Viewport Images auto generation cron." msgstr "" #: tpl/presets/entry.tpl.php:16 msgid "Standard Presets" msgstr "" #: tpl/presets/entry.tpl.php:17 #: tpl/toolbox/entry.tpl.php:20 msgid "Import / Export" msgstr "" #: tpl/presets/entry.tpl.php:23 msgid "LiteSpeed Cache Configuration Presets" msgstr "" #: tpl/presets/standard.tpl.php:17 msgid "Essentials" msgstr "" #: tpl/presets/standard.tpl.php:19 msgid "Default Cache" msgstr "" #: tpl/presets/standard.tpl.php:20 msgid "Higher TTL" msgstr "" #: tpl/presets/standard.tpl.php:24 msgid "This no-risk preset is appropriate for all websites. Good for new users, simple websites, or cache-oriented development." msgstr "" #: tpl/presets/standard.tpl.php:25 msgid "A QUIC.cloud connection is not required to use this preset. Only basic caching features are enabled." msgstr "" #: tpl/presets/standard.tpl.php:29 #: tpl/toolbox/settings-debug.tpl.php:117 msgid "Basic" msgstr "" #: tpl/presets/standard.tpl.php:31 msgid "Everything in Essentials, Plus" msgstr "" #: tpl/presets/standard.tpl.php:33 msgid "Mobile Cache" msgstr "" #: tpl/presets/standard.tpl.php:36 msgid "This low-risk preset introduces basic optimizations for speed and user experience. Appropriate for enthusiastic beginners." msgstr "" #: tpl/presets/standard.tpl.php:37 msgid "A QUIC.cloud connection is required to use this preset. Includes optimizations known to improve site score in page speed measurement tools." msgstr "" #: tpl/presets/standard.tpl.php:41 msgid "Advanced (Recommended)" msgstr "" #: tpl/presets/standard.tpl.php:43 msgid "Everything in Basic, Plus" msgstr "" #: tpl/presets/standard.tpl.php:44 msgid "Guest Mode and Guest Optimization" msgstr "" #: tpl/presets/standard.tpl.php:45 msgid "CSS, JS and HTML Minification" msgstr "" #: tpl/presets/standard.tpl.php:47 msgid "JS Defer for both external and inline JS" msgstr "" #: tpl/presets/standard.tpl.php:48 msgid "DNS Prefetch for static files" msgstr "" #: tpl/presets/standard.tpl.php:50 msgid "Remove Query Strings from Static Files" msgstr "" #: tpl/presets/standard.tpl.php:55 msgid "This preset is good for most websites, and is unlikely to cause conflicts. Any CSS or JS conflicts may be resolved with Page Optimization > Tuning tools." msgstr "" #: tpl/presets/standard.tpl.php:56 #: tpl/presets/standard.tpl.php:70 msgid "A QUIC.cloud connection is required to use this preset. Includes many optimizations known to improve page speed scores." msgstr "" #: tpl/presets/standard.tpl.php:60 msgid "Aggressive" msgstr "" #: tpl/presets/standard.tpl.php:62 msgid "Everything in Advanced, Plus" msgstr "" #: tpl/presets/standard.tpl.php:63 msgid "CSS & JS Combine" msgstr "" #: tpl/presets/standard.tpl.php:64 msgid "Asynchronous CSS Loading with Critical CSS" msgstr "" #: tpl/presets/standard.tpl.php:65 msgid "Removed Unused CSS for Users" msgstr "" #: tpl/presets/standard.tpl.php:66 msgid "Lazy Load for Iframes" msgstr "" #: tpl/presets/standard.tpl.php:69 msgid "This preset might work out of the box for some websites, but be sure to test! Some CSS or JS exclusions may be necessary in Page Optimization > Tuning." msgstr "" #: tpl/presets/standard.tpl.php:74 msgid "Extreme" msgstr "" #: tpl/presets/standard.tpl.php:76 msgid "Everything in Aggressive, Plus" msgstr "" #: tpl/presets/standard.tpl.php:77 msgid "Lazy Load for Images" msgstr "" #: tpl/presets/standard.tpl.php:78 msgid "Viewport Image Generation" msgstr "" #: tpl/presets/standard.tpl.php:79 msgid "JS Delayed" msgstr "" #: tpl/presets/standard.tpl.php:80 msgid "Inline JS added to Combine" msgstr "" #: tpl/presets/standard.tpl.php:81 msgid "Inline CSS added to Combine" msgstr "" #: tpl/presets/standard.tpl.php:84 msgid "This preset almost certainly will require testing and exclusions for some CSS, JS and Lazy Loaded images. Pay special attention to logos, or HTML-based slider images." msgstr "" #: tpl/presets/standard.tpl.php:85 msgid "A QUIC.cloud connection is required to use this preset. Enables the maximum level of optimizations for improved page speed scores." msgstr "" #: tpl/presets/standard.tpl.php:92 msgid "LiteSpeed Cache Standard Presets" msgstr "" #: tpl/presets/standard.tpl.php:96 msgid "Use an official LiteSpeed-designed Preset to configure your site in one click. Try no-risk caching essentials, extreme optimization, or something in between." msgstr "" #: tpl/presets/standard.tpl.php:121 msgid "Who should use this preset?" msgstr "" #: tpl/presets/standard.tpl.php:131 msgid "This will back up your current settings and replace them with the %1$s preset settings. Do you want to continue?" msgstr "" #: tpl/presets/standard.tpl.php:133 msgid "Apply Preset" msgstr "" #: tpl/presets/standard.tpl.php:152 msgid "unknown" msgstr "" #: tpl/presets/standard.tpl.php:163 msgid "History" msgstr "" #: tpl/presets/standard.tpl.php:173 msgid "Error: Failed to apply the settings %1$s" msgstr "" #: tpl/presets/standard.tpl.php:175 msgid "Restored backup settings %1$s" msgstr "" #: tpl/presets/standard.tpl.php:178 msgid "Applied the %1$s preset %2$s" msgstr "" #: tpl/presets/standard.tpl.php:189 msgid "Backup created %1$s before applying the %2$s preset" msgstr "" #: tpl/presets/standard.tpl.php:193 msgid "This will restore the backup settings created %1$s before applying the %2$s preset. Any changes made since then will be lost. Do you want to continue?" msgstr "" #: tpl/presets/standard.tpl.php:195 msgid "Restore Settings" msgstr "" #: tpl/toolbox/beta_test.tpl.php:36 msgid "Try GitHub Version" msgstr "" #: tpl/toolbox/beta_test.tpl.php:43 msgid "LiteSpeed Cache is disabled. This functionality will not work." msgstr "" #: tpl/toolbox/beta_test.tpl.php:48 msgid "Use this section to switch plugin versions. To beta test a GitHub commit, enter the commit URL in the field below." msgstr "" #: tpl/toolbox/beta_test.tpl.php:57 msgid "Use latest GitHub Dev commit" msgstr "" #: tpl/toolbox/beta_test.tpl.php:61 msgid "Use latest GitHub Master commit" msgstr "" #: tpl/toolbox/beta_test.tpl.php:65 #: tpl/toolbox/beta_test.tpl.php:81 msgid "Use latest WordPress release version" msgstr "" #: tpl/toolbox/beta_test.tpl.php:65 msgid "OR" msgstr "" #: tpl/toolbox/beta_test.tpl.php:73 msgid "Downgrade not recommended. May cause fatal error due to refactored code." msgstr "" #: tpl/toolbox/beta_test.tpl.php:78 msgid "Press the %s button to use the most recent GitHub commit. Master is for release candidate & Dev is for experimental testing." msgstr "" #: tpl/toolbox/beta_test.tpl.php:78 msgid "Use latest GitHub Dev/Master commit" msgstr "" #: tpl/toolbox/beta_test.tpl.php:81 msgid "Press the %s button to stop beta testing and go back to the current release from the WordPress Plugin Directory." msgstr "" #: tpl/toolbox/beta_test.tpl.php:85 msgid "In order to avoid an upgrade error, you must be using %1$s or later before you can upgrade to %2$s versions." msgstr "" #: tpl/toolbox/edit_htaccess.tpl.php:41 msgid "LiteSpeed Cache View .htaccess" msgstr "" #: tpl/toolbox/edit_htaccess.tpl.php:46 msgid ".htaccess Path" msgstr "" #: tpl/toolbox/edit_htaccess.tpl.php:53 msgid "Frontend .htaccess Path" msgstr "" #: tpl/toolbox/edit_htaccess.tpl.php:58 #: tpl/toolbox/edit_htaccess.tpl.php:76 msgid "Default path is" msgstr "" #: tpl/toolbox/edit_htaccess.tpl.php:62 #: tpl/toolbox/edit_htaccess.tpl.php:80 msgid "PHP Constant %s is supported." msgstr "" #: tpl/toolbox/edit_htaccess.tpl.php:63 #: tpl/toolbox/edit_htaccess.tpl.php:81 msgid "You can use this code %1$s in %2$s to specify the htaccess file path." msgstr "" #: tpl/toolbox/edit_htaccess.tpl.php:71 msgid "Backend .htaccess Path" msgstr "" #: tpl/toolbox/edit_htaccess.tpl.php:91 msgid "Current %s Contents" msgstr "" #: tpl/toolbox/entry.tpl.php:24 msgid "View .htaccess" msgstr "" #: tpl/toolbox/entry.tpl.php:28 msgid "Heartbeat" msgstr "" #: tpl/toolbox/entry.tpl.php:29 msgid "Report" msgstr "" #: tpl/toolbox/entry.tpl.php:33 #: tpl/toolbox/settings-debug.tpl.php:55 msgid "Debug Settings" msgstr "" #: tpl/toolbox/entry.tpl.php:34 msgid "Log View" msgstr "" #: tpl/toolbox/entry.tpl.php:35 msgid "Beta Test" msgstr "" #: tpl/toolbox/entry.tpl.php:41 msgid "LiteSpeed Cache Toolbox" msgstr "" #: tpl/toolbox/heartbeat.tpl.php:19 msgid "Heartbeat Control" msgstr "" #: tpl/toolbox/heartbeat.tpl.php:26 msgid "Disable WordPress interval heartbeat to reduce server load." msgstr "" #: tpl/toolbox/heartbeat.tpl.php:28 msgid "Disabling this may cause WordPress tasks triggered by AJAX to stop working." msgstr "" #: tpl/toolbox/heartbeat.tpl.php:43 msgid "Turn ON to control heartbeat on frontend." msgstr "" #: tpl/toolbox/heartbeat.tpl.php:56 #: tpl/toolbox/heartbeat.tpl.php:86 #: tpl/toolbox/heartbeat.tpl.php:116 msgid "Specify the %s heartbeat interval in seconds." msgstr "" #: tpl/toolbox/heartbeat.tpl.php:57 #: tpl/toolbox/heartbeat.tpl.php:87 #: tpl/toolbox/heartbeat.tpl.php:117 msgid "WordPress valid interval is %s seconds." msgstr "" #: tpl/toolbox/heartbeat.tpl.php:58 #: tpl/toolbox/heartbeat.tpl.php:88 #: tpl/toolbox/heartbeat.tpl.php:118 msgid "Set to %1$s to forbid heartbeat on %2$s." msgstr "" #: tpl/toolbox/heartbeat.tpl.php:73 msgid "Turn ON to control heartbeat on backend." msgstr "" #: tpl/toolbox/heartbeat.tpl.php:103 msgid "Turn ON to control heartbeat in backend editor." msgstr "" #: tpl/toolbox/import_export.tpl.php:19 msgid "Export Settings" msgstr "" #: tpl/toolbox/import_export.tpl.php:25 msgid "Export" msgstr "" #: tpl/toolbox/import_export.tpl.php:31 msgid "Last exported" msgstr "" #: tpl/toolbox/import_export.tpl.php:36 msgid "This will export all current LiteSpeed Cache settings and save them as a file." msgstr "" #: tpl/toolbox/import_export.tpl.php:40 msgid "Import Settings" msgstr "" #: tpl/toolbox/import_export.tpl.php:48 msgid "Import" msgstr "" #: tpl/toolbox/import_export.tpl.php:54 msgid "Last imported" msgstr "" #: tpl/toolbox/import_export.tpl.php:59 msgid "This will import settings from a file and override all current LiteSpeed Cache settings." msgstr "" #: tpl/toolbox/import_export.tpl.php:63 msgid "Reset All Settings" msgstr "" #: tpl/toolbox/import_export.tpl.php:67 msgid "This will reset all settings to default settings." msgstr "" #: tpl/toolbox/import_export.tpl.php:70 msgid "Are you sure you want to reset all settings back to the default settings?" msgstr "" #: tpl/toolbox/import_export.tpl.php:71 msgid "Reset Settings" msgstr "" #: tpl/toolbox/log_viewer.tpl.php:23 msgid "Purge Log" msgstr "" #: tpl/toolbox/log_viewer.tpl.php:28 msgid "Crawler Log" msgstr "" #: tpl/toolbox/log_viewer.tpl.php:35 msgid "LiteSpeed Logs" msgstr "" #: tpl/toolbox/log_viewer.tpl.php:46 #: tpl/toolbox/log_viewer.tpl.php:75 msgid "Clear Logs" msgstr "" #: tpl/toolbox/log_viewer.tpl.php:64 #: tpl/toolbox/report.tpl.php:62 msgid "Click to copy" msgstr "" #: tpl/toolbox/log_viewer.tpl.php:65 msgid "Copy Log" msgstr "" #: tpl/toolbox/purge.tpl.php:17 msgid "Purge Front Page" msgstr "" #: tpl/toolbox/purge.tpl.php:18 msgid "This will Purge Front Page only" msgstr "" #: tpl/toolbox/purge.tpl.php:23 msgid "Purge Pages" msgstr "" #: tpl/toolbox/purge.tpl.php:24 msgid "This will Purge Pages only" msgstr "" #: tpl/toolbox/purge.tpl.php:32 msgid "Purge %s Error" msgstr "" #: tpl/toolbox/purge.tpl.php:33 msgid "Purge %s error pages" msgstr "" #: tpl/toolbox/purge.tpl.php:41 msgid "Purge the LiteSpeed cache entries created by this plugin" msgstr "" #: tpl/toolbox/purge.tpl.php:48 msgid "This will purge all minified/combined CSS/JS entries only" msgstr "" #: tpl/toolbox/purge.tpl.php:56 msgid "Purge all the object caches" msgstr "" #: tpl/toolbox/purge.tpl.php:65 msgid "Reset the entire opcode cache" msgstr "" #: tpl/toolbox/purge.tpl.php:74 msgid "This will delete all generated critical CSS files" msgstr "" #: tpl/toolbox/purge.tpl.php:83 msgid "This will delete all generated unique CSS files" msgstr "" #: tpl/toolbox/purge.tpl.php:92 msgid "This will delete all localized resources" msgstr "" #: tpl/toolbox/purge.tpl.php:101 msgid "This will delete all generated image LQIP placeholder files" msgstr "" #: tpl/toolbox/purge.tpl.php:110 msgid "This will delete all generated Viewport Images" msgstr "" #: tpl/toolbox/purge.tpl.php:119 msgid "This will delete all cached Gravatar files" msgstr "" #: tpl/toolbox/purge.tpl.php:127 msgid "Purge the cache entries created by this plugin except for Critical CSS & Unique CSS & LQIP caches" msgstr "" #: tpl/toolbox/purge.tpl.php:136 msgid "Empty Entire Cache" msgstr "" #: tpl/toolbox/purge.tpl.php:137 msgid "Clears all cache entries related to this site, including other web applications." msgstr "" #: tpl/toolbox/purge.tpl.php:137 msgid "This action should only be used if things are cached incorrectly." msgstr "" #: tpl/toolbox/purge.tpl.php:141 msgid "This will clear EVERYTHING inside the cache." msgstr "" #: tpl/toolbox/purge.tpl.php:141 msgid "This may cause heavy load on the server." msgstr "" #: tpl/toolbox/purge.tpl.php:141 msgid "If only the WordPress site should be purged, use Purge All." msgstr "" #: tpl/toolbox/purge.tpl.php:185 msgid "Purge By..." msgstr "" #: tpl/toolbox/purge.tpl.php:188 msgid "Select below for \"Purge by\" options." msgstr "" #: tpl/toolbox/purge.tpl.php:197 msgid "Category" msgstr "" #: tpl/toolbox/purge.tpl.php:201 msgid "Post ID" msgstr "" #: tpl/toolbox/purge.tpl.php:205 msgid "Tag" msgstr "" #: tpl/toolbox/purge.tpl.php:214 msgid "Purge pages by category name - e.g. %2$s should be used for the URL %1$s." msgstr "" #: tpl/toolbox/purge.tpl.php:217 msgid "Purge pages by post ID." msgstr "" #: tpl/toolbox/purge.tpl.php:220 msgid "Purge pages by tag name - e.g. %2$s should be used for the URL %1$s." msgstr "" #: tpl/toolbox/purge.tpl.php:223 msgid "Purge pages by relative or full URL." msgstr "" #: tpl/toolbox/purge.tpl.php:224 msgid "e.g. Use %1$s or %2$s." msgstr "" #: tpl/toolbox/purge.tpl.php:234 msgid "Purge List" msgstr "" #: tpl/toolbox/report.tpl.php:38 msgid "Send to LiteSpeed" msgstr "" #: tpl/toolbox/report.tpl.php:40 msgid "Regenerate and Send a New Report" msgstr "" #: tpl/toolbox/report.tpl.php:48 msgid "To generate a passwordless link for LiteSpeed Support Team access, you must install %s." msgstr "" #: tpl/toolbox/report.tpl.php:51 msgid "Install DoLogin Security" msgstr "" #: tpl/toolbox/report.tpl.php:52 msgid "Go to plugins list" msgstr "" #: tpl/toolbox/report.tpl.php:58 msgid "LiteSpeed Report" msgstr "" #: tpl/toolbox/report.tpl.php:62 msgid "Last Report Number" msgstr "" #: tpl/toolbox/report.tpl.php:63 msgid "Last Report Date" msgstr "" #: tpl/toolbox/report.tpl.php:66 msgid "The environment report contains detailed information about the WordPress configuration." msgstr "" #: tpl/toolbox/report.tpl.php:68 msgid "If you run into any issues, please refer to the report number in your support message." msgstr "" #: tpl/toolbox/report.tpl.php:75 msgid "System Information" msgstr "" #: tpl/toolbox/report.tpl.php:87 msgid "Attach PHP info to report. Check this box to insert relevant data from %s." msgstr "" #: tpl/toolbox/report.tpl.php:96 msgid "Passwordless Link" msgstr "" #: tpl/toolbox/report.tpl.php:100 #: tpl/toolbox/report.tpl.php:102 msgid "Generate Link for Current User" msgstr "" #: tpl/toolbox/report.tpl.php:105 msgid "To grant wp-admin access to the LiteSpeed Support Team, please generate a passwordless link for the current logged-in user to be sent with the report." msgstr "" #: tpl/toolbox/report.tpl.php:107 msgid "Please do NOT share the above passwordless link with anyone." msgstr "" #. translators: %s: Link tags #: tpl/toolbox/report.tpl.php:112 msgid "Generated links may be managed under %sSettings%s." msgstr "" #: tpl/toolbox/report.tpl.php:122 msgid "Notes" msgstr "" #: tpl/toolbox/report.tpl.php:126 msgid "Optional" msgstr "" #: tpl/toolbox/report.tpl.php:127 msgid "provide more information here to assist the LiteSpeed team with debugging." msgstr "" #: tpl/toolbox/report.tpl.php:139 msgid "Send this report to LiteSpeed. Refer to this report number when posting in the WordPress support forum." msgstr "" #: tpl/toolbox/settings-debug.tpl.php:19 msgid "Debug Helpers" msgstr "" #: tpl/toolbox/settings-debug.tpl.php:23 msgid "View Site Before Optimization" msgstr "" #: tpl/toolbox/settings-debug.tpl.php:27 msgid "View Site Before Cache" msgstr "" #: tpl/toolbox/settings-debug.tpl.php:37 msgid "Disable All Features for 24 Hours" msgstr "" #: tpl/toolbox/settings-debug.tpl.php:44 msgid "Remove `Disable All Feature` Flag Now" msgstr "" #: tpl/toolbox/settings-debug.tpl.php:48 msgid "LiteSpeed Cache is temporarily disabled until: %s." msgstr "" #: tpl/toolbox/settings-debug.tpl.php:69 msgid "This will disable LSCache and all optimization features for debug purpose." msgstr "" #: tpl/toolbox/settings-debug.tpl.php:80 msgid "Admin IP Only" msgstr "" #: tpl/toolbox/settings-debug.tpl.php:82 msgid "Outputs to a series of files in the %s directory." msgstr "" #: tpl/toolbox/settings-debug.tpl.php:83 msgid "To prevent filling up the disk, this setting should be OFF when everything is working." msgstr "" #: tpl/toolbox/settings-debug.tpl.php:84 msgid "The Admin IP option will only output log messages on requests from admin IPs listed below." msgstr "" #: tpl/toolbox/settings-debug.tpl.php:97 msgid "Allows listed IPs (one per line) to perform certain actions from their browsers." msgstr "" #: tpl/toolbox/settings-debug.tpl.php:98 msgid "Your IP" msgstr "" #: tpl/toolbox/settings-debug.tpl.php:104 msgid "More information about the available commands can be found here." msgstr "" #: tpl/toolbox/settings-debug.tpl.php:119 msgid "Advanced level will log more details." msgstr "" #: tpl/toolbox/settings-debug.tpl.php:130 msgid "MB" msgstr "" #: tpl/toolbox/settings-debug.tpl.php:132 msgid "Specify the maximum size of the log file." msgstr "" #: tpl/toolbox/settings-debug.tpl.php:147 msgid "Shorten query strings in the debug log to improve readability." msgstr "" #: tpl/toolbox/settings-debug.tpl.php:160 msgid "Only log listed pages." msgstr "" #: tpl/toolbox/settings-debug.tpl.php:174 msgid "Prevent any debug log of listed pages." msgstr "" #: tpl/toolbox/settings-debug.tpl.php:188 msgid "Prevent writing log entries that include listed strings." msgstr "" LICENSE000064400000104515152075713260005567 0ustar00 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . changelog.txt000064400000405261152075713260007254 0ustar00 = 5.6 - Aug 1 2023 = * 🌱**Page Optimize** New JS Delay Includes option. (Mitchell Krog/Gerard Reches/Ignacy Hołoga) * **Crawler** Sitemap can use search for URL now. * **GUI** Restrict the scope of balloon CSS rules to avoid conflicts. (#567) * **Object Cache** Detect Memcached in more situations. (#568) * **API** Support `litespeed_purged_front` hook. (Umberto Fiorelli) = 5.5.1 - Jul 19 2023 = * 🐞**Image Optimization** Fixed a bug where WebP replacements couldn't be pulled without optimizing the original images. * 🐞**Image Optimization** Invalid images will now be removed when sending requests to the server. (#138993) * **Cloud** Added support for error codes `unpulled_images` and `blocklisted`. (Tynan) = 5.5 - Jun 20 2023 = * 🌱**Crawler** Can now use multiple sitemaps. (Tobolo/Tim Nolte) * 🌱**Crawler** Now runs asynchronously when manually invoked. * 🌱**Crawler** Now runs asynchronously when invoked from cron. * 🐞**Crawler** Fixed the realtime status bug when crawling. * **Crawler** Summary page now displays server load. (Ruikai) * 🐞**Page Optimize** Fixed an issue where UCSS could not be generated for error pages. (james58899) #556 * 🌱**Image Optimize** Now pulls images asynchronously. * **Image Optimize** Now prevents concurrent requests via a locking mechanism. * **Image Optimize** The process can now bypass invalid image records and continue. * 🐞**Image Optimize** Fixed an issue where images ready for optimization might have to wait for new images to be added before sending the request. * **Cloud** Replaced dashboard links with login/link to my.quic.cloud actions. * **GUI** Added indicators to show when certain options are passively enabled by Guest Mode. * **Htaccess** Added a noabort rule to support asynchronous crawling. * **Htaccess** The "Do Not Cache User Agents" option is now case-insensitive. (Ellen Dabo) * **General** The "Server IP" option now allows IPv4 format only. (Ruikai) * **Misc** Every page's closing HTML comments now displays UCSS/CCSS status. * **Object** Fixed a warning for null get_post_type_object. * **Object** Object_Cache::delete now always returns a boolean value. * **Cache** Fixed advanced-cache.php file warnings for WordPress versions less than 5.3. * **Debug** Added debug logging to record the plugin's total processing time. * **API** HTML minification can now be bypassed via the litespeed_html_min filter. = 5.4 - Apr 19 2023 = * **Image Optimize** Refactored DB storage for this feature. * **Image Optimize** Reduced DB table size. * **Image Optimize** Existing `img_optm` DB tables will have their data gradually transitioned to the new storage format with this update. Once an `img_optm` table is empty, it won't be used anymore. * **Page Optimize** Enabled WebP support for Googlebot User Agent. = 5.3.3 - Feb 22 2023 = * **Page Optimize** Excluded Jetpack stats JS. * **DB Optimize** Fixed DB Optm SQL for revision postmeta. * **Cache** Fixed an undefined array key warning. * **Purge** Prevented undefined array key warning when widgets are disabled. * **Object** Fixed dynamic property deprecation warnings. * **Admin** Safely redirect to homepage if referer is unknown. * **Activation** Check that item slug exists first. * **Cache** Prevented cache header to send globally if header part already closed. * **CSS** Improved string handling for CSS minifier. * **Debug** Fixed undefined array key warnings. * **Misc** Fixed implicit conversion in random string generation function `Str::rrand`. = 5.3.2 - Jan 10 2023 = * **Object** Fixed object cache lib incr, decr functions (thanks bdrbros/DANIEL) #516 * **Database Optimize** Database optimizer now handles postmeta when cleaning revisions #515 * **Cache** Made nocache the default for 4xx/5xx response codes. * **Cache** Default cache TTL settings removed for 403 response code, changed to 10 mins for 500 response code. * **GUI** Added a description for the redetect nodes function. * **GUI** Added a description for the refresh button sync function. = 5.3.1 - Dec 12 2022 = * **CLI** Presets feature is now usable from the CLI. (xLukii) * **CLI** Added 'import_remote' for litespeed-option to enable importing options from URLs. (xLukii) * **Cache** Added LiteSpeed headers to site health check for full page cache. * **Crawler* Fixed unauthorized crawler toggle operation. (#CVE-2022-46800) * **UCSS** Fixed a bug where items weren't added back to the UCSS queue after purging. * **Page Optimize** Fixed a bug where generated CSS would return 404 after upgrading via CLI. * **3rd** Fixed a bug where a WooCommerce session doesn't exist when checking cart, notices (Jason Levy/Gilles) * **GUI** Made LiteSpeed admin notice icon grayscale to avoid distraction. (martinsauter) * **GUI** Fixed RTL style for notification icon. * **API** Added a new hook `litespeed_optm_uri_exc` to exclude URI from page optimization. * **API** Excluded `.well-known` path from page optimization. = 5.3 - Oct 31 2022 = * 🌱**Presets** New `Presets` feature and menu item. * 🌱**UCSS** New option `UCSS File Excludes and Inline` to increase page score. (Ankit) * **UCSS** When UCSS is purged, automatically append URL to UCSS generation queue. (Ankit) * **Page Optimize** Removed a redundant `defer` attribute from Lazy Load image library usage. (#928019) * **Image Optimize** Dropped `Create WebP Versions` setting. Will automatically enable when `Image WebP Replacement` is activated. * **Cloud** Fixed a bug where internal updates were delayed for API keys. * **Cloud** Improved auto alias feature by waiting for second request from alias domain validation before removing a pending alias. * **Purge** Automatically Purge All when plugin auto update is done. * **Purge** Fixed a potential PHP8 error that occurred when removing unused widgets. (acsnaterse) * **Cache** Fixed an infinite 301 redirection caused by UTM-encoded link. * **CLI** Added syntax examples for values that include line breaks (xLukii) * **CLI** Purge requests will now be included with the original request to avoid potential CSS/JS 404 issues. * **ESI** Check all user roles for cache vary and page optimization excludes. * **GUI** Added a LiteSpeed icon to admin message banners to indicate the banners are from our plugin. (Michael D) * **Crawler** Fixed a cache-miss issue that occurred when Guest Mode was ON and WebP Replacement was OFF. * **3rd** Remove WooCommerce private cache. * **3rd** Removed LiteSpeed metabox from ACF field group edit page. (keepmovingdk) = 5.2.1 - Sep 7 2022 = * 🐞**Core** Fixed a fatal error that occurred when uninstalling. (#894556 Hostinger) * **Dash** Show partner info on the dashboard for partner-tier QC accounts. * **UCSS** Auto-purge UCSS on post update. (Ankit) * 🕸️**Crawler** Respect the `LITESPEED_CRAWLER_DISABLE_BLOCKLIST` constant for unexpected results too. (Abe) = 5.2 - Aug 17 2022 = * 🌱**UCSS** Added UCSS message queue to improve service quality and reliability * 🐞**VPI** Fixed conflict w/ image lazyload; used HTML before image lazyload to avoid invalid `data:base64` results. * **VPI** Changed VPI Cron default setting to OFF. * **VPI** Automatically resend requests when VPI result contains invalid `data:` image value. * **Conf** Fixed an issue with URI Excludes, where paths using both ^ and $ were not correctly excluded (Eric/Abe) * **Conf** Auto corrected `WP_CONTENT_URL` protocol if it was explicitly set to `http://`. * **Cloud** No longer sync the configuration to QUIC.cloud if configuration is unchanged. * **Cloud** Appended home_url value into synced configuration data for wp-content folder path correction. * 🕸️**Crawler** Improved compatibility with server `open_basedir` PHP setting limit when detecting load before crawling. (Tom Robak/mmieszalski) = 5.1 - Aug 1 2022 = * 🌱**Toolbox** Debug log can now show Purge/Crawler logs as well. (Tynan) * **UCSS** Prepared for future message queue. * **UCSS** Moved UCSS class to its own file. * **3rd** Added 3rd-party support for WC PDF Product Vouchers. (Tynan) * **Core** Fixed potential PHP warning when saving summary data. (Sarah Richardson) * **Purge** Purge can now clear the summary correctly. (Kevin) * **VPI** Added `queue_k` to API notification. = 5.0.1 - Jul 27 2022 = * 🐞**Cloud** Fixed a potential PHP error that could occur with the cloud service summary. (Bruno Cantuaria) * **3rd** Added Autoptimize back to compatibility list. = 5.0.0.1 - Jul 26 2022 = * 🔥🐞**Cloud** Fixed an issue with the cloud request timestamp update which causes a usage sync failure. (great thanks to Kevin) = 5.0 - Jul 25 2022 = * 🌱**VPI** Added Viewport Images feature to LiteSpeed Options metabox on Post Edit page. * 🌱**CDN** Added Auto CDN Setup feature for simple QUIC.cloud CDN setup. (Kevin) * 🌱**Page Optimize** Automatically cache remote CSS/JS files when fetching for optimization (Lauren) * 🌱**Cache** Added LiteSpeed Options for page-level cache control on Post Edit page. (denisgomesfranco) * 🌱**Cloud** Auto Alias feature. * 🌱**Debug** Added `Debug String Excludes` option. (Hanna) * 🌱**UCSS** Added `Purge this page - UCSS` option to Admin Bar dropdown menu. (Ankit) * 🌱**Guest** Added `litespeed_guest_off=1` URL query string parameter to bypass Guest Mode. (cbdfactum) * 🐞**Page Optimize** Fixed an issue where CSS anchors could be wrongly converted to a full path when minifying. (Tynan) * **Page Optimize** Bypass CCSS/UCSS generation when a self-crawled CSS resource returns a 404 code. (Abe) * **Object** Allow `LSCWP_OBJECT_CACHE` predefined to turn off Object Cache. (knutsp) * **Data** Fixed an issue where empty version tags in the database repeatedly toggled the upgrade banner and reset settings to default. * **Purge** Fixed an issue where the site's index page could be purged upon deletion of an unviewable post. (Kevin) * **Toolbox** Added `View site before optimization` button under `Debug` tab. (Ryan D) * **Admin** Switch to using the `DONOTCACHEPAGE` constant to indicated WP-Admin pages are not cacheable. * **Admin** Moved no-cache header to very beginning to avoid caching unexpected exits. * **Cloud** Added message queue service for VPI. (Abe) * **Cloud** Bypassed 503 error nodes from node redetection process. (Abe) * **Cloud** Fixed a failure to detect `out_of_quota`. (Lauren) * **Cloud** Added ability to display dismissable banners generated by QUIC.cloud. * 🕸️**Crawler** Added realtime load detection before crawl. * 🕸️**Crawler** Adjusted crawler behavior for Divi pages to allow for Divi's CCSS generation process. (miketemby) * 🕸️**API** PHP constant `LITESPEED_CRAWLER_DISABLE_BLOCKLIST` and filter `litespeed_crawler_disable_blocklist` to disable blocklist. (Tobolo) * **CDN** Automatically add a trailing slash to `CDN URL` and `Original URLs` if user didn't provide one. (Lucas) * **Cache** When a URL redirects to a URL with a query string, consider these as different for caching purposes. (Shivam) * **Media** Added ability to disable lazyload from the LiteSpeed Options metabox on Post Edit page. * **Media** Added new default values to `WebP Attribute to Replace` setting for WPBakery and Slider Revolution. (JibsouX) * **Image Optimize** Dropped redundant `Page Speed` user agent when serving WebP images. (serpentdriver) * **GUI** Fixed an issue where manually dismissable admin messages were instead being treated as one-time messages. (Tynan Beatty) * **GUI** Fixed an issue where subsequent admin alerts would overwrite existing alerts in the queue. (Kevin/Tynan) * **GUI** Updated time offset in log. (Ruikai #PR444 #PR445) * **GUI** Added `litespeed_media_ignore_remote_missing_sizes` API description. * **CCSS** Fixed an issue where CCSS was unexpectedly bypassed if `CSS Combine` was OFF and `UCSS Inline` was ON. (Ruikai) * **Debug** Added response headers to debug log. (Kevin) = 4.6 - Mar 29 2022 = * **Page Optimize** Improved compatibility for JS Delay. * 🐞**Page Optimize** Fixed an issue for network subsites that occurred when only CSS/JS Minify are enabled. * **Localization** Added query string compatibility for Resource URLs. * **Vary** Fixed a potential PHP warning when server variable `REQUEST_METHOD` is not detected. * **Cache** Guest Mode now respects Cache Excludes settings. * **GUI** Added warning notice when enabling `Localize Resources` feature; each localized JS resource requires thorough testing! * **GUI** Fixed a PHP Deprecated warning that occurred with the Mobile Cache User Agent setting on PHP v8.1+. (jrmora) * **Conf** Removed Google related scripts from default `Localization Files` value. * **Media** WordPress core Lazy Load feature is now automatically disabled when LiteSpeed Lazy Load Images option is enabled. (VR51 #Issue440) * 🐞**API** Filter `litespeed_ucss_per_pagetype` for UCSS now also applies to CSS Combine to avoid UCSS failure. (Ankit) * **API** Added a filter `litespeed_media_ignore_remote_missing_sizes` to disable auto detection for remote images that are missing dimensions. (Lucas) = 4.5.0.1 - Feb 24 2022 = * 🔥🐞**Media** Fixed an issue where lazy-loaded images would disappear when using custom CSS image loading effects. = 4.5 - Feb 23 2022 = * 🌱**Page Optimize** Localization is back. * **Guest** Fixed organic traffic issue as different browsers may fail to set `document.referrer`. * **Image Optimize** Improved wp_postmeta table compatibility when gathering images. (Thanks to Thomas Stroemme) * 🐞**Page Optimize** Fixed a potential CSS/JS 404 issue for existing records that have been marked as expired. * **ESI** `LITESPEED_ESI_OFF` now affects `litespeed_esi_url` API filter too. * **Guest** Added a check to determine if Guest Mode is blocked by a third-party, and display warning if it is (Ruikai) * **Guest** To support WP sites with multiple domains, Guest Mode detection URL no longer uses domain. * **Report** Network now shows Toolbox page when having a large number of subsites. * **DB Optimize** Reduced default subsites count from 10 to 3 under Network Admin -> DB Optimize page to avoid timeout. * **Cloud** Fixed potential `lack_of_token` error when requesting domain key for cases where local summary value was not historically included in the array. * **Cloud** Fixed a PHP fatal error that occurred when encountering a frequency issue under CLI. (Dean Taylor #Issue410) * **Avatar** Force gravatar cache refresh in browsers and on CDN (rafaucau #PR430) * **API** New filter `litespeed_purge_ucss` to purge a single page UCSS. (#376681) * **API** New filter `litespeed_ucss_per_pagetype` for UCSS per page type generation. (Ankit) * **GUI** Replaced some GUI text and settings with more inclusive language (kebbet #PR437 #PR435) * **3rd** Excluded `WP Statistics` from inline JS optimize. (Ryan D) * **3rd** Added API filter `litespeed_3rd_aelia_cookies` for Aelia CurrencySwitcher. * **Media** Updated image lazyload library to 17.5.0. = 4.4.7 - Jan 11 2022 = * **Page Optimize** Dropped `Inline Lazy Load Images Library` option. Now will always inline lazyload library. (Ankit) * **3rd** Prevented JavaScript files from being appended to Rank Math SEO sitemap. * **Purge** Dropped default stale purge when purging a post. * **Cloud** Dropped unused API calls. * **Cloud** Dropped redundant IP validation in API calls. = 4.4.6 - Dec 27 2022 = * **Guest** Restored `document.referrer` for organic traffic purposes when Guest Mode is enabled. (michelefns) * **Image Optimize** Fixed a potential PHP notice when uploading images in WP w/ PHP7.4+. (titsmaker) * **ESI** Fixed an issue where ESI settings were not updated on customized widgets(#422 Abe) * **3rd** Reverted ESI Adminbar change on Elementor front pages for backward compatibility (#423 Abe) * **3rd** Fixed an issue where disabling ESI potential caused a PHP warning when using `Perfmatters`. (Jeffrey Zhang) * **Misc** Check whether HTTP_REFERER is set or not before using it in Router class. (#425 Abe) = 4.4.5 - Dec 1 2021 = * **Data** Fixed potential PHP notice when generating CSS/JS optimized files w/ PHP v7.4+. (Sarah Richardson/silencedgd/slr1979) * **API** Added `LITESPEED_ESI_OFF` constant to disable ESI, when defined before the WP `init` hook. * **API** Added `LSCWP_DEBUG_PATH` constant to specify debug log path. (khanh-nt) * 🐞**GUI** Fixed an issue where admin messages were not displayed. (Daniel McD) * **CDN** Used WP remote function to communicate w/ Cloudflare per WP guidance. * **3rd** Added compatibility for Perfmatters plugin's script manager (#417 Abe) * **3rd** Added compatibility for Elementor's Editor button when ESI is on (#418 Abe) = 4.4.4 - Nov 23 2021 = * **Page Optimize** Delay deletion of outdated CSS/JS files for a default of 20 days to avoid 404 errors with cached search engine copies. * **Cache** When caching, no longer send a purge request for CSS/JS removal to avoid cache engine conflicts. * 🐞**Core** Optimized SQL queries while autoloading if expected options are missing; reduced by 7 and 3 queries on backend and frontend respectively. (#396425 Jackson) * **Page Optimize** Fixed a 404 issue that occurred when upgrading the plugin manually, with a package upload or through the plugin manager. (Tobolo/Małgorzata/Abe) * **API** Added `litespeed_ccss_url` and `litespeed_ucss_url` API to manipulate the request URL for CCSS and UCSS. * **REST** Fixed a potential warning when detecting cacheable status on REST call. (rafaucau) * **OLS** Fixed an issue where the `COOKIEHASH` constant was undefined when used with OpenLiteSpeed as an MU plugin or with network activation. * **3rd** Sanitized POST data for nextgengallery. * **Cloud** Sanitized GET data when linking to QUIC.cloud. (#591762 WPScan) = 4.4.3 - Oct 13 2021 = * 🐞**Media** Fixed an issue where WebP is served erroneously under Guest Mode on older versions of Safari. (hash73) * 🐞**Media** Reverted regex change to fix `Lazy Load Image Parent Class Name Excludes` failure. (thpstock) * **Purge** Disabled `Purge Delay` in the optimization process by default. * **Conf** Dropped `.htaccess Path Settings` options for security concern. (WP) * **Conf** Dropped `CSS HTTP/2 Push`/`JS HTTP/2 Push` options. (Kevin) * **Conf** Set `Guest Optimization` default to OFF. * **Conf** Set `CCSS Per URL` default to OFF to avoid consuming more quota than intended after upgrade to v4. (n111) * **Object** Fixed an issue with Object Cache warnings during upgrade, when Guest Mode is enabled. * ☁️**Cloud** Fixed an issue with PHP notices when inquiring about quota usage for a service not currently in use. * **GUI** Added GO detail warning. (n111) * **GUI** Moved "quota will be still in use" warning from Guest Mode to Guest Optimization section. * **API** Added `LITESPEED_CFG_HTACCESS` PHP Constant to specify .htaccess path. * **API** Added `litespeed_qs_forbidden` hook to bypass `?LSCWP_CTRL=` query string. (minhduc) * **API** Added `litespeed_delay_purge` hook to delay the following Purge header until the next request. * **API** Added `litespeed_wpconfig_readonly` hook to disable `WP_CACHE` constant update based on the wp-config.php file. (#633545) = 4.4.2 - Sep 23 2021 = * **Purge** In order to clear pages containing 404 CSS/JS, the purge header will always be sent even in cases where purge must be delayed. * 🐞**Purge** Fixed a potential PHP warning caused when generating different optimized filenames. * **Cron** Dropped unnecessary HTML response in cron which sometimes resulted in wp-cron report email. (Gilles) * **Page Optimize** Purge caused by CSS/JS file deletion will now be silent. * **Page Optimize** Fixed an issue where the homepage failed to purge when addressing the 404 CSS/JS issue. * **Avatar** Fixed potential localized Avatar folder creation warning. (mattk0220/josebab) * **API** Added filter `litespeed_optm_html_after_head` to move all optimized code(UCSS/CCSS/Combined CSS/Combined JS) to be right before the `` tag. (ducpl/Kris Regmi) * **Debug** Under debug mode, cache/purge tags will be plaintext. = 4.4.1 - Sep 16 2021 = * 🐞**ESI** Fixed ESI failure on non-cached pages caused by `DONOTCACHEPAGE` constant. * 🐞**Page Optimize** Fixed an issue where the minified CSS/JS file failed to update when the file was changed. (ceap80) * 🐞**Page Optimize** Fixed an issue where the combined CSS/JS file randomly returned a 404 error when visiting the same URL with different query strings. (Abe) * **API** Added `litespeed_const_DONOTCACHEPAGE` hook to control the cache-or-not result of the `DONOTCACHEPAGE` constant. = 4.4 - Sep 8 2021 = * 🌱**Crawler** Added the ability to enable or disable specific crawlers. (⭐ Contributed by Astrid Wang #PR390) * 🌱**UCSS** Added `UCSS Inline` option. (Ankit). * 🌱**UCSS** Added `UCSS URI Excludes` option. (RC Verma). * 🐞**Page Optimize** Fixed an issue where combined CSS/JS files would potentially return 404 errors after a Purge All. (Special thanks to Abe & Ruikai) * **Page Optimize** Minimized the potential for 404 errors by query string when Purging All. * **Page Optimize** Dropped redundant query strings for minified CSS/JS files. * **Conf** Ugrade configuration safely to avoid the issue of new functions not being found in old codebase. * **Conf** Configuration upgrade process now adds a notification to admin pages and disables configuration save until upgrade is complete. (Lisa) * **JS** Fixed an issue where JS Defer caused a `litespeed_var_1_ is not defined` error when enabled w/ ESI options. (Tobolo) * 🐞**JS** Fixed an issue where `JS Delay` doesn't work for combined JS when `JS Combine` is enabled. (Special thanks to Joshua & Ankit) * **JS** `JS Delay` now will continue loading JS, even if there is an error in the current JS loading process. * 🐞**CCSS** If CCSS fails to generate, Load CSS Asynchronously will now be disabled. (Stars #54074166) * 🐞**UCSS** If UCSS generation fails the generated error will no longer be served inside the file. (Ryan D) * **Log** Updated the Debug log to use less code for prefix. * **3rd** Always respect `DONOTCACHEPAGE` constant definition to fix DIVI dynamic css calculation process. = 4.3 - Aug 16 2021 = * **UCSS** Separated UCSS Purge from CCSS Purge. (⭐ Contributed by Alice Tang #PR388) * 🐞**Cloud** Fixed an issue where CCSS/UCSS quota data failed to update locally. * **Cloud** Added server load as a factor when detecting node availability. * **Cloud** Improved the speed of checking daily quota and showing the related error message. * **Cloud** Added ability to re-detect node availability if the current node is responding w/ a heavy load code. * **Cloud** CCSS/UCSS/LQIP queue now exits immediately when quota is depleted. * **Cloud** Replaced separate `d/regionnodes` with a single `d/nodes` in the node list API for image optimization. * **LQIP** Fixed an issue with LQIP network compatibility. (⭐ Contributed by Alice Tang #PR387) * **GUEST** JS no longer preloads for Guest Optimization. (Ankit) * 🐞**Data** Fixed an issue where deleting the `cssjs` data folder causes a failure in the upgrade process. (Joshua #PR391) * **GUI** Fixed a potential dashboard PHP warning when no queue existed. (jrmora) * **GUI** Added daily quota on dashboard. * **GUI** Added downgrade warning to Toolbox -> Beta Test. * **GUI** Tuned `.litespeed-desc` class to full width in CSS. * **Conf** `Preserve EXIF/XMP data` now defaults to ON due to copyright concerns. (Tobolo) * 🐞**3rd** Fixed a PHP warning when using Google AMP w/ /amp as structure. (thanhstran98) = 4.2 - Jul 29 2021 = * **Cloud** Auto redirect to a new node if the current node is not available anymore. * **Cloud** Combined CCSS/UCSS to sub services of Page Optimization. * **Cloud** Added a daily quota rate limit to help mitigate the heavy service load at the beginning of the month. * **Cloud** Cached the node IP list in order to speed up security check. (Lucas) * 🐞**GUEST** Fixed an issue where Guest Mode remained enabled even when the UA setting is empty. (Stars) * **GUEST** Guest Mode will no longer cache POST requests. * **UCSS** Purging CSS/JS now purges the UCSS queue as well, to avoid failure when generating UCSS. * **UCSS** Separated service entry `UCSS` from `CCSS`. * **CCSS** Simplified `load_queue/save_queue/build_filepath_prefix` functions. (⭐ Contributed by Alice Tang #PR373) * **CCSS** If CCSS request fails, details are now saved in the CSS file. * **CCSS** Renamed CCSS ID in inline HTML from `litespeed-optm-css-rules` to `litespeed-ccss`. (Alice) * **Page Optimize** CCSS/UCSS now supports Cloud queue/notify for asynchronous generation. * **Page Optimize** Simplified CCSS/UCSS generation function. * **Page Optimize** Added the ability to cancel CCSS/UCSS Cloud requests. * **Page Optimize** Unnecessary quesry strings will now be dropped from CSS/JS combined files. * **Crawler** Reset position now resets crawler running status too. * **REST** Cloud request to REST will now detect whether an IP in in the Cloud IP list for security reasons. * **Object** Enhanced Object Cache compatibility for `CONF_FILE` constant detection. * **API** Added shorter alias `litespeed_tag` and other similar aliases for Cache Tag API. * **API** Renamed `LITESPEED_BYPASS_OPTM` to `LITESPEED_NO_OPTM` for Page Optimization. * **Toolbox** Dropped v3.6.4- versions in Beta Test as they will cause a fatal error in downgrade. * **GUI** Added shortcut links to each section on the Dashboard. * **GUI** Added UCSS whitelist usage description. (wyb) * **GUI** Showed the default recommended values for Guest Mode UA/IPs. * **3rd** Fixed AMP plugin compatibility. (⭐ Contributed by Alice Tang #PR368) * **3rd** Bypassed all page optimization including CDN/WebP for AMP pages. * **3rd** Improved compatibility with All in One SEO plugin sitemap. (arnaudbroes/flschaves #Issue372) * **3rd** Added wsform nonce. (#365 cstrouse) * **3rd** Added Easy Digital Download (EDD) & WP Menu Cart nonce. (#PR366 AkramiPro) * **3rd** Improved compatibility w/ Restrict Content Pro. (Abe #PR370) * **3rd** Improved compatibility w/ Gravity Forms. (Ruikai #371) = 4.1 - Jun 25 2021 = * 🌱**UCSS/CCSS/LQIP** Moved queue storage to file system from database wp-options table to lessen the IO load. (#633504) * 🌱**3rd** Added an option to disable ESI for the WooCommerce Cart. (#358 Anna Feng/Astrid Wang) * **ESI** Fixed an ESI nonce issue introduced in v4.0. (Andrew Choi) * **Object** Used new `.litespeed_conf.dat` instead of `.object-cache.ini` for object cache configuration storage. * **Conf** Now updating related files after plugin upgrade and not just after activation. * 🌱**Guest** Added a Guest Mode JS Excludes option. (Ankit/Mamac/Rcverma) * **Guest** Guest Mode now uses a lightweight script to update guest vary for reduced server load. * **Guest** Guest Mode now adds missing image dimensions. * **Guest** Guest vary will no longer update if there's already a vary in place to address the infinite loop caused by CloudFlare's incorrect cache control setting for PHP. * **Guest** Guest vary update request will no longer be sent if `lscache_vary` is already set. * **Guest** Added a Configurable Guest Mode UA/IP under the Tuning tab in the General menu. * **Guest** Guest Mode now allows cron to be hooked, even when UCSS/CCSS options are off. (#338437 Stars) * **Guest** Simplified the vary generation process under Guest Mode. * **Guest** Added a Guest Mode HTML comment for easier debugging. (Ruikai) * **Guest** Guest vary update ajax now bypasses potential POST cache. * **CCSS** Added back the options `Separate CCSS Cache Post Types` and `Separate CCSS Cache URIs`. (Joshua/Ankit) * **CCSS** CCSS/UCSS queue is now limited to a maximum of 500 entries. * **Control** The cache control constant `LSCACHE_NO_CACHE` will now have a higher priority than the Forced Public Cache setting. * **Crawler** The Crawler can now crawl Guest Mode pages. * **Crawler** Fixed a potential XSS vulnerability in the Crawler settings. (#927355) * **Crawler** The Crawler now supports a cookie value of `_null`. (Tobolo) * **Media** Updated the default value for the Responsive Placeholder SVG to be transparent. * **Media** WebP images in the background may now be served in Guest Mode. * **Media** WebP images in CSS may now be bypassed if the requesting Guest Mode client doesn't support WebP. * **Media** Fixed empty default image placeholder under Guest Mode. * 🐞**Image Optimize** Changed the missing `$_POST` to `$post_data` so the database status is properly updated. (#345 Lucas) * **Import** Export file is now readable to allow importing of partial configurations. (Ryan D/Joshua) * **Page Optimize** Fixed W3 validator errors in Guest Mode. (#61393817) * **3rd** A fatal WooCommerce error is no longer triggered by a custom theme reusing a previous LSCWP cache detection tag. * **3rd** AMP may now bypass Guest Mode automatically. * **Localize** Dropped the `Localize Resources` option as Guest Mode is a sufficient replacement. (Note: Due to user feedback during the development period, we have decided to reinstate this option in a future version.) * **Cloud** Changed the WP API url. * **Lang** Corrected a missing language folder. * **GUI** Added a CCSS/UCSS loading page visualization. (⭐ Contributed by Astrid Wang & Anna Feng #PR360) * **GUI** Added a warning to indicate when Guest Mode CCSS/UCSS quota is in use. (Contributed by Astrid Wang & Anna Feng #PR361) * **GUI** Added a `litespeed-info` text color. (Astrid Wang) * **GUI** Implemented various UI/UX improvements. (Joshua/Lisa) * **GUI** Duplicate cloud service messages with the same content will only display once now. (Marc Dahl) * **GUI** Added a WebP replacement warning for Guest Mode Optimization if WebP replacement is off. * **Misc** Dropped `wp_assets` from distribution to reduce the package size. (lowwebtech) * **Misc** Increased the new version and score detection intervals. * **Misc** Optimized WP Assets images. (#352 lowwebtech) * **Debug** Dropped the redundant error_log debug info. = 4.0 - Apr 30 2021 = * 🌱🌱🌱**Guest** Introduced `Guest Mode` for instantly cacheable content on the first visit. * 🌱**UCSS** Added a new service: `Unique CSS`, to drop unused CSS from elements from combined CSS * 🌱**CCSS** Added `HTML Lazyload` option. (Ankit) * 🌱**CCSS** Added `CCSS Per URL` option to allow Critical CSS to be generated for each page instead of for each Post Type. * 🌱**Media** Added `Add Missing Sizes` setting for improving Cumulative Layout Shift. (Fahim) * 🌱**JS** Switched to new JS minification library for better compression and compatibility w/ template literals. (LuminSol) * **Media** WebP may now be replaced in CSS. * **Media** Can now drop image tags in noscript to avoid lazyload. (Abe #314 /mattthomas-photography) * **Media** Bypass optimization if a page is not cacheable. * **Image Optimize** Auto hook to `wp_update_attachment_metadata` to automate image gathering process, and to handle the new thumbnail generation after images are uploaded. (smerriman). * **Image Optimize** Repeated image thumbnails won't be gathered anymore. * **Image Optimize** Simplified the rescan/gather/upload_hook for existing image detection. * **Image Optimize** Fixed the duplicated optimize size records in the postmeta table. (Abe #315) * **Image Optimize** Allow either JSON POST request or normal form request in `notify_img`. (Lucas #313) * **Image Optimize** Optimized SQL query for better efficiency. (lucas/Lauren) * **Image Optimize** Fixed issue where rescan mass created duplicate images. (#954399) * **Image Optimize** Image optimization pie will not show 100% anymore if there is still a small amount in the unfinished queue. * **Image Optimize** WebP generation defaults to ON for Guest Mode. * **Image Optimize** `Priority Line` package now can have smaller request interval. * **ESI** Disable ESI when page is not cacheable. (titsmaker) * **ESI** Fixed an issue where Divi was disabling all in edit mode, but couldn't disable ESI. (Abe) * **ESI** ESI init moved under `init` hook from `plugin_loaded` hook. * **CDN** Add basic support for CloudFlare API Tokens (Abe #320) * **CSS** Simplified `Font Display Optimization` option. * **CSS** Fixed manual cron timeout issue. (jesse Distad) * **CSS** Inline CSS may now use `data-no-optimize` to be excluded from optimization. (popaionut) * **JS** Combined `Load JS Defer` and `Load Inline JS Defer` options. * **JS** Forced async to defer. * **JS** Moved Google Analytics JS from constant default to setting default for removal. * **JS** Fixed potential JS parsing issue caused by JS src being changed to data-src by other plugins. (ankit) * **JS** Excluded spotlight from JS optimize. (tobolo) * **CCSS** Fixed CCSS/UCSS manual cron timeout issue. * **CCSS** Only 10 items will be kept for CCSS history. * **CCSS** The appearance of CCSS Purge in the topbar menu will be determined by the existence of CCSS cache, and not the setting only. * **CCSS** To avoid stuck queues when the current request keeps failing, the CCSS queue will always drop once requested. * **CCSS** CCSS will no longer hide adminbar. * **CCSS** CCSS may now be separate for network subsites. (Joshua) * **CCSS** Gave CCSS a unique filename per URL per user role per subsite. * **CCSS** Dropped `Separate CCSS Cache Post Types` option. * **CCSS** Dropped `Separate CCSS Cache URIs` option. * **CCSS** Subsites purge Avatar/CSS/JS/CCSS will not affect the whole network anymore. * **CCSS** Implemented a better queue list for CCSS that auto collapses if there are more than 20 entries, and shows the total on top. * **CSSJS** Now using separate CSS and JS folders instead of `cssjs`. * **CSSJS** Automatically purge cache after CCSS is generated. * **Network** Dropped network CSS/JS rewrite rules. * **Cache** Send cache tag header whenever adding a tag to make it effective in the page optimization process. * **Core** Used hook for buffer optimization; Used `init()` instead of `constructor`. * **Object** Used `cls` instead of `get_instance` for init. * **Cloud** Replaced one-time message with a dismissible-only message when the domain key has been automatically cleared due to domain/key dismatch. * **API** Dropped function `hook_vary_add()`. * **API** Dropped function `vary_add()`. * **API** Dropped function `filter_vary_cookies()`. * **API** Dropped function `hook_vary()`. * **API** Dropped action `litespeed_vary_add`. * **API** Dropped filter `litespeed_api_vary`. * **API** Use `litespeed_vary_curr_cookies` and `litespeed_vary_cookies` for Vary cookie operations instead. * **API** Dropped action `litespeed_vary_append`. * **Vary** 3rd party vary cookies will not append into .htaccess anymore but only present in response vary header if in use. * **Vary** Dropped function `append()`. * **Vary** Commenter cookie is now considered cacheable. * **Crawler** Minor update to crawler user agent to accommodate mobile_detect.php (Abe #304) * **Data** Added a table truncate function. * **Data** Added new tables url & url_file. * **Data** Dropped cssjs table. * **Data** Options/Summary data is now stored in JSON format to speed up backend visit. (#233250) * **Data** Default `CSS Combine External and Inline` and `JS Combine External and Inline` to On for new installations for better compatibility. * **Purge** Fixed potential purge warning for certain themes. * **Purge** Purge will be stored for next valid visit to trigger if it is initially generated by CLI. * **Page Optimize** `CSS Combine`/`JS Combine` will now share the same file if the contents are the same. Limited disk usage for better file usage and fewer issues with random string problems. * **Page Optimize** Dropped option CSS/JS Cache TTL. * **Page Optimize** Bypass optimization if page not cacheable. * **Page Optimize** Purge CSS/JS will purge the `url_file` table too. * **Page Optimize** Optionally store a vary with a shorter value. * **Page Optimize** Removing query strings will no longer affect external assets. (ankit) * **Page Optimize** Better regex for optimization parsing. * **Page Optimize** Eliminated w3 validator for DNS prefetch and duplicated ID errors. (sumit Pandey) * **Page Optimize** New Optimization for Guest Only option under Tuning. * **Page Optimize** Now forbidding external link redirection for localization. * **Debug** Implemented a better debug format for the 2nd parameter in the log. * **GUI** Bypass page score banner when score is not detected (both 0). (ankit) * **GUI** Fixed deprecated JQuery function warning in WP-Admin. (krzxsiek) = 3.6.4 - Mar 15 2021 = * **Toolbox** Fixed Beta Test upgrade error when upgrading to v3.7+. = 3.6.3 - Mar 10 2021 = * **Core** Fixed potential upgrade failure when new versions have changes in activation related functions. * **Core** Upgrade process won't get deactivated anymore on Network setup. = 3.6.2 - Feb 1 2021 = * **Page Optimize** Fixed an issue where network purge CSS/JS caused 404 errors for subsites. * **Page Optimize** Fixed an issue where purge CSS/JS only caused 404 errors. * **Page Optimize** Added a notice for CSS/JS data detection and potential random string issue. * **Page Optimize** Limited localization resources to specified .js only. (closte #292/ormonk) * **JS** Data src may now be bypassed from JS Combine. (ankit) * **CLI** Fixed a message typo in Purge. (flixwatchsupport) * **Browser** Added font/otf to Browser Cache expire list. (ruikai) * **Data** Updated data files to accept PR from dev branch only. * **3rd** Add data-view-breakpoint-pointer to js_excludes.txt for the Events Calendar plugin. (therealgilles) * **Cloud** Bypassed invalid requests. * **Doc** CDN Mapping description improvement. (mihai A.) = 3.6.1 - Dec 21 2020 = * **WP** Tested up to WP v5.6. * **WebP** Reverted WebP support on Safari Big Sur and Safari v14.0.1+ due to an inability to detect MacOS versions from UA. (@antomal) * **CDN** Dropped the option `Load JQuery Remotely`. * **CDN** Fixed CDN URL replacement issue in optimized CSS files. (@ankit) * **CDN** Fixed an issue where CDN CLI wouldn't set mapping image/CSS/JS to OFF when `false` was the value. * **CDN** Started using React for CDN Mapping settings. * **GUI** Secured Server IP setting from potential XSS issues. (@WonTae Jang) * **Toolbox** Supported both dev and master branches for Beta Test. Latest version updated to v3.6.1. * **Purge** Purge Pages now can purge non-archive pages too. * **Admin** Simplified the admin JS. * **Admin** Limited crawler-related react JS to crawler page only. = 3.6 - Dec 14 2020 = * 🌱**WebP** Added WebP support on Safari Big Sur or Safari v14.0.1+. (@ruikai) * 🐞**Config** Fixed an issue where new installations were not getting the correct default .htaccess content. * **Crawler** Will auto bypass empty sub-sitemap instead of throwing an exception. (@nanoprobes @Tobolo) * **Crawler** Now using React for Cookie Simulation settings instead of Vue.js. Dropped Vue.js. * **Crawler** Dropped `Sitemap Generation` (will only use 3rd party sitemap for crawler). * **CSS** Added `CSS Combine External and Inline` option for backward compatibility. (@lisa) * **Object** Forbid .object-cache.ini visits. (@Tarik) * **Page Optimize** Dropped `Remove Comments` option to avoid combine error. * **CSS** Added a predefined CSS exclude file `data/css_excludes.txt`. * **CSS** Excluded Flatsome theme random inline CSS from combine. * **CSS** Excluded WoodMart theme from combine. (@moemauphie) * **Page Optimize** Excluded tagDiv.com Newspaper theme dynamic CSS/JS from CSS/JS Combine. * **CSS** Added predefined JS defer excludes list. (@Shivam) * **JS** `data-no-defer` option now supports inline JS. (@rafaucau) * **Media** Lazyload inline library is now bypassed by JS Combine. * **Admin** Fixed WP-Admin console ID duplicate warnings. * **Cloud** Dropped QUIC.cloud sync options that have long been unused. * **CSS** Dropped `Unique CSS File` option (UCSS will always generate unique file, will use whitelist to group post type to one CSS). * **GUI** Dropped Help tab. * **Toolbox** Added 3.5.2 to version list. = 3.5.2 - Oct 27 2020 = * **CSS** `CSS Combine` is now compatible w/ inline noscript CSS. (@galbaras) * **GUI** Added ability to manually dismiss the JS option reset message in v3.5.1 upgrade process. (#473917) * 🐞**CSS** `CSS Excludes` setting will no longer lose items beginning w/ `#`. (@ankit) * **API** New `litespeed_media_reset` API function for image editing purposes. (@Andro) = 3.5.1 - Oct 20 2020 = * **JS** Inline JS containing nonces can now be combined. * **JS** Reset JS Combine/Defer to OFF when upgrading to avoid breaking sites. * **JS** Added new option JS Combine External and Inline to allow backwards compatibility. * **JS** Added Inline JS Defer option back. (@ankit) * **Page Optimize** Dropped Inline JS Minify option and merged the feature into JS Minify. * **JS** Pre-added jQuery to the default JS excludes/defer list for better layout compatibility for new users. * **JS** Excluded Stripe/PayPal/Google Map from JS optimization. (@FPCSJames) * **JS** Allowed excluded JS to still be HTTP2 pushed. (@joshua) * **CCSS** Critical CSS now can avoid network pollution from other sites. (@ankit) * **Toolbox** Beta Test now displays recent public versions so it is easier to revert to an older version * **Vary** Server environment variable Vary can now be passed to original server from QUIC.cloud for non-LiteSpeed servers. * **ESI** Improved backward compatibility for ESI nonce list. (@zach E) * 🐞**Misc** Fixed failure of upgrade button on plugin news banner and made cosmetic improvements. * **Doc** Added note that LSCWP works with ClassicPress. = 3.5.0.2 - Sep 30 2020 = * This is a temporary revert fix. Code is SAME as v3.4.2. = 3.5.0.1 - Sep 29 2020 = * 🔥🐞**CSS** Fixed print media query issue when having CSS Combine. (@paddy-duncan) = 3.5 - Sep 29 2020 = * **Page Optimize** Refactored CSS/JS optimization. * **Page Optimize** CSS and JS Combine now each save to a single file without memory usage issues. * **CSS** Inline CSS Minify is now a part of CSS Minify, and will respect the original priorities. (thanks to @galbaras) * **JS** JS Combine now generates a single JS file in the footer. (Special thanks to @ankit) * **JS** JS Combine now combines external JS files, too. (Thanks to @ankit) * **JS** JS Deferred Excludes now uses the original path/filename as keywords instead of the minified path/filename, when JS Minify is enabled. * **JS** JS Combine now combines inline JS, too. * **JS** JS Excludes may now be used for inline JS snippet. * **Page Optimize** Inline CSS Minify and Max Combined File Size retired due to changes listed above. * **CSS** Combined CSS Priority retired due to changes listed above. * **JS** Exclude JQuery, Combined JS Priority, Load Inline JS Deferred, and Inline JS Deferred Excludes retired due to changes listed above. * **JS** Predefined data file data/js_excludes.txt now available for JS Excludes. * **ESI** Predefined data file data/esi.nonces.txt now available for ESI Nonces. * **ESI** Remote Fetch ESI Nonces functionality retired. * **API** Added support for new litespeed_esi_nonces filter. * **Object** Object Cache will not try to reconnect after failure to connect in a single process. * **CCSS** Remote read CSS will add the scheme if it is missing from the URL. * **CCSS** CSS will no longer be prepared for a URL if 404 result is detected. * **CCSS** Fixed most failures caused by third party CSS syntax errors. * **CCSS** Remote read CSS will fix the scheme if the URL doesn't have it. * **CCSS** Excluded 404 when preparing CSS before request. * **CCSS** Adjusted CCSS timeout from 180 seconds to 30 seconds. * **Image Optimize** Fixed the delete attachment database error that occurred when not using the image optimization service yet. * **Media** Added iOS 14 WebP support. * **Data** Fixed database creation failure for MySQL v8. * **Cloud** Error code err_key will clear the domain key in order to avoid duplicate invalid requests. * **Network** Fixed issue with object cache password file storage that occurred when resaving the settings. (#302358) * **Misc** Fixed IP detect compatibility w/ Apache. * **GUI** Fixed the description for Do Not Cache Categories. * **Preload** Upgraded Instant Click to a new stable preload library. (@stasonua0) = 3.4.2 - Sep 8 2020 = * **CCSS** Corrected the issue that wrongly appended non-CSS files to CSS in links before sending request. * **3rd** YITH wishlist now sends a combined single sub request for all widgets contained in one page. (LSWS v5.4.9 build 3+ required) * **ESI** Added support for ESI combine feature. * **GUI** Dropped banner notification for missing domain key when domain key is not initialized. * **Log** When QC whitelist check fails, a detailed failure log is now appended. = 3.4.1 - Sep 2 2020 = * 🐞**CCSS** Fixed an issue where dynamically generated CSS failed with `TypeError: Cannot read property type of undefined`. * 🐞**Page Optimize** Fixed CSS optimization compatibility for CSS dynamically generated with PHP. * **Page Optimize** Added the ability to defer JS even when the resource is excluded from other JS optimizations. (@slr1979) * **ESI** Added support for ESI last parameter inline value. * **3rd** YITH Wishlist, when cached for the first time, will no longer send sub requests. = 3.4 - Aug 26 2020 = * 🌱**LQIP** New setting **LQIP Excludes**. * 🌱**LQIP** Added a Clear LQIP Queue button. * 🌱**CCSS** Added a Clear CCSS Queue button. * **CCSS** Fixed an issue which wrongly included preloaded images in CCSS. (@pixtweaks) * **Network** Primary site and subsite settings now display correctly. * **Page Optimize** Noscript tags generated by LSCWP will only be dropped when the corresponding option is enabled. (@ankit) * **DB Optimize** Fixed database optimizer conflicts w/ object cache transient setting. (#752931) * **3rd** Fixed an issue with WooCommerce product purge when order is placed. * **3rd** Improved WooCommerce product comment compatibility with **WooCommerce Photo Reviews Premium** plugin when using ESI. * **CDN** Fixed Remote jQuery compatibility with WordPress v5.5. (@pixtweaks) * **API** New API `litespeed_purge_all_object` and `litespeed_purged_all_object` action hooks. = 3.3.1 - Aug 12 2020 = * 🌱**Page Optimize** New option to Remove Noscript Tags. (@phuc88bmt) * 🐞**LQIP** Fixed a critical bug that bypassed all requests in v3.3. * **LQIP** Requests are now bypassed if domain has no credit left. * **Page Optimize** Inline defer will be bypassed if document listener is detected in the code. (@ssurfer) * **CCSS** Print-only styles will no longer be included in Critical CSS. * **API** Added hooks to Purge action to handle file deletions. (@biati) * **Cloud** Plain permalinks are no longer required for use of cloud services. * **Data** Added an access denial to work with OpenLiteSpeed. (@spenweb #PR228) * **GUI** Spelling and grammar adjustments. (@blastoise186 #PR253) = 3.3 - Aug 6 2020 = * 🌱**Page Optimize** Added a new setting, Inline JS Deferred Excludes. (@ankit) * **Page Optimize** CSS/JS Combine/Minify file versions will be differentiated by query string hash instead of new filename to reduce DB/file system storage. * **Page Optimize** Added the ability to use local copies of external JS files for better control over page score impacts. * **Page Optimize** Improved combination of CSS media queries. (@galbaras) * **Page Optimize** Reprioritized Inline JS Defer to be optimized before encoding, for a significantly smaller result. * **LQIP** Detect if the file exists before sending LQIP request to QUIC.cloud. * **CCSS** Sped up CCSS process significantly by sending HTML and CSS in request. * **CCSS** Improvements to mobile CSS support in CCSS. * **CCSS** Minimize CCSS failures by attempting to automatically fix CSS syntax errors. * **Cloud** Domain Key will be deleted after QUIC.cloud site_not_registered error to avoid endless repeated requests. * **CDN** CDN Original URL will default to WP Site URL if not set. (@ruikai) * **CLI** Global output format `--format=json/yaml/dump` and `--json` support in CLI. (@alya1992) * **CDN** Improved handling of non-image CSS `url()` sources in CDN. (@daniel McD) * 🐞**CDN** Fixed CDN replacement conflict w/ JS/CSS Optimize. (@ankit) * **Crawler** Only reset Crawler waiting queues when crawling begins. (@ruikai) * **Network** Network Enable Cache is no longer reset to ON Use Network Settings in enabled. (@RavanH) * 🐞**Activation** Fixed a PHP warning that appeared during uninstall. (@RavanH) * **Debug** Automatically omit long strings when dumping an array to debug log. * **Report** Subsites report now shows overwritten values along w/ original values. (#52593959) * **REST** Improved WP5.5 REST compatibility. (@oldrup) * **GUI** Server IP setting moved from Crawler menu to General menu. * **GUI** Localize resources moved to Localization tab. * **Config** News option now defaults to ON. = 3.2.4 - Jul 8 2020 = * **Object** New installations no longer get custom data.ini reset, as this could cause lost configuration. (@Eric) * **ESI** Now using `svar` to load nonces more quickly. (@Lauren) * **ESI** Fixed the conflicts between nonces in inline JS and ESI Nonces when Inline JS Deferred is enabled. (@JesseDistad) * 🐞**ESI** Fixed Fetch Latest Predefined Nonce button. * 🐞**Cache** Fixed an issue where mobile visits were not being cached when Cache Mobile was disabled. * **CDN** Bypass CDN constant `LITESPEED_BYPASS_CDN` now will apply to all CDN replacements. * **Router** Dropped `Router::get_uid()` function. * **Crawler** Updated role simulator function for future UCSS usage. * **GUI** Textarea will now automatically adjust the height based on the number of rows input. * **CLI** Fixed an issue that caused WP-Cron to exit when a task errored out. (@DovidLevine @MatthewJohnson) * **Cloud** No longer communcate with QUIC.cloud when Domain Key is not set and Debug is enabled. * **Cloud** Score banner no longer automatically fetches a new score. (@LucasRolff) = 3.2.3.2 - Jun 19 2020 = * 🔥🐞**Page Optimize** Hotfix for CSS/JS minify/combine. (@jdelgadoesteban @martin_bailey) = 3.2.3.1 - Jun 18 2020 = * **API** New filter `litespeed_buffer_before` and `litespeed_buffer_after`. (#PR243 @joejordanbrown) = 3.2.3 - Jun 18 2020 = * 🌱**Page Optimize** Added Unique CSS option for future removal of unused CSS per page. (@moongear) * **Page Optimize** Fixed an issue where Font Optimization could fail when having Load JS Deferred and Load Inline JS Deferred. (#PR241 @joejordanbrown) * 🐞**Page Optimize** Fixed an issue with Font Display Optimization which caused Google Fonts to load incorrectly. (#PR240 @joejordanbrown @haidan) * 🐞**Network** Use Primary Site Configuration setting for network sites now works properly with Object Cache and Browser Cache. (#56175101) * **API** Added filter `litespeed_is_from_cloud` to detect if the current request is from QC or not. (@lechon) * **ESI** ESI Nonce now can fetch latest list with one click. * **GUI** Updated remaining documentation links & some minor UI tweaks. (@Joshua Reynolds) = 3.2.2 - Jun 10 2020 = * 🌱**Purge** Scheduled Purge URLs now supports wildcard. (#427338) * 🌱**ESI** ESI Nonce supports wildcard match now. * **Network** Use Primary Site Settings now can support Domain Key, and override mechanism improved. (@alican532 #96266273) * **Cloud** Debug mode will now have no interval limit for most cloud requests. (@ruikai) * **Conf** Default Purge Stale to OFF. * **GUI** Purge Stale renamed to Serve Stale. * **Data** Predefined nonce list located in `/litespeed-cache/data/esi.nonce.txt`. Pull requests welcome. * **Debug** Limited parameter log length. * 🐞**CDN** Fixed an issue where upgrading lost value of CDN switch setting. (#888668) * **3rd** Caldera Forms ESI Nonce enhancement. (@paconarud16 @marketingsweet) * **3rd** Elementor now purges correctly after post/page updates. * **3rd** Disabled Page Optimization features on AMP to avoid webfont JS inject. (@rahulgupta1985) = 3.2.1 - Jun 1 2020 = * **Cloud** LQIP/CCSS rate limit tweaks. (@ianpegg) * **Admin** Improved frontend Admin Bar menu functionality. (#708642) * **Crawler** Fixed an issue where cleaning up a crawler map with a leftover page number would cause a MySQL error. (@saowp) * **Image Optimize** Added WP default thumbnails to image optimization summary list. (@johnny Nguyen) * **REST** Improved REST compatibility w/ WP4.4-. (#767203) * **GUI** Moved Use Primary Site Configuration to General menu. (@joshua) = 3.2 - May 27 2020 = * **Image Optimize** Major improvements in queue management, scalability, and speed. (@LucasRolff) * **Cloud** Implemented a series of communication enhancements. (@Lucas Rolff) * **Crawler** Enhanced PHP 5.3 compatibility. (@JTS-FIN #230) * **Page Optimize** Appended image template in wpDiscuz script into default lazyload image exclude list. (@philipfaster @szmigieldesign) * **Page Optimize** Eliminated the 404 issue for CSS/JS in server environments with missing SCRIPT_URI. (@ankit) * **Data** ENhanced summary data storage typecasting. = 3.1 - May 20 2020 = * 🌱**Network** Added Debug settings to network level when on network. * 🐞**Purge** Network now can purge all. * 🐞**Network** Fixed issue where saving the network primary site settings failed. * **Network** Moved Beta Test to network level when on network. * 🐞**Cache** Fixed issue in admin where new post editor was wrongly cached for non-admin roles. (@TEKFused) * 🐞**Data** Fixed issue with crawler & img_optm table creation failure. (@berdini @piercand) * 🐞**Core** Improved plugin activation compatibility on Windows 10 #224 (@greenphp) * **Core** Improved compatibility for .htaccess path search. * **Object** Catch RedisException. (@elparts) * Fixed Script URI issue in 3.0.9 #223 (@aonsyed) * **Image Optimize** Show thumbnail size set list in image optimization summary. (@Johnny Nguyen) * **Debug** Parameters will now be logged. = 3.0.9 - May 13 2020 = * **Purge** Comment cache can be successfully purged now. * **Data** Better MySQL charset support for crawler/image optimize table creation. (@Roshan Jonah) * **API** New hook to fire after Purge All. (@salvatorefresta) * **Crawler** Resolve IP for crawler. * **Task** PHP5.3 Cron compatibility fix. * **3rd** Elementor edit mode compatibility. * **Page Optimize** Fixed an issue where Purge Stale returned 404 for next visitor on CSS/JS. * **Page Optimize** Fixed the PHP warning when srcset doesn't have size info inside. (@gvidano) * **Cloud** Fixed the potential PHP warning when applying for the domain key. * **Core** PHP __DIR__ const replacement. (@MathiasReker) = 3.0.8.6 - May 4 2020 = * **CCSS** Bypassed CCSS functionality on frontend when domain key isn't setup yet. * **Cloud** Fixed WP node redetection bug when node expired. (@Joshua Reynolds) * **Crawler** Fixed an issue where URL is wrongly blacklisted when using ADC. = 3.0.8.5 - May 1 2020 = * 🔥🐞**3rd** Hotfix for WPLister critical error due to v3.0.8.4 changes. * **Image Optimize** Unfinished queue now will get more detailed info to indicate the proceeding status on node. * **CLI** Options can now use true/false as value for bool. (@gavin) * **CLI** Detect error if the ID does not exist when get/set an option value. * **Doc** An API comment typo for `litespeed_esi_load-` is fixed. = 3.0.8.4 - Apr 30 2020 = * 🌱**Crawler** New setting: Sitemap timeout. (#364607) * **Image Optimize** Images that fail to optimize are now counted to increase next request limit. * **Cloud** Redetect fastest node every 3 days. * **Cloud** Suppressed auto upgrade version detection error. (@marc Dahl) * **3rd** 3rd party namespace compatibility. (#366352) = 3.0.8.3 - Apr 28 2020 = * **Cloud** Better compatibility for the Link to QUIC.cloud operation. (@Ronei de Sousa Almeida) * **Image Optimize** Automatically clear invalid image sources before sending requests. (@Richard Hordern) = 3.0.8.2 - Apr 27 2020 = * **GUI** Corrected the Request Domain Key wording. = 3.0.8.1 - Apr 27 2020 = * **Object** Object cache compatibility for upgrade from v2.9.9- versions. = 3.0.8 - Apr 27 2020 = * Released v3 on WordPress officially. = 3.0.4 - Apr 23 2020 = * **Cloud** Apply Domain Key now receives error info in next apply action if failed to generate. * **GUI** Apply Domain Key timeout now displays troubleshooting guidance. * **REST** Added /ping and /token to REST GET for easier debug. * **Cache** Dropped `advanced-cache.php` file detection and usage. = 3.0.3 - Apr 21 2020 = * **Conf** Settings from all options (data ini, defined constant, and forced) will be filtered and cast to expected type. * **Upgrade** CDN mapping and other multiple line settings will now migrate correctly when upgrading from v2 to v3. = 3.0.2 - Apr 17 2020 = * **GUI** More guidance on domain key setting page. * **Cloud** Now Apply Domain Key will append the server IP if it exists in Crawler Server IP setting. = 3.0.1 - Apr 16 2020 = * **Data** Increased timeout for database upgrade related to version upgrade. Display a banner while update in progress. * **Page Optimize** All appended HTML attributes now will use double quotes to reduce the conflicts when the optimized resources are in JS snippets. = 3.0 - Apr 15 2020 = * 🌱**Media** LQIP (Low Quality Image Placeholder). * 🌱**Page Optimize** Load Inline JS Deferred Compatibility Mode. (Special thanks to @joe B - AppsON) * 🌱**Cloud** New QUIC.cloud API key setting. * 🌱**ESI** New ESI nonce setting. * 🌱**Media** JPG quality control. (@geckomist) * 🌱**Media** Responsive local SVG placeholder. * 🌱**Discussion** Gravatar warmup cron. * 🌱**DB** Table Engine Converter tool. (@johnny Nguyen) * 🌱**DB** Database summary: Autoload size. (@JohnnyNguyen) * 🌱**DB** Database summary: Autoload entries list. * 🌱**DB** Revisions older than. (@thememasterguru) * 🌱**Cache** Forced public cache setting. (#308207) * 🌱**Crawler** New timeout setting to avoid incorrect blacklist addition. (#900171) * 🌱**Htaccess** Frontend & backend .htaccess path customize. (@jon81) * 🌱**Toolbox** Detailed Heartbeat Control (@K9Heaven) * 🌱**Purge** Purge Stale setting. * 🌱**Page Optimize** Font display optimization. (@Joeee) * 🌱**Page Optimize** Google font URL display optimization. * 🌱**Page Optimize** Load Inline JS deferred. * 🌱**Page Optimize** Store gravatar locally. (@zzTaLaNo1zz @JohnnyNguyen) * 🌱**Page Optimize** DNS prefetch control setting. * 🌱**Page Optimize** Lazy Load Image Parent Class Name Excludes. (@pako69) * 🌱**Page Optimize** Lazy load iframe class excludes. (@vnnloser) * 🌱**Page Optimize** Lazy load exclude URIs. (@wordpress_fan1 @aminaz) * 🌱**GUI** New Dashboard and new menus. * 🌱**Image Optimize** Supported GIF WebP optimization. (@Lucas Rolff) * 🌱**Image Optimize** New workflow for image optimization (Gather first, request second). * 🌱**Image Optimize** The return of Rescan. * 🌱**CLI** Get single option cmd. * 🌱**CLI** QUIC.cloud cmd supported. * 🌱**CLI** CLI can send report now. * 🌱**Health** Page speed and page score now are in dashboard. * 🌱**Conf** Supported consts overwritten of `LITESPEED_CONF__` for all settings. (@menathor) * 🌱**REST** New REST TTL setting. (@thekendog) * 🌱**CDN** New setting `HTML Attribute To Replace`. CDN can now support any HTML attribute to be replaced. (@danushkaj91) * 🌱**Debug** Debug URI includes/excludes settings. * 🌱**Crawler** 🐞 Support for multiple domains in custom sitemap. (@alchem) * 🌱**Crawler** New Crawler dashboard. New sitemap w/ crawler status. New blacklist w/ reason. * 🌱**Media** LQIP minimum dimensions setting. (@Lukasz Szmigiel) * **Crawler** Able to add single rows to blacklist. * **Crawler** Crawler data now saved into database instead of creating new files. * **Crawler** Larger timeout to avoid wrongly added to blacklist. * **Crawler** Manually changed the priority of mobile and WebP. (@rafaucau) * **Browser** Larger Browser Cache TTL for Google Page Score improvement. (@max2348) * **Task** Task refactored. Disabled cron will not show in cron list anymore. * **Task** Speed up task load speed. * **ESI** Added Bloom nonce to ESI for Elegant Themes. * **Cloud** Able to redetect cloud nodes now. * **Img_optm** Fixed stale data in redirected links. * **Lazyload** CSS class `litespeed_lazyloaded` is now appended to HTML body after lazyload is finished. (@Adam Wilson) * **Cache** Default drop qs values. (@gijo Varghese) * **LQIP** Show all LQIP images in Media column. * **CDN** Can now support custom REST API prefix other than wp-json. (#174 @therealgilles) * **IAPI** Used REST for notify/destroy/check_img; Removed callback passive/aggreesive IAPI func * **CSSJS** Saved all static files to litespeed folder; Uninstallation will remove static cache folder too; Reduced .htaccess rules by serving CSS/JS directly. * **Object** Fixed override different ports issue. (@timofeycom #ISSUE178) * **Conf** DB Tables will now only create when activating/upgrading/changing settings. * **DB** Simplified table operation funcs. * **CSSJS** Bypassed CSS/JS generation to return 404 if file is empty (@grubyy) * **CSSJS** Inline JS defer will not conflict with JS inline optm anymore. * **CDN** settings will not be overwritten by primary settings in network anymore. (@rudi Khoury) * **OPcache** Purged all opcache when updating cache file. (@closte #170) * **CLI** CLI cmd renamed. * **CLI** Well-formatted table to show all options. * **Purge** Only purge related posts that have a status of "published" to avoid unnecessary "draft" purges. (@Jakub Knytl) * **GUI** Removed basic/adv mode for settings. Moved non-cache settings to its own menu. * **Htaccess** Protected .htaccess.bk file. Only kept one backup. (@teflonmann) * **Crawler** Crawler cookie now support `_null` as empty value. * **Crawler** Avoid crawler PHP fatal error on Windows OS. (@technisolutions) * **Admin** Simplified admin setting logic. * **Conf** Multi values settings now uniformed to multi lines for easier setting. * **Conf** New preset default data file `data/consts.default.ini`. * **Conf** Config setting renamed and uniformed. * **Conf** Dropped `Conf::option()`. Used `Conf::val()` instead. * **Conf** Improved conf initialization and upgrade conversion workflow. * **Core** Code base refactored. New namespace LiteSpeed. * **API** New API: iframe lazyload exclude filter. * **GUI** human readable seconds. (@MarkCanada) * **API** API refactored. * NOTE: All 3rd party plugins that are using previous APIs, especially `LiteSpeed_Cache_API`, need to be adjusted to the latest one. Same for ESI blocks.* ESI shortcode doesn't change. * **API** New hook `litespeed_update_confs` to settings update. * **API** New Hooks `litespeed_frontend_shortcut` and `litespeed_backend_shortcut` for dropdown menu. (@callaloo) * **API** Removed `litespeed_option_*` hooks. Use `litespeed_force_option` hook insteadly * **API** Renamed `litespeed_force_option` to `litespeed_conf_force`. * **API** Removed function `litespeed_purge_single_post`. * **REST** New rest API to fetch public IP. * **GUI** Hiding Cloudflare/Object Cache/Cloud API key credentials. (@menathor) * **GUI** Renamed all backend link tag from lscache to litespeed. * **GUI** fixed duplicated form tag. * **GUI** Fix cron doc link. (@arnab Mohapatra) * **GUI** Frontend adminbar menu added `Purge All` actions. (@Monarobase) * **GUI** Localized vue.js to avoid CloudFlare cookie. (@politicske) * **GUI** Always show optm column in Media Library for future single row optm operation. (@mikeyhash) * **GUI** Displayed TTL range below the corresponding setting. * **GUI** GUI refactored. * **Debug** Report can now append notes. * **3rd** Default added parallax-image to webp replacement for BB. * **3rd** User Switching plugin compatibility. (@robert Staddon) * **3rd** Beaver Builder plugin compatibility with v3.0. * **3rd** Avada plugin compatibility w/ BBPress. (@pimg) * **3rd** WooCommerce PayPal Checkout Gateway compatibility. (#960642 @Glen Cabusas) * **Network** Fixed potential timeout issue when containing a large volume of sites. (@alican532) * **Debug** `Disable All Features` now will see the warning banner if ON. * **Debug** Dropped `log filters` section. * **Debug** Debug and Tools sections combined into new `Toolbox` section. * 🐞**Crawler** Multi sites will now use separate sitemap even when `Use Primary Site` is ON. (@mrhuynhanh) * 🐞**Img_optm** Fixed large volume image table storage issue. (#328956) * 🐞 **Cloud** Cloud callback hash validation fixed OC conflict. (@pbpiotr) * 🎊 Any user that had the contribution to our WP community or changelog (even just bug report/feedback/suggestion) can apply for extra credits in QUIC.cloud. = 2.9.9.2 - Nov 24 2019 = * 🌱**GUI** New settings to limit News Feed to plugin page only. = 2.9.9.1 - Nov 18 2019 = * 🌱**Env** Environment Report can now append a passwordless link for support access without wp-admin password. * **Admin** The latest v3.0 beta test link may now be shown on the admin page when it's available. * **3rd** Compatibility with [DoLogin Security](https://wordpress.org/plugins/dologin/). * 🐞**ESI** Fixed a failure issue with Vary Group save. (@rafasshop) * 🐞**3rd** In browsers where WebP is not supported, Divi image picker will no longer serve WebP. (@Austin Tinius) = 2.9.9 - Oct 28 2019 = * Core: Preload all classes to avoid getting error for upcoming v3.0 upgrade. * Object: Improved compatibility with upcoming v3.0 release. * ESI: Unlocked ESI for OLS in case OLS is using QUIC.cloud CDN which supports ESI. * 3rd: Elementor Edit button will now show when ESI enabled. (#PR149 #335322 @maxgorky) * 🐞Media: Fixed missing Media optimization column when Admin role is excluded from optimization in settings. (@mikeyhash @pako69 @dgilfillan) = 2.9.8.7 - Oct 11 2019 = * 3rd: Enhanced WP stateless compatibility. (#PR143) * 3rd: Fixed a PHP warning caused by previous PR for AMP. (#PR176) = 2.9.8.6 - Sep 24 2019 = * 3rd: Bypassed page optimizations for AMP. (#359748 #PR169) * GUI: Firefox compatibility with radio button state when reloading pages. (#288940 #PR162) * GUI: Updated Slack invitation link. (#PR173) = 2.9.8.5 - Aug 21 2019 = * CCSS: Removed potential PHP notice when getting post_type. (@amcgiffert) * CDN: Bypassed CDN replacement on admin page when adding media to page/post. (@martin_bailey) * 🐞Media: Fixed inability to update or destroy postmeta data for child images. (#167713) = 2.9.8.4 - Jul 25 2019 = * Object: Increased compatibility with phpredis 5.0. * Object: Appended `wc_session_id` to default Do Not Cache Groups setting to avoid issue where WooCommerce cart items were missing when Object Cache is used. NOTE: Existing users must add `wc_session_id` manually! (#895333) * CSS: Added null onload handler for CSS async loading. (@joejordanbrown) * 🕷️: Increased crawler timeout to avoid wrongly adding a URL to the blacklist. * 3rd: WooCommerce Advanced Bulk Edit can now purge cache automatically. = 2.9.8.3 - Jul 9 2019 = * CSS: Enhanced the CSS Minify compatibility for CSS with missing closing bracket syntax errors. (@fa508210020) * 🕷️: Crawler now supports both cookie and no-cookie cases. (@tabare) * CCSS: Enhanced compatibility with requested pages where meta info size exceeds 8k. (@Joe B) * CCSS: No longer processing "font" or "import" directives as they are not considered critical. (@Ankit @Joe B) * IAPI: Removed IPv6 from all servers to avoid invalid firewall whitelist. = 2.9.8.2 - Jun 17 2019 = * 🔥🐞 3rd: Fixed PHP 5.3 compatibility issue with Facetwp. = 2.9.8.1 - Jun 17 2019 = * 3rd: Set ESI template hook priority to highest number to prevent ESI conflict with Enfold theme. (#289354) * 3rd: Improved Facetwp reset button compatibility with ESI. (@emilyel) * 3rd: Enabled user role change to fix duplicate login issue for plugins that use alternative login processes. (#114165 #717223 @sergiom87) * GUI: Wrapped static text with translate function. (@halilemreozen) = 2.9.8 - May 22 2019 = * Core: Refactored loading priority so user related functions & optimization features are set after user initialization. (#717223 #114165 #413338) * Media: Improved backup file calculation query to prevent out-of-memory issue. * Conf: Feed cache now defaults to ON. * API: Fully remote attachment compatibility API of image optimization now supported. * 🕷️: Bypassed vary change for crawler; crawler can now simulate default vary cookie. * ESI: Refactored ESI widget. Removed `widget_load_get_options()` function. * ESI: Changed the input name of widget fields in form. * 3rd: Elementor can now save ESI widget settings in frontend builder. * 3rd: WP-Stateless compatibility. * IAPI: Image optimization can now successfully finish the destroy process with large volume images with automatic continual mode. * 🐞CDN: Fixed issue with Load JQuery Remotely setting where WP 5.2.1 provided an unexpected jQuery version. * 🐞3rd: Login process now gets the correct role; fixed double login issue. = 2.9.7.2 - May 2 2019 = * Conf: Enhanced compatibility when an option is not properly initialized. * Conf: Prevent non-array instance in widget from causing 500 error. (#210407) * CCSS: Increase CCSS generation timeout to 60s. * Media: Renamed lazyload CSS class to avoid conflicts with other plugins. (@DynamoProd) * JS: Improved W3 validator. (@istanbulantik) * QUIC: Synced cache tag prefix for static files cache. * ESI: Restored query strings to ESI admin bar for accurate rendering. (#977284) * ESI: Tweaked ESI init priority to honor LITESPEED_DISABLE_ALL const. ESI will now init after plugin loaded. * 🐞ESI: No longer initialize ESI if ESI option is OFF. * API: New "Disable All" API function. * API: New "Force public cache" API function. * 🐞Vary: Fixed an issue with saving vary groups. * 🐞IAPI: Fixed an issue where image md5 validation failed due to whitespace in the image path. * 🐞3rd: Bypass all optimization/ESI/Cache features when entering Divi Theme Builder frontend editor. * 🐞3rd: Fixed an issue where DIVI admin bar exit button didn't work when ESI was ON. = 2.9.7.1 - Apr 9 2019 = * Purge: Purge All no longer includes Purge CCSS/Placeholder. * 3rd: Divi Theme Builder no longer experiences nonce expiration issues in the contact form widget. (#475461) = 2.9.7 - Apr 1 2019 = * 🌱🌱🌱 QUIC.cloud CDN feature. Now Apache/Nginx can use LiteSpeed cache freely. = 2.9.6 - Mar 27 2019 = * 🌱IAPI: Appended XMP to `Preserve EXIF data` setting. WebP will now honor this setting. (#902219) * Object: Fixed SASL connection with LSMCD. * ESI: Converted ESI URI parameters to JSON; Added ESI validation. * Import: Import/Export will now use JSON format. Please re-export any backed up settings. Previous backup format is no longer recognized. * Media: WebP replacement will honor `Role Excludes` setting now. (@mfazio26) * Data: Forbid direct visit to const.default.ini. * Utility: Can handle WHM passed in `LITESPEED_ERR` constant now. * IAPI: Communicate via JSON encoding. * IAPI: IAPI v2.9.6. = 2.9.5 - Mar 14 2019 = * 🌱 Auto convert default WordPress nonce to ESI to avoid expiration. * 🌱 API: Ability to easily convert custom nonce to ESI by registering `LiteSpeed_Cache_API::nonce_action`. * OPTM: Tweaked redundant attr `data-no-optimize` in func `_analyse_links` to `data-ignore-optimize` to offer the API to bypass optimization but still move src to top of source code. * API: Renamed default nonce ESI ID from `lscwp_nonce_esi` to `nonce`. * API: Added WebP generation & validation hook API. (@alim #wp-stateless) * API: Added hook to bypass vary commenter check. (#wpdiscuz) * Doc: Clarified Cache Mobile description. (@JohnnyNguyen) * Doc: Replaced incorrect link in description. (@JohnnyNguyen) * 3rd: Improved wpDiscuz compatibility. * 🐞3rd: Fixed Divi Theme Builder comment compatibility on non-builder pages. (#410919) * 3rd: Added YITH ESI adjustment. = 2.9.4.1 - Feb 28 2019 = * 🔥🐞Tag: Fixed issue where unnecessary warning potentially displayed after upgrade process when object cache is enabled. = 2.9.4 - Feb 27 2019 = * 🐞REST: New REST class with better WP5 Gutenberg and internal REST call support when ESI is embedded. * ESI: ESI block ID is now in plain text in ESI URL parameters. * 🐞ESI: Fixed a redundant ESI 301 redirect when comma is in ESI URL. * ESI: REST call can now parse shortcodes in ESI. * API: Changed ESI `parse_esi_param()` function to private and `load_esi_block` function to non-static. * API: Added `litespeed_is_json` hook for buffer JSON conversion. * GUI: Prepended plugin name to new version notification banner. * 3rd: WPML multi domains can now be handled in optimization without CDN tricks. = 2.9.3 - Feb 20 2019 = * ESI: ESI shortcodes can now be saved in Gutenberg editor. * ESI: ESI now honors the parent page JSON data type to avoid breaking REST calls (LSWS 5.3.6+). * ESI: Added is_json parameter support for admin_bar. * ESI: Simplified comment form code. * 3rd: Better page builder plugin compatibility within AJAX calls. * 3rd: Compatibility with FacetWP (LSWS 5.3.6+). * 3rd: Compatibility with Beaver Builder. * Debug: Added ESI buffer content to log. * Tag: Only append blog ID to cache tags when site is part of a network. * IAPI: Optimized database query for pulling images. * GUI: Added more plugin version checking for better feature compatibility. * GUI: Ability to bypass non-critical banners with the file .litespeed_no_banner. * Media: Background image WebP replacement now supports quotes around src. = 2.9.2 - Feb 5 2019 = * API: Add a hook `litespeed_esi_shortcode-*` for ESI shortcodes. * 3rd: WooCommerce can purge products now when variation stock is changed. * 🐞🕷️: Forced HTTP1.1 for crawler due to a CURL HTTP2 bug. = 2.9.1 - Jan 25 2019 = * Compatibility: Fixed fatal error for PHP 5.3. * Compatibility: Fixed PHP warning in htmlspecialchars when building URLs. (@souljahn2) * Media: Excluded invalid image src from lazyload. (@andrew55) * Optm: Improved URL compatibility when detecting closest cloud server. * ESI: Supported JSON format comment format in ESI with `is_json` parameter. * API: Added filters to CCSS/CSS/JS content. (@lhoucine) * 3rd: Improved comment compatibility with Elegant Divi Builder. * IAPI: New Europe Image Optimization server (EU5). Please whitelist the new [IAPI IP List](https://wp.api.litespeedtech.com/ips). * GUI: No longer show banners when `Disable All` in `Debug` is ON. (@rabbitwordpress) * GUI: Fixed button style for RTL languages. * GUI: Removed unnecessary translation in report. * GUI: Updated readme wiki links. * GUI: Fixed pie styles in image optimization page. = 2.9 - Dec 31 2018 = * 🌱Media: Lazy Load Image Classname Excludes. (@thinkmedia) * 🌱: New EU/AS cloud servers for faster image optimization handling. * 🌱: New EU/AS cloud servers for faster CCSS generation. * 🌱: New EU/AS cloud servers for faster responsive placeholder generation. * 🌱Conf: Ability to set single options via link. * 🌱Cache: Ability to add custom TTLs to Force Cache URIs. * Purge: Added post type to Purge tags. * Purge: Redefined CCSS page types. * Core: Using Exception for .htaccess R/W. * IAPI: New cloud servers added. Please whitelist the new [IAPI IP List](https://wp.api.litespeedtech.com/ips). * Optm: Trim BOM when detecting if the page is HTML. * GUI: Added PageSpeed Score comparison into promotion banner. * GUI: Refactored promotion banner logic. * GUI: Removed page optimized comment when ESI Silence is requested. * GUI: WHM transient changed to option instead of transient when storing. * GUI: Appending more descriptions to CDN filetype setting. * IAPI: Removed duplicate messages. * IAPI: Removed taken_failed/client_pull(duplicated) status. * Debug: Environment report no longer generates hash for validation. * 3rd: Non-cacheable pages no longer punch ESI holes for Divi compatibility. * 🐞Network: Added slashes for mobile rules when activating plugin. * 🐞CCSS: Eliminated a PHP notice when appending CCSS. = 2.8.1 - Dec 5 2018 = * 🐞🕷️: Fixed an activation warning related to cookie crawler. (@kacper3355 @rastel72) * 🐞Media: Replace safely by checking if pulled images is empty or not first. (@Monarobase) * 3rd: Shortcode ESI compatibility with Elementor. = 2.8 - Nov 30 2018 = * 🌱: ESI shortcodes. * 🌱: Mobile crawler. * 🌱: Cookie crawler. * API: Can now add `_litespeed_rm_qs=0` to bypass Remove Query Strings. * Optm: Removed error log when minify JS failed. * 🐞Core: Fixed a bug that caused network activation PHP warning. * Media: Removed canvas checking for WebP to support TOR. (@odeskumair) * Media: Eliminated potential image placeholder PHP warning. * 3rd: Bypassed Google recaptcha from Remove Query Strings for better compatibility. * IAPI: Showed destroy timeout details. * Debug: Moved Google Fonts log to advanced level. * GUI: Replaced all Learn More links for functions. * GUI: Cosmetic updates including Emoji. * 🕷️: Removed duplicated data in sitemap and blacklist. = 2.7.3 - Nov 26 2018 = * Optm: Improved page render speed with Web Font Loader JS library for Load Google Fonts Asynchronously. * Optm: Directly used JS library files in plugin folder instead of short links `/min/`. * Optm: Handled exceptions in JS optimization when meeting badly formatted JS. * 3rd: Added Adobe Lightroom support for NextGen Gallery. * 3rd: Improved Postman app support for POST JSON requests. * IAPI: US3 server IP changed to 68.183.60.185. = 2.7.2 - Nov 19 2018 = * 🌱: Auto Upgrade feature. * CDN: Bypass CDN for cron to avoid WP jQuery deregister warning. = 2.7.1 - Nov 15 2018 = * 🌱CLI: Ability to set CDN mapping by `set_option litespeed-cache-cdn_mapping[url][0] https://url`. * 🌱CDN: Ability to customize default CDN mapping data in default.ini. * 🌱API: Default.ini now supports both text-area items and on/off options. * Vary: Refactored Vary and related API. * Vary: New hook to manipulate vary cookies value. * Core: Activation now can generate Object Cache file. * Core: Unified Object Cache/rewrite rules generation process across activation/import/reset/CLI. * Core: Always hook activation to make activation available through the front end. * 🐞IAPI: Fixed a bug where environment report gave incorrect image optimization data. * 🐞OLS: Fixed a bug where login cookie kept showing a warning on OpenLiteSpeed. * 🐞Core: Fixed a bug where Import/Activation/CLI was missing CDN mapping settings. * API: Filters `litespeed_cache_media_lazy_img_excludes/litespeed_optm_js_defer_exc` passed-in parameter is changed from string to array. = 2.7 - Nov 2 2018 = * 🌱: Separate Purge log for better debugging. * 3rd: Now fully compatible with WPML. * IAPI: Sped up Image Optimization workflow. * GUI: Current IP now shows in Debug settings. * GUI: Space separated placeholder queue list for better look. * IAPI: EU3 server IP changed to 165.227.131.98. = 2.6.4.1 - Oct 25 2018 = * 🔥🐞Media: Fixed a bug where the wrong table was used in the Image Optimization process. * IAPI: IAPI v2.6.4.1. = 2.6.4 - Oct 24 2018 = * 🌱: Ability to create custom default config options per hosting company. * 🌱: Ability to generate mobile Critical CSS. * 🐞Media: Fixed a bug where Network sites could incorrectly override optimized images. * 🐞CDN: Fixed a bug where image URLs containing backslashes were matched. * Cache: Added default Mobile UA config setting. * GUI: Fixed unknown shortcut characters for non-English languages Setting tabs. = 2.6.3 - Oct 18 2018 = * 🌱: Ability to Reset All Options. * 🌱CLI: Added new `lscache-admin reset_options` command. * GUI: Added shortcuts for more of the Settings tabs. * Media: Updated Lazy Load JS library to the most recent version. * There is no longer any need to explicitly Save Settings upon Import. * Remove Query String now will remove *all* query strings in JS/CSS static files. * IAPI: Added summary info to debug log. = 2.6.2 - Oct 11 2018 = * Setting: Automatically correct invalid numeric values in configuration settings upon submit. * 🐞Media: Fixed the issue where iframe lazy load was broken by latest Chrome release. (@ofmarconi) * 🐞: Fixed an issue with Multisite where subsites failed to purge when only primary site has WooCommerce . (@kierancalv) = 2.6.1 - Oct 4 2018 = * 🌱: Ability to generate separate Critical CSS Cache for Post Types & URIs. * API: Filter `litespeed_frontend_htaccess` for frontend htaccess path. * Media: Removed responsive placeholder generation history to save space. = 2.6.0.1 - Sep 24 2018 = * 🔥🐞: Fixed an issue in responsive placeholder generation where redundant history data was being saved and using a lot of space. = 2.6 - Sep 22 2018 = * Vary: Moved `litespeed_cache_api_vary` hook outside of OLS condition for .htaccess generation. * CDN: Trim spaces in original URL of CDN setting. * API: New filter `litespeed_option_` to change all options dynamically. * API: New `LiteSpeed_Cache_API::force_option()` to change all options dynamically. * API: New `LiteSpeed_Cache_API::vary()` to set default vary directly for easier compaitiblity with WPML WooCommerce Multilingual. * API: New `LiteSpeed_Cache_API::nonce()` to safely and easily allow caching of wp-nonce. * API: New `LiteSpeed_Cache_API::hook_vary_add()` to add new vary. * Optm: Changed HTML/JS/CSS optimization options assignment position from constructor to `finalize()`. * Doc: Added nonce to FAQ and mentioned nonce in 3rd Party Compatibility section. * GUI: Moved inline minify to under html minify due to the dependency. * 3rd: Cached Aelia CurrencySwitcher by default. * 🐞: Fixed issue where enabling remote JQuery caused missing jquery-migrate library error. = 2.5.1 - Sep 11 2018 = * 🌱 Responsive placeholder. (@szmigieldesign) * Changed CSS::ccss_realpath function scope to private. * 🐞 Detected JS filetype before optimizing to avoid PHP source conflict. (@closte #50) = 2.5 - Sep 6 2018 = * [IMPROVEMENT] CLI can now execute Remove Original Image Backups. (@Shon) * [UPDATE] Fixed issue where WP-PostViews documentation contained extra slashes. (#545638) * [UPDATE] Check LITESPEED_SERVER_TYPE for more accurate LSCache Disabled messaging. * [IAPI] Fixed a bug where optimize/fetch error notification was not being received. (@LucasRolff) = 2.4.4 - Aug 31 2018 = * [NEW] CLI can now support image optimization. (@Shon) * [IMPROVEMENT] GUI Cron/CLI will not create admin message anymore. * [UPDATE] Media Fixed a PHP notice that appeared when pulling optimized images. * [UPDATE] Fixed a PHP notice when detecting origin of ajax call. (@iosoft) * [DEBUG] Debug log can now log referer URL. * [DEBUG] Changes to options will now be logged. = 2.4.3 - Aug 27 2018 = * [NEW] Media Ability to inline image lazyload JS library. (@Music47ell) * [IMPROVEMENT] Media Deleting images will now clear related optimization file & info too. * [IMPROVEMENT] Media Non-image postfix data will now be bypassed before sending image optimization request. * [BUGFIX] CDN CDN URL will no longer be replaced during admin ajax call. (@pankaj) * [BUGFIX] CLI WPCLI can now save options without incorrectly clearing textarea items. (@Shon) * [GUI] Moved Settings above Manage on the main menu. = 2.4.2 - Aug 21 2018 = * [IMPROVEMENT] Media Sped up Image Optimization process by replacing IAPI server pull communication. * [IMPROVEMENT] Media Ability to delete optimized WebP/original image by item in Media Library. (@redgoodapple) * [IMPROVEMENT] CSS Optimize Generate new optimized CSS name based on purge timestamp. Allows CSS cache to be cleared for visitors. (@bradbrownmagic) * [IMPROVEMENT] API added litespeed_img_optm_options_per_image. (@gintsg) * [UPDATE] Stopped showing "No Image Found" message when all images have finished optimization. (@knutsp) * [UPDATE] Improved a PHP warning when saving settings. (@sergialarconrecio) * [UPDATE] Changed backend adminbar icon default behavior from Purge All to Purge LSCache. * [UPDATE] Clearing CCSS cache will clear unfinished queue too. * [UPDATE] Added "$" exact match when adding URL by frontend adminbar dropdown menu, to avoid affecting any sub-URLs. * [UPDATE] Fixed IAPI error message showing array bug. (@thiomas) * [UPDATE] Debug Disable All will do a Purge All. * [UPDATE] Critical CSS server IP changed to 142.93.3.57. * [GUI] Showed plugin update link for IAPI version message. * [GUI] Bypassed null IAPI response message. * [GUI] Grouped related settings with indent. * [IAPI] Added 503 handler for IAPI response. * [IAPI] IAPI v2.4.2. * [IAPI] Center Server IP Changed from 34.198.229.186 to 142.93.112.87. = 2.4.1 - Jul 19 2018 = * [NEW FEATURE] Media Auto Level Up. Auto refill credit. * [NEW FEATURE] Media Auto delete original backups after pulled. (@borisov87 @JMCA2) * [NEW FEATURE] Media Auto request image optimization. (@ericsondr) * [IMPROVEMENT] Media Fetch 404 error will notify client as other errors. * [IMPROVEMENT] Media Support WebP for PageSpeed Insights. (@LucasRolff) * [BUGFIX] CLI Fixed the issue where CLI import/export caused certain textarea settings to be lost. (#767519) * [BUGFIX] CSS Optimize Fixed the issue that duplicated optimized CSS and caused rapid expansion of CSS cache folder. * [GUI] Media Refactored operation workflow and interface. * [UPDATE] Media Set timeout seconds to avoid pulling timeout. (@Jose) * [UPDATE] CDNFixed the notice when no path is in URL. (@sabitkamera) * [UPDATE] Media Auto correct credits when pulling. * [UPDATE] GUI Removed redundant double quote in gui.cls. (@DaveyJake) * [IAPI] IAPI v2.4.1. * [IAPI] Allow new error status notification and success message from IAPI. = 2.4 - Jul 2 2018 = * [NEW FEATURE] Media Added lossless optimization. * [NEW FEATURE] Media Added Request Original Images ON/OFF. * [NEW FEATURE] Media Added Request WebP ON/OFF. (@JMCA2) * [IMPROVEMENT] Media Improved optimization tools to archive maximum compression and score. * [IMPROVEMENT] Media Improved speed of image pull. * [IMPROVEMENT] Media Automatically recover credit after pulled. * [REFACTOR] Config Separated configure const class. * [BUGFIX] Report Report can be sent successfully with emoji now. (@music47ell) * [IAPI] New Europe Image Optimization server (EU3/EU4). * [IAPI] New America Image Optimization server (US3/US4/US5/US6). * [IAPI] New Asian Image Optimization server (AS3). * [IAPI] Refactored optimization process. * [IAPI] Increased credit limit. * [IAPI] Removed request interval limit. * [IAPI] IAPI v2.4. * We strongly recommended that you re-optimize your image library to get a better compression result. = 2.3.1 - Jun 18 2018 = * [IMPROVEMENT] New setting to disable Generate Critical CSS. (@cybmeta) * [IMPROVEMENT] Added filter to can_cdn/can_optm check. (@Jacob) * [UPDATE] *Critical CSS* Added 404 css. Limit cron interval. * [UPDATE] AJAX will not bypass CDN anymore by default. (@Jacob) * [GUI] Show Disable All Features warning if it is on in Debug tab. = 2.3 - Jun 13 2018 = * [NEW FEATURE] Automatically generate critical CSS. (@joeee @ivan_ivanov @3dseo) * [BUGFIX] "Mark this page as..." from dropdown menu will not reset settings anymore. (@cbratschi) = 2.2.7 - Jun 4 2018 = * [IMPROVEMENT] Improved redirection for manual image pull to avoid too many redirections warning. * [IAPI] Increased credit limit. * [BUGFIX] Fixed 503 error when enabling log filters in Debug tab. (#525206) * [UPDATE] Improve compatibility when using sitemap url on servers with allow_url_open off. * [UPDATE] Removed Crawler HTTP2 option due to causing no-cache blacklist issue for certain environments. * [UPDATE] Privacy policy can be now translated. (@Josemi) * [UPDATE] IAPI Increased default img request max to 3000. = 2.2.6 - May 24 2018 = * [NEW FEATURE] Original image backups can be removed now. (@borisov87 @JMCA2) * [BUGFIX] Role Excludes in Tuning tab can save now. (@pako69) * [UPDATE] Added privacy policy support. = 2.2.5 - May 14 2018 = * [IAPI] Image Optimization New Asian Image Optimization server (AS2). * [INTEGRATION] Removed wpForo 3rd party file. (@massimod) = 2.2.4 - May 7 2018 = * [IMPROVEMENT] Improved compatibility with themes using the same js_min library. (#129093 @Darren) * [BUGFIX] Fixed a bug when checking image path for dynamic files. (@miladk) * [INTEGRATION] Compatibility with Universal Star Rating. (@miladk) = 2.2.3 - Apr 27 2018 = * [NEW FEATURE] WebP For Extra srcset setting in Media tab. (@vengen) * [REFACTOR] Removed redundant LS consts. * [REFACTOR] Refactored adv_cache generation flow. * [BUGFIX] Fixed issue where inline JS minify exception caused a blank page. (@oomskaap @kenb1978) * [UPDATE] Changed HTTP/2 Crawl default value to OFF. * [UPDATE] Added img.data-src to default WebP replacement value for WooCommerce WebP support. * [UPDATE] Detached crawler from LSCache LITESPEED_ON status. * [API] Improved ESI API to honor the cache control in ESI wrapper. * [API] Added LITESPEED_PURGE_SILENT const to bypass the notification when purging * [INTEGRATION] Fixed issue with nonce expiration when using ESI API. (#923505 @Dan) * [INTEGRATION] Improved compatibility with Ninja Forms by bypassing non-javascript JS from inline JS minify. * [INTEGRATION] Added a hook for plugins that change the CSS/JS path e.g. Hide My WordPress. = 2.2.2 - Apr 16 2018 = * [NEW FEATURE] WebP Attribute To Replace setting in Media tab. (@vengen) * [IMPROVEMENT] Generate adv_cache file automatically when it is lost. * [IMPROVEMENT] Improved compatibility with ajax login. (@veganostomy) * [UPDATE] Added object cache lib check in case user downgrades LSCWP to non-object-cache versions. * [UPDATE] Avoided infinite loop when users enter invalid hook values in Purge All Hooks settings. * [UPDATE] Updated log format in media&cdn class. * [UPDATE] Added more items to Report. = 2.2.1 - Apr 10 2018 = * [NEW FEATURE] Included Directories setting in CDN tab. (@Dave) * [NEW FEATURE] Purge All Hooks setting in Advanced tab. * [UPDATE] Added background-image WebP replacement support. (@vengen) * [UPDATE] Show recommended values for textarea items in settings. * [UPDATE] Moved CSS/JS optimizer log to Advanced level. * [INTEGRATION] Added WebP support for Avada Fusion Sliders. (@vengen) = 2.2.0.2 - Apr 3 2018 = * [HOTFIX] Object Cache Fixed the PHP warning caused by previous improvement to Object Cache. = 2.2.0.1 - Apr 3 2018 = * [HOTFIX] Object parameter will no longer cause warnings to be logged for Purge and Cache classes. (@kelltech @khrifat) * [UPDATE] Removed duplicated del_file func from Object Cache class. * [BUGFIX] `CLI` no longer shows 400 error upon successful result. = 2.2 - Apr 2 2018 = * [NEW FEATURE] Debug Disable All Features setting in Debug tab. (@monarobase) * [NEW FEATURE] Cache Force Cacheable URIs setting in Excludes tab. * [NEW FEATURE] Purge Purge all LSCache and other caches in one link. * [REFACTOR] Purge Refactored Purge class. * [BUGFIX] Query strings in DoNotCacheURI setting now works. * [BUGFIX] Cache Mobile cache compatibility with WebP vary. (@Shivam #987121) * [UPDATE] Purge Moved purge_all to Purge class from core class. * [API] Set cacheable/Set force cacheable. (@Jacob) = 2.1.2 - Mar 28 2018 = * [NEW FEATURE] Image Optimization Clean Up Unfinished Data feature. * [IAPI] IAPI v2.1.2. * [IMPROVEMENT] CSS/JS Minify Reduced loading time significantly by improving CSS/JS minify loading process. (@kokers) * [IMPROVEMENT] CSS/JS Minify Cache empty JS Minify content. (@kokers) * [IMPROVEMENT] Cache Cache 301 redirect when scheme/host are same. * [BUGFIX] Media Lazy load now can support WebP. (@relle) * [UPDATE] CSS/JS Optimize Serve static files for CSS async & lazy load JS library. * [UPDATE] Report Appended Basic/Advanced View setting to Report. * [UPDATE] CSS/JS Minify Removed zero-width space from CSS/JS content. * [GUI] Added Purge CSS/JS Cache link in Admin. = 2.1.1.1 - Mar 21 2018 = * [BUGFIX] Fixed issue where activation failed to add rules to .htaccess. * [BUGFIX] Fixed issue where 304 header was blank on feed page refresh. = 2.1.1 - Mar 20 2018 = * [NEW FEATURE] Browser Cache Unlocked for non-LiteSpeed users. * [IMPROVEMENT] Image Optimization Fixed issue where images with bad postmeta value continued to show in not-yet-requested queue. = 2.1 - Mar 15 2018 = * [NEW FEATURE] Image Optimization Unlocked for non-LiteSpeed users. * [NEW FEATURE] Object Cache Unlocked for non-LiteSpeed users. * [NEW FEATURE] Crawler Unlocked for non-LiteSpeed users. * [NEW FEATURE] Database Cleaner and Optimizer Unlocked for non-LiteSpeed users. * [NEW FEATURE] Lazy Load Images Unlocked for non-LiteSpeed users. * [NEW FEATURE] CSS/JS/HTML Minify/Combine Optimize Unlocked for non-LiteSpeed users. * [IAPI] IAPI v2.0. * [IAPI] Increased max rows prefetch when client has additional credit. * [IMPROVEMENT] CDN Multiple domains may now be used. * [IMPROVEMENT] Report Added WP environment constants for better debugging. * [REFACTOR] Separated Cloudflare CDN class. * [BUGFIX] Image Optimization Fixed issue where certain MySQL version failed to create img_optm table. (@philippwidmer) * [BUGFIX] Image Optimization Fixed issue where callback validation failed when pulling and sending request simultaneously. * [GUI] Added Slack community banner. * [INTEGRATION] CDN compatibility with WPML multiple domains. (@egemensarica) = 2.0 - Mar 7 2018 = * [NEW FEATURE] Image Optimization Added level up guidance. * [REFACTOR] Image Optimization Refactored Image Optimization class. * [IAPI] Image Optimization New European Image Optimization server (EU2). * [IMPROVEMENT] Image Optimization Manual pull action continues pulling until complete. * [IMPROVEMENT] CDN Multiple CDNs can now be randomized for a single resource. * [IMPROVEMENT] Image Optimization Improved compatibility of long src images. * [IMPROVEMENT] Image Optimization Reduced runtime load. * [IMPROVEMENT] Image Optimization Avoid potential loss/reset of notified images status when pulling. * [IMPROVEMENT] Image Optimization Avoid duplicated optimization for multiple records in Media that have the same image source. * [IMPROVEMENT] Image Optimization Fixed issue where phantom images continued to show in not-yet-requested queue. * [BUGFIX] Core Improved compatibility when upgrading outside of WP Admin. (@jikatal @TylorB) * [BUGFIX] Crawler Improved HTTP/2 compatibility to avoid erroneous blacklisting. * [BUGFIX] Crawler Changing Delay setting will use server variable for min value validation if set. * [UPDATE] Crawler Added HTTP/2 protocol switch in the Crawler settings. * [UPDATE] Removed unnecessary translation strings. * [GUI] Display translated role group name string instead of English values. (@Richard Hordern) * [GUI] Added Join LiteSpeed Slack link. * [GUI] Import / Export Cosmetic changes to Import Settings file field. * [INTEGRATION] Improved compatibility with WPML Media for Image Optimization. (@szmigieldesign) = 1.9.1.1 - February 20 2018 = * [Hotfix] Removed empty crawler when no role simulation is set. = 1.9.1 - February 20 2018 = * [NEW FEATURE] Role Simulation crawler. * [NEW FEATURE] WebP multiple crawler. * [NEW FEATURE] HTTP/2 support for crawler. * [BUGFIX] Fixed a js bug with the auto complete mobile user agents field when cache mobile is turned on. * [BUGFIX] Fixed a constant undefined warning after activation. * [GUI] Sitemap generation settings are no longer hidden when using a custom sitemap. = 1.9 - February 12 2018 = * [NEW FEATURE] Inline CSS/JS Minify. * [IMPROVEMENT] Removed Composer vendor to thin the plugin folder. * [UPDATE] Tweaked H2 to H1 in Admin headings for accessibility. (@steverep) * [GUI] Added Mobile User Agents to basic view. * [GUI] Moved Object Cache & Browser Cache from Cache tab to Advanced tab. * [GUI] Moved LSCache Purge All from Adminbar to dropdown menu. = 1.8.3 - February 2 2018 = * [NEW FEATURE] Crawler server variable limitation support. * [IMPROVEMENT] Added Store Transients option to fix transients missing issue when Cache Wp-Admin setting is OFF. * [IMPROVEMENT] Tweaked ARIA support. (@steverep) * [IMPROVEMENT] Used strpos instead of strncmp for performance. (@Zach E) * [BUGFIX] Transient cache can now be removed when the Cache Wp-Admin setting is ON in Object Cache. * [BUGFIX] Network sites can now save Advanced settings. * [BUGFIX] Media list now shows in network sites. * [BUGFIX] Show Crawler Status button is working again. * [UPDATE] Fixed a couple of potential PHP notices in the Network cache tab and when no vary group is set. * [GUI] Added Learn More link to all setting pages. = 1.8.2 - January 29 2018 = * [NEW FEATURE] Instant Click in the Advanced tab. * [NEW FEATURE] Import/Export settings. * [NEW FEATURE] Opcode Cache support. * [NEW FEATURE] Basic/Advanced setting view. * [IMPROVEMENT] Added ARIA support in widget settings. * [BUGFIX] Multiple WordPress instances with same Object Cache address will no longer see shared data. * [BUGFIX] WebP Replacement may now be set at the Network level. * [BUGFIX] Object Cache file can now be removed at the Network level uninstall. = 1.8.1 - January 22 2018 = * [NEW FEATURE] Object Cache now supports Redis. * [IMPROVEMENT] Memcached Object Cache now supports authorization. * [IMPROVEMENT] A 500 error will no longer be encountered when turning on Object Cache without the proper PHP extension installed. * [BUGFIX] Object Cache settings can now be saved at the Network level. * [BUGFIX] Mu-plugin now supports Network setting. * [BUGFIX] Fixed admin bar showing inaccurate Edit Page link. * [UPDATE] Removed warning information when no Memcached server is available. = 1.8 - January 17 2018 = * [NEW FEATURE] Object Cache. * [REFACTOR] Refactored Log class. * [REFACTOR] Refactored LSCWP basic const initialization. * [BUGFIX] Fixed Cloudflare domain search breaking when saving more than 50 domains under a single account. * [UPDATE] Log filter settings are now their own item in the wp-option table. = 1.7.2 - January 5 2018 = * [NEW FEATURE] Cloudflare API support. * [IMPROVEMENT] IAPI key can now be reset to avoid issues when domain is changed. * [BUGFIX] Fixed JS optimizer breaking certain plugins JS. * [UPDATE] Added cdn settings to environment report. * [GUI] Added more shortcuts to backend adminbar. * [INTEGRATION] WooCommerce visitors are now served from public cache when cart is empty. = 1.7.1.1 - December 29 2017 = * [BUGFIX] Fixed an extra trailing underscore issue when saving multiple lines with DNS Prefetch. * [UPDATE] Cleaned up unused dependency vendor files. = 1.7.1 - December 28 2017 = * [NEW FEATURE] Added DNS Prefetch setting on the Optimize page. * [NEW FEATURE] Added Combined File Max Size setting on the Tuning page. * [IMPROVEMENT] Improved JS/CSS minify to achieve higher page scores. * [IMPROVEMENT] Optimized JS/CSS files will not be served from private cache for OLS or with ESI off. * [UPDATE] Fixed a potential warning for new installations on the Settings page. * [UPDATE] Fixed an issue with guest users occasionally receiving PHP warnings. * [BUGFIX] Fixed a bug with the Improve HTTPS Compatibility setting failing to save. * Thanks to all of our users for your encouragement and support! Happy New Year! * PS: Lookout 2018, we're back! = 1.7 - December 22 2017 = * [NEW FEATURE] Drop Query Strings setting in the Cache tab. * [NEW FEATURE] Multiple CDN Mapping in the CDN tab. * [IMPROVEMENT] Improve HTTP/HTTPS Compatibility setting in the Advanced tab. * [IMPROVEMENT] Keep JS/CSS original position in HTML when excluded in setting. * [IAPI] Reset client level credit after Image Optimization data is destroyed. * [REFACTOR] Refactored build_input/textarea functions in admin_display class. * [REFACTOR] Refactored CDN class. * [GUI] Added a notice to Image Optimization and Crawler to warn when cache is disabled. * [GUI] Improved image optimization indicator styles in Media Library List. = 1.6.7 - December 15 2017 = * [IAPI] Added ability to scan for new image thumbnail sizes and auto-resend image optimization requests. * [IAPI] Added ability to destroy all optimization data. * [IAPI] Updated IAPI to v1.6.7. * [INTEGRATION] Fixed certain 3rd party plugins calling REST without user nonce causing logged in users to be served as guest. = 1.6.6.1 - December 8 2017 = * [IAPI] Limit first-time submission to one image group for test-run purposes. * [BUGFIX] Fixed vary group generation issue associated with custom user role plugins. * [BUGFIX] Fixed WooCommerce issue where logged-in users were erroneously purged when ESI is off. * [BUGFIX] Fixed WooCommerce cache miss issue when ESI is off. = 1.6.6 - December 6 2017 = * [NEW FEATURE] Preserve EXIF in Media setting. * [NEW FEATURE] Clear log button in Debug Log Viewer. * [IAPI] Fixed notified images resetting to previous status when pulling. * [IAPI] Fixed HTTPS compatibility for image optimization initialization. * [IAPI] An error message is now displayed when image optimization request submission is bypassed due to a lack of credit. * [IAPI] IAPI v1.6.6. * [IMPROVEMENT] Support JS data-no-optimize attribute to bypass optimization. * [GUI] Added image group wiki link. * [INTEGRATION] Improved compatibility with Login With Ajax. * [INTEGRATION] Added function_exists check for WooCommerce to avoid 500 errors. = 1.6.5.1 - December 1 2017 = * [HOTFIX] Fixed warning message on Edit .htaccess page. = 1.6.5 - November 30 2017 = * [IAPI] Manually pull image optimization action button. * [IAPI] Automatic credit system for image optimization to bypass unfinished image optimization error. * [IAPI] Notify failed images from LiteSpeed's Image Server. * [IAPI] Reset/Clear failed images feature. * [IAPI] Redesigned report page. * [REFACTOR] Moved pull_img logic from admin_api to media. * [BUGFIX] Fixed a compatibility issue for clients who have allow_url_open setting off. * [BUGFIX] Fixed logged in users sometimes being served from guest cache. * [UPDATE] Environment report is no longer saved to a file. * [UPDATE] Removed crawler reset notification. * [GUI] Added more details on image optimization. * [GUI] Removed info page from admin menu. * [GUI] Moved environment report from network level to single site level. * [GUI] Crawler time added in a user friendly format. * [INTEGRATION] Improved compatibility with FacetWP json call. = 1.6.4 - November 22 2017 = * [NEW FEATURE] Send env reports privately with a new built-in report number referral system. * [IAPI] Increased request timeout to fix a cUrl 28 timeout issue. * [BUGFIX] Fixed a TTL max value validation bug. * [INTEGRATION] Improved Contact Form 7 REST call compatibility for logged in users. * Thanks for all your ratings. That encouraged us to be more diligent. Happy Thanksgiving. = 1.6.3 - November 17 2017 = * [NEW FEATURE] Only async Google Fonts setting. * [NEW FEATURE] Only create WebP images when optimizing setting. * [NEW FEATURE] Batch switch images to original/optimized versions in Image Optimization. * [NEW FEATURE] Browser Cache TTL setting. * [NEW FEATURE] Cache WooCommerce Cart setting. * [IMPROVEMENT] Moved optimized JS/CSS snippet in header html to after meta charset. * [IMPROVEMENT] Added a constant for better JS/CSS optimization compatibility for different dir WordPress installation. * [IAPI] Take over failed callback check instead of bypassing it. * [IAPI] Image optimization requests are now limited to 500 images per request. * [BUGFIX] Fixed a parsing failure bug not using attributes in html elements with dash. * [BUGFIX] Fixed a bug causing non-script code to move to the top of a page when not using combination. * [UPDATE] Added detailed logs for external link detection. * [UPDATE] Added new lines in footer comment to avoid Firefox crash when enabled HTML minify. * [API] `Purge private` / `Purge private all` / `Add private tag` functions. * [GUI] Redesigned image optimization operation links in Media Lib list. * [GUI] Tweaked wp-admin form save button position. * [GUI] Added "learn more" link for image optimization. = 1.6.2.1 - November 6 2017 = * [INTEGRATION] Improved compatibility with old WooCommerce versions to avoid unknown 500 errors. * [BUGFIX] Fixed WebP images sometimes being used in non-supported browsers. * [BUGFIX] Kept query strings for HTTP/2 push to avoid re-fetching pushed sources. * [BUGFIX] Excluded JS/CSS from HTTP/2 push when using CDN. * [GUI] Fixed a typo in Media list. * [GUI] Made more image optimization strings translatable. * [GUI] Updated Tuning description to include API documentation. = 1.6.2 - November 3 2017 = * [NEW FEATURE] Do Not Cache Roles. * [NEW FEATURE] Use WebP Images for supported browsers. * [NEW FEATURE] Disable Optimization Poll ON/OFF Switch in Media tab. * [NEW FEATURE] Revert image optimization per image in Media list. * [NEW FEATURE] Disable/Enable image WebP per image in Media list. * [IAPI] Limit optimized images fetching cron to a single process. * [IAPI] Updated IAPI to v1.6.2. * [IAPI] Fixed repeating image request issue by adding a failure status to local images. * [REFACTOR] Refactored login vary logic. = 1.6.1 - October 29 2017 = * [IAPI] Updated LiteSpeed Image Optimization Server API to v1.6.1. = 1.6 - October 27 2017 = * [NEW FEATURE] Image Optimization. * [NEW FEATURE] Role Excludes for Optimization. * [NEW FEATURE] Combined CSS/JS Priority. * [IMPROVEMENT] Bypass CDN for login/register page. * [UPDATE] Expanded ExpiresByType rules to include new font types. ( Thanks to JMCA2 ) * [UPDATE] Removed duplicated type param in admin action link. * [BUGFIX] Fixed CDN wrongly replacing img base64 and "fake" src in JS. * [BUGFIX] Fixed image lazy load replacing base64 src. * [BUGFIX] Fixed a typo in Optimize class exception. * [GUI] New Tuning tab in admin settings panel. * [REFACTOR] Simplified router by reducing actions and adding types. * [REFACTOR] Renamed `run()` to `finalize()` in buffer process. = 1.5 - October 17 2017 = * [NEW FEATURE] Exclude JQuery (to fix inline JS error when using JS Combine). * [NEW FEATURE] Load JQuery Remotely. * [NEW FEATURE] JS Deferred Excludes. * [NEW FEATURE] Lazy Load Images Excludes. * [NEW FEATURE] Lazy Load Image Placeholder. * [IMPROVEMENT] Improved Lazy Load size attribute for w3c validator. * [UPDATE] Added basic caching info and LSCWP version to HTML comment. * [UPDATE] Added debug log to HTML detection. * [BUGFIX] Fixed potential font CORS issue when using CDN. * [GUI] Added API docs to setting description. * [REFACTOR] Relocated all classes under includes with backwards compatibility. * [REFACTOR] Relocated admin templates. = 1.4 - October 11 2017 = * [NEW FEATURE] Lazy load images/iframes. * [NEW FEATURE] Clean CSS/JS optimizer data functionality in DB Optimizer panel. * [NEW FEATURE] Exclude certain URIs from optimizer. * [IMPROVEMENT] Improved optimizer HTML check compatibility to avoid conflicts with ESI functions. * [IMPROVEMENT] Added support for using ^ when matching the start of a path in matching settings. * [IMPROVEMENT] Added wildcard support in CDN original URL. * [IMPROVEMENT] Moved optimizer table initialization to admin setting panel with failure warning. * [UPDATE] Added a one-time welcome banner. * [UPDATE] Partly relocated class: 'api'. * [API] Added API wrapper for removing wrapped HTML output. * [INTEGRATION] Fixed WooCommerce conflict with optimizer. * [INTEGRATION] Private cache support for WooCommerce v3.2.0+. * [GUI] Added No Optimization menu to frontend. = 1.3.1.1 - October 6 2017 = * [BUGFIX] Improved optimizer table creating process in certain database charset to avoid css/js minify/combination failure. = 1.3.1 - October 5 2017 = * [NEW FEATURE] Remove WP Emoji Option. * [IMPROVEMENT] Separated optimizer data from wp_options to improve compatibility with backup plugins. * [IMPROVEMENT] Enhanced crawler cron hook to prevent de-scheduling in some cases. * [IMPROVEMENT] Enhanced Remove Query Strings to also remove Emoji query strings. * [IMPROVEMENT] Enhanced HTML detection when extra spaces are present at the beginning. * [UPDATE] Added private cache support for OLS. * [BUGFIX] Self-redirects are no longer cached. * [BUGFIX] Fixed css async lib warning when loading in HTTP/2 push. = 1.3 - October 1 2017 = * [NEW FEATURE] Added Browser Cache support. * [NEW FEATURE] Added Remove Query Strings support. * [NEW FEATURE] Added Remove Google Fonts support. * [NEW FEATURE] Added Load CSS Asynchronously support. * [NEW FEATURE] Added Load JS Deferred support. * [NEW FEATURE] Added Critical CSS Rules support. * [NEW FEATURE] Added Private Cached URIs support. * [NEW FEATURE] Added Do Not Cache Query Strings support. * [NEW FEATURE] Added frontend adminbar shortcuts ( Purge this page/Do Not Cache/Private cache ). * [IMPROVEMENT] Do Not Cache URIs now supports full URLs. * [IMPROVEMENT] Improved performance of Do Not Cache settings. * [IMPROVEMENT] Encrypted vary cookie. * [IMPROVEMENT] Enhanced HTML optimizer. * [IMPROVEMENT] Limited combined file size to avoid heavy memory usage. * [IMPROVEMENT] CDN supports custom upload folder for media files. * [API] Added purge single post API. * [API] Added version compare API. * [API] Enhanced ESI API for third party plugins. * [INTEGRATION] Compatibility with NextGEN Gallery v2.2.14. * [INTEGRATION] Compatibility with Caldera Forms v1.5.6.2+. * [BUGFIX] Fixed CDN&Minify compatibility with css url links. * [BUGFIX] Fixed .htaccess being regenerated despite there being no changes. * [BUGFIX] Fixed CDN path bug for subfolder WP instance. * [BUGFIX] Fixed crawler path bug for subfolder WP instance with different site url and home url. * [BUGFIX] Fixed a potential Optimizer generating redundant duplicated JS in HTML bug. * [GUI] Added a more easily accessed submit button in admin settings. * [GUI] Admin settings page cosmetic changes. * [GUI] Reorganized GUI css/img folder structure. * [REFACTOR] Refactored configuration init. * [REFACTOR] Refactored admin setting save. * [REFACTOR] Refactored .htaccess operator and rewrite rule generation. = 1.2.3.1 - September 20 2017 = * [UPDATE] Improved PHP5.3 compatibility. = 1.2.3 - September 20 2017 = * [NEW FEATURE] Added CDN support. * [IMPROVEMENT] Improved compatibility when upgrading by fixing a possible fatal error. * [IMPROVEMENT] Added support for custom wp-content paths. * [BUGFIX] Fixed non-primary network blogs not being able to minify. * [BUGFIX] Fixed HTML Minify preventing Facebook from being able to parse og tags. * [BUGFIX] Preview page is no longer cacheable. * [BUGFIX] Corrected log and crawler timezone to match set WP timezone. * [GUI] Revamp of plugin GUI. = 1.2.2 - September 15 2017 = * [NEW FEATURE] Added CSS/JS minification. * [NEW FEATURE] Added CSS/JS combining. * [NEW FEATURE] Added CSS/JS HTTP/2 server push. * [NEW FEATURE] Added HTML minification. * [NEW FEATURE] Added CSS/JS cache purge button in management. * [UPDATE] Improved debug log formatting. * [UPDATE] Fixed some description typos. = 1.2.1 - September 7 2017 = * [NEW FEATURE] Added Database Optimizer. * [NEW FEATURE] Added Tab switch shortcut. * [IMPROVEMENT] Added cache disabled check for management pages. * [IMPROVEMENT] Renamed .htaccess backup for security. * [BUGFIX] Fixed woocommerce default ESI setting bug. * [REFACTOR] Show ESI page for OLS with notice. * [REFACTOR] Management Purge GUI updated. = 1.2.0.1 - September 1 2017 = * [BUGFIX] Fixed a naming bug for network constant ON2. = 1.2.0 - September 1 2017 = * [NEW FEATURE] Added ESI support. * [NEW FEATURE] Added a private cache TTL setting. * [NEW FEATURE] Debug level can now be set to either 'Basic' or 'Advanced'. * [REFACTOR] Renamed const 'NOTSET' to 'ON2' in class config. = 1.1.6 - August 23 2017 = * [NEW FEATURE] Added option to privately cache logged-in users. * [NEW FEATURE] Added option to privately cache commenters. * [NEW FEATURE] Added option to cache requests made through WordPress REST API. * [BUGFIX] Fixed network 3rd-party full-page cache detection bug. * [GUI] New Cache and Purge menus in Settings. = 1.1.5.1 - August 16 2017 = * [IMPROVEMENT] Improved compatibility of frontend&backend .htaccess path detection when site url is different than installation path. * [UPDATE] Removed unused format string from header tags. * [BUGFIX] 'showheader' Admin Query String now works. * [REFACTOR] Cache tags will no longer output if not needed. = 1.1.5 - August 10 2017 = * [NEW FEATURE] Scheduled Purge URLs feature. * [NEW FEATURE] Added buffer callback to improve compatibility with some plugins that force buffer cleaning. * [NEW FEATURE] Hide purge_all admin bar quick link if cache is disabled. * [NEW FEATURE] Required htaccess rules are now displayed when .htaccess is not writable. * [NEW FEATURE] Debug log features: filter log support; heartbeat control; log file size limit; log viewer. * [IMPROVEMENT] Separate crawler access log. * [IMPROVEMENT] Lazy PURGE requests made after output are now queued and working. * [IMPROVEMENT] Improved readme.txt with keywords relating to our compatible plugins list. * [UPDATE] 'ExpiresDefault' conflict msg is now closeable and only appears in the .htaccess edit screen. * [UPDATE] Improved debug log formatting. * [INTEGRATION] Compatibility with MainWP plugin. * [BUGFIX] Fixed WooCommerce order not purging product stock quantity. * [BUGFIX] Fixed WooCommerce scheduled sale price not updating issue. * [REFACTOR] Combined cache_enable functions into a single function. = 1.1.4 - August 1 2017 = * [IMPROVEMENT] Unexpected rewrite rules will now show an error message. * [IMPROVEMENT] Added Cache Tag Prefix setting info in the Env Report and Info page. * [IMPROVEMENT] LSCWP setting link is now displayed in the plugin list. * [IMPROVEMENT] Improved performance when setting cache control. * [UPDATE] Added backward compatibility for v1.1.2.2 API calls. (used by 3rd-party plugins) * [BUGFIX] Fixed WPCLI purge tag/category never succeeding. = 1.1.3 - July 31 2017 = * [NEW FEATURE] New LiteSpeed_Cache_API class and documentation for 3rd party integration. * [NEW FEATURE] New API function litespeed_purge_single_post($post_id). * [NEW FEATURE] PHP CLI support for crawler. * [IMPROVEMENT] Set 'no cache' for same location 301 redirects. * [IMPROVEMENT] Improved LiteSpeed footer comment compatibility. * [UPDATE] Removed 'cache tag prefix' setting. * [BUGFIX] Fixed a bug involving CLI purge all. * [BUGFIX] Crawler now honors X-LiteSpeed-Cache-Control for the 'no-cache' header. * [BUGFIX] Cache/rewrite rules are now cleared when the plugin is uninstalled. * [BUGFIX] Prevent incorrect removal of the advanced-cache.php on deactivation if it was added by another plugin. * [BUGFIX] Fixed subfolder WP installations being unable to Purge By URL using a full URL path. * [REFACTOR] Reorganized existing code for an upcoming ESI release. = 1.1.2.2 - July 13 2017 = * [BUGFIX] Fixed blank page in Hebrew language post editor by removing unused font-awesome and jquery-ui css libraries. = 1.1.2.1 - July 5 2017 = * [UPDATE] Improved compatibility with WooCommerce v3.1.0. = 1.1.2 - June 20 2017 = * [BUGFIX] Fixed missing form close tag. * [UPDATE] Added a wiki link for enabling the crawler. * [UPDATE] Improved Site IP description. * [UPDATE] Added an introduction to the crawler on the Information page. * [REFACTOR] Added more detailed error messages for Site IP and Custom Sitemap settings. = 1.1.1.1 - June 15 2017 = * [BUGFIX] Hotfix for insufficient validation of site IP value in crawler settings. = 1.1.1 - June 15 2017 = * [NEW] As of LiteSpeed Web Server v.5.1.16, the crawler can now be enabled/disabled at the server level. * [NEW] Added the ability to provide a custom sitemap for crawling. * [NEW] Added ability to use site IP address directly in crawler settings. * [NEW] Crawler performance improved with the use of new custom user agent 'lsrunner'. * [NEW] "Purge By URLs" now supports full URL paths. * [NEW] Added thirdparty WP-PostRatings compatibility. * [BUGFIX] Cache is now cleared when changing post status from published to draft. * [BUGFIX] WHM activation message no longer continues to reappear after being dismissed. * [COSMETIC] Display recommended values for settings. = 1.1.0.1 - June 8 2017 = * [UPDATE] Improved default crawler interval setting. * [UPDATE] Tested up to WP 4.8. * [BUGFIX] Fixed compatibility with plugins that output json data. * [BUGFIX] Fixed tab switching bug. * [BUGFIX] Removed occasional duplicated messages on save. * [COSMETIC] Improved crawler tooltips and descriptions. = 1.1.0 - June 6 2017 = * [NEW] Added a crawler - this includes configuration options and a dedicated admin page. Uses wp-cron * [NEW] Added integration for WPLister * [NEW] Added integration for Avada * [UPDATE] General structure of the plugin revamped * [UPDATE] Improved look of admin pages * [BUGFIX] Fix any/all wp-content path retrieval issues * [BUGFIX] Use realpath to clear symbolic link when determining .htaccess paths * [BUGFIX] Fixed a bug where upgrading multiple plugins did not trigger a purge all * [BUGFIX] Fixed a bug where cli import_options did not actually update the options. * [REFACTOR] Most of the files in the code were split into more, smaller files = 1.0.15 - April 20 2017 = * [NEW] Added Purge Pages and Purge Recent Posts Widget pages options. * [NEW] Added wp-cli command for setting and getting options. * [NEW] Added an import/export options cli command. * [NEW] Added wpForo integration. * [NEW] Added Theme My Login integration. * [UPDATE] Purge adjacent posts when publish a new post. * [UPDATE] Change environment report file to .php and increase security. * [UPDATE] Added new purgeby option to wp-cli. * [UPDATE] Remove nag for multiple sites. * [UPDATE] Only inject LiteSpeed javascripts in LiteSpeed pages. * [REFACTOR] Properly check for zero in ttl settings. * [BUGFIX] Fixed the 404 issue that can be caused by some certain plugins when save the settings. * [BUGFIX] Fixed mu-plugin compatibility. * [BUGFIX] Fixed problem with creating zip backup. * [BUGFIX] Fixed conflict with jetpack. = 1.0.14.1 - January 31 2017 = * [UPDATE] Removed Freemius integration due to feedback. = 1.0.14 - January 30 2017 = * [NEW] Added error page caching. Currently supports 403, 404, 500s. * [NEW] Added a purge errors action. * [NEW] Added wp-cli integration. * [UPDATE] Added support for multiple varies. * [UPDATE] Reorganize the admin interface to be less cluttered. * [UPDATE] Add support for LiteSpeed Web ADC. * [UPDATE] Add Freemius integration. * [REFACTOR] Made some changes so that the rewrite rules are a little more consistent. * [BUGFIX] Check member type before adding purge all button. * [BUGFIX] Fixed a bug where activating/deactivating the plugin quickly caused the WP_CACHE error to show up. * [BUGFIX] Handle more characters in the rewrite parser. * [BUGFIX] Correctly purge posts when they are made public/private. = 1.0.13.1 - November 30 2016 = * [BUGFIX] Fixed a bug where a global was being used without checking existence first, causing unnecessary log entries. = 1.0.13 - November 28 2016 = * [NEW] Add an Empty Entire Cache button. * [NEW] Add stale logic to certain purge actions. * [NEW] Add option to use primary site settings for all subsites in a multisite environment. * [NEW] Add support for Aelia CurrencySwitcher * [UPDATE] Add logic to allow third party vary headers * [UPDATE] Handle password protected pages differently. * [BUGFIX] Fixed bug caused by saving settings. * [BUGFIX] FIxed bug when searching for advanced-cache.php = 1.0.12 - November 14 2016 = * [NEW] Added logic to generate environment reports. * [NEW] Created a notice that will be triggered when the WHM Plugin installs this plugin. This will notify users when the plugin is installed by their server admin. * [NEW] Added the option to cache 404 pages via 404 Page TTL setting. * [NEW] Reworked log system to be based on selection of yes or no instead of log level. * [NEW] Added support for Autoptimize. * [NEW] Added Better WP Minify integration. * [UPDATE] On plugin disable, clear .htaccess. * [UPDATE] Introduced URL tag. Changed Purge by URL to use this new tag. * [BUGFIX] Fixed a bug triggered when .htaccess files were empty. * [BUGFIX] Correctly determine when to clear files in multisite environments (wp-config, advanced-cache, etc.). * [BUGFIX] When disabling the cache, settings changed in the same save will now be saved. * [BUGFIX] Various bugs from setting changes and multisite fixed. * [BUGFIX] Fixed two bugs with the .htaccess path search. * [BUGFIX] Do not alter $_GET in add_quick_purge. This may cause issues for functionality occurring later in the same request. * [BUGFIX] Right to left radio settings were incorrectly displayed. The radio buttons themselves were the opposite direction of the associated text. = 1.0.11 - October 11 2016 = * [NEW] The plugin will now set cachelookup public on. * [NEW] New option - check advanced-cache.php. This enables users to have two caching plugins enabled at the same time as long as the other plugin is not used for caching purposes. For example, using another cache plugin for css/js minification. * [UPDATE] Rules added by the plugin will now be inserted into an LSCACHE START/END PLUGIN comment block. * [UPDATE] For woocommerce pages, if a user visits a non-cached page with a non-empty cart, do not cache the page. * [UPDATE] If woocommerce needs to display any notice, do not cache the page. * [UPDATE] Single site settings are now in both the litespeed cache submenu and the settings submenu. * [BUGFIX] Multisite network options were not updated on upgrade. This is now corrected. = 1.0.10 - September 16 2016 = * Added a check for LSCACHE_NO_CACHE definition. * Added a Purge All button to the admin bar. * Added logic to purge the cache when upgrading a plugin or theme. By default this is enabled on single site installations and disabled on multisite installations. * Added support for WooCommerce Versions < 2.5.0. * Added .htaccess backup rotation. Every 10 backups, an .htaccess archive will be created. If one already exists, it will be overwritten. * Moved some settings to the new Specific Pages tab to reduce clutter in the General tab. * The .htaccess editor is now disabled if DISALLOW_FILE_EDIT is set. * After saving the Cache Tag Prefix setting, all cache will be purged. = 1.0.9.1 - August 26 2016 = * Fixed a bug where an error displayed on the configuration screen despite not being an error. * Change logic to check .htaccess file less often. = 1.0.9 - August 25 2016 = * [NEW] Added functionality to cache and purge feeds. * [NEW] Added cache tag prefix setting to avoid conflicts when using LiteSpeed Cache for WordPress with LiteSpeed Cache for XenForo and LiteMage. * [NEW] Added hooks to allow third party plugins to create config options. * [NEW] Added WooCommerce config options. * The plugin now also checks for wp-config in the parent directory. * Improved WooCommerce support. * Changed .htaccess backup process. Will create a .htaccess_lscachebak_orig file if one does not exist. If it does already exist, creates a backup using the date and timestamp. * Fixed a bug where get_home_path() sometimes returned an invalid path. * Fixed a bug where if the .htaccess was removed from a WordPress subdirectory, it was not handled properly. = 1.0.8.1 - July 28 2016 = * Fixed a bug where check cacheable was sometimes not hit. * Fixed a bug where extra slashes in clear rules were stripped. = 1.0.8 - July 25 2016 = * Added purge all on update check to purge by post id logic. * Added uninstall logic. * Added configuration for caching favicons. * Added configuration for caching the login page. * Added configuration for caching php resources (scripts/stylesheets accessed as .php). * Set login cookie if user is logged in and it isn’t set. * Improved NextGenGallery support to include new actions. * Now displays a notice on the network admin if WP_CACHE is not set. * Fixed a few php syntax issues. * Fixed a bug where purge by pid didn’t work. * Fixed a bug where the Network Admin settings were shown when the plugin was active in a subsite, but not network active. * Fixed a bug where the Advanced Cache check would sometimes not work. = 1.0.7.1 - May 26 2016 = * Fixed a bug where enabling purge all in the auto purge on update settings page did not purge the correct blogs. * Fixed a bug reported by user wpc on our forums where enabling purge all in the auto purge on update settings page caused nothing to be cached. = 1.0.7 - May 24 2016 = * Added login cookie configuration to the Advanced Settings page. * Added support for WPTouch plugin. * Added support for WP-Polls plugin. * Added Like Dislike Counter third party integration. * Added support for Admin IP Query String Actions. * Added confirmation pop up for purge all. * Refactor: LiteSpeed_Cache_Admin is now split into LiteSpeed_Cache_Admin, LiteSpeed_Cache_Admin_Display, and LiteSpeed_Cache_Admin_Rules * Refactor: Rename functions to accurately represent their functionality * Fixed a bug that sometimes caused a “no valid header” error message. = 1.0.6 - May 5 2016 = * Fixed a bug reported by Knut Sparhell that prevented dashboard widgets from being opened or closed. * Fixed a bug reported by Knut Sparhell that caused problems with https support for admin pages. = 1.0.5 - April 26 2016 = * [BETA] Added NextGen Gallery plugin support. * Added third party plugin integration. * Improved cache tag system. * Improved formatting for admin settings pages. * Converted bbPress to use the new third party integration system. * Converted WooCommerce to use the new third party integration system. * If .htaccess is not writable, disable separate mobile view and do not cache cookies/user agents. * Cache is now automatically purged when disabled. * Fixed a bug where .htaccess was not checked properly when adding common rules. * Fixed a bug where multisite setups would be completely purged when one site requested a purge all. = 1.0.4 - April 7 2016 = * Added logic to cache commenters. * Added htaccess backup to the install script. * Added an htaccess editor in the wp-admin dashboard. * Added do not cache user agents. * Added do not cache cookies. * Created new LiteSpeed Cache Settings submenu entries. * Implemented Separate Mobile View. * Modified WP_CACHE not defined message to only show up for users who can manage options. * Moved enabled all/disable all from network management to network settings. * Fixed a bug where WP_CACHE was not defined on activation if it was commented out. = 1.0.3 - March 23 2016 = * Added a Purge Front Page button to the LiteSpeed Cache Management page. * Added a Default Front Page TTL option to the general settings. * Added ability to define web application specific cookie names through rewrite rules to handle logged-in cookie conflicts when using multiple web applications. [Requires LSWS 5.0.15+] * Improved WooCommerce handling. * Fixed a bug where activating lscwp sets the “enable cache” radio button to enabled, but the cache was not enabled by default. * Refactored code to make it cleaner. * Updated readme.txt. = 1.0.2 - March 11 2016 = * Added a "Use Network Admin Setting" option for "Enable LiteSpeed Cache". For single sites, this choice will default to enabled. * Added enable/disable all buttons for network admin. This controls the setting of all managed sites with "Use Network Admin Setting" selected for "Enable LiteSpeed Cache". * Exclude by Category/Tag are now text areas to avoid slow load times on the LiteSpeed Cache Settings page for sites with a large number of categories/tags. * Added a new line to advanced-cache.php to allow identification as a LiteSpeed Cache file. * Activation/Deactivation are now better handled in multi-site environments. * Enable LiteSpeed Cache setting is now a radio button selection instead of a single checkbox. * Can now add '$' to the end of a URL in Exclude URI to perform an exact match. * The _lscache_vary cookie will now be deleted upon logout. * Fixed a bug in multi-site setups that would cause a "function already defined" error. = 1.0.1 - March 8 2016 = * Added Do Not Cache by URI, by Category, and by Tag. URI is a prefix/string equals match. * Added a help tab for plugin compatibilities. * Created logic for other plugins to purge a single post if updated. * Fixed a bug where woocommerce pages that display the cart were cached. * Fixed a bug where admin menus in multi-site setups were not correctly displayed. * Fixed a bug where logged in users were served public cached pages. * Fixed a compatibility bug with bbPress. If there is a new forum/topic/reply, the parent pages will now be purged as well. * Fixed a bug that didn't allow cron job to update scheduled posts. = 1.0.0 - January 20 2016 = * Initial Release. qc-ping.txt000064400000000120152075713260006644 0ustar00For QUIC.cloud connectivity ping test, please do not delete, generated by LSCWP autoload.php000064400000007335152075713260007105 0ustar00 tbody > :nth-child(odd), .litespeed-wrap ul.striped > :nth-child(odd), .litespeed-wrap .alternate { background-color: #f9f9f9; } .litespeed-wrap .notice, .litespeed-wrap div.updated, .litespeed-wrap div.error { border-left: 4px solid #fff; box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1); padding: 1px 12px; } .litespeed-wrap .notice-success, .litespeed-wrap div.updated { border-left-color: #46b450; } .litespeed-wrap .notice-success.notice-alt { background-color: #ecf7ed; } .litespeed-wrap .notice-warning { border-left-color: #ffb900; } .litespeed-wrap .notice-warning.notice-alt { background-color: #fff8e5; } .litespeed-wrap .notice-error, .litespeed-wrap div.error { border-left-color: #dc3232; } .litespeed-wrap .notice-error.notice-alt { background-color: #fbeaea; } .litespeed-wrap .notice-info { border-left-color: #00a0d2; } .litespeed-wrap .notice-info.notice-alt { background-color: #e5f5fa; } assets/css/litespeed-dark-mode.css000064400000101247152075713260013204 0ustar00/* ======================================= DARK MODE STYLES ======================================= */ /* Dark Mode Toggle Button */ .litespeed-dark-mode-toggle { position: fixed; top: 32px; right: 20px; z-index: 999999; background: none; border: none; width: auto; height: auto; color: inherit; cursor: pointer; font-size: 16px; } /* ======================================= DARK MODE STYLES Auto-applied based on browser preference OR manually toggled with .litespeed-darkmode class ======================================= */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) { background-color: #1e1e1e; color: #e0e0e0; } } body.litespeed-darkmode { background-color: #1e1e1e; color: #e0e0e0; } /* WordPress admin wrapper */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) #wpwrap { background-color: #1e1e1e; color: #e0e0e0; } } body.litespeed-darkmode #wpwrap { background-color: #1e1e1e; color: #e0e0e0; } /* Main content area */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) #wpcontent { background-color: #181818; color: #e0e0e0; } } body.litespeed-darkmode #wpcontent { background-color: #181818; color: #e0e0e0; } /* Form inputs */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) input[type='text'], body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) input[type='number'], body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) input[type='email'], body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) input[type='url'], body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) input[type='password'], body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) input[type='search'], body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) input[type='file'], body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) textarea, body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) select { background-color: #2d2d30; border: 1px solid #3e3e42; color: #e0e0e0; } } body.litespeed-darkmode input[type='text'], body.litespeed-darkmode input[type='number'], body.litespeed-darkmode input[type='email'], body.litespeed-darkmode input[type='url'], body.litespeed-darkmode input[type='password'], body.litespeed-darkmode input[type='search'], body.litespeed-darkmode input[type='file'], body.litespeed-darkmode textarea, body.litespeed-darkmode select { background-color: #2d2d30; border: 1px solid #3e3e42; color: #e0e0e0; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) input[type='text']:focus, body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) input[type='number']:focus, body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) input[type='email']:focus, body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) input[type='url']:focus, body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) input[type='password']:focus, body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) input[type='search']:focus, body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) input[type='file']:focus, body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) textarea:focus, body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) select:focus { background-color: #404043; border: 1px solid #5a5a5d; box-shadow: 0 0 0 1px #0073aa; } } body.litespeed-darkmode input[type='text']:focus, body.litespeed-darkmode input[type='number']:focus, body.litespeed-darkmode input[type='email']:focus, body.litespeed-darkmode input[type='url']:focus, body.litespeed-darkmode input[type='password']:focus, body.litespeed-darkmode input[type='search']:focus, body.litespeed-darkmode input[type='file']:focus, body.litespeed-darkmode textarea:focus, body.litespeed-darkmode select:focus { background-color: #404043; border: 1px solid #5a5a5d; box-shadow: 0 0 0 1px #0073aa; } /* Buttons */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .button, body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .button-secondary { background-color: #3e3e42; border-color: #5a5a5d; color: #e0e0e0; } } body.litespeed-darkmode .button, body.litespeed-darkmode .button-secondary { background-color: #3e3e42; border-color: #5a5a5d; color: #e0e0e0; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) circle.litespeed-pie_bg { stroke: #3e3e42; } } body.litespeed-darkmode circle.litespeed-pie_bg { stroke: #3e3e42; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) g.litespeed-pie_info text { fill: #e0e0e0; } } body.litespeed-darkmode g.litespeed-pie_info text { fill: #e0e0e0; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) g.litespeed-pie_info .litespeed-pie-done { fill: #6dd17e; } } body.litespeed-darkmode g.litespeed-pie_info .litespeed-pie-done { fill: #6dd17e; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-switch input:not(:checked) + label { background-color: #3e3e42; color: #e0e0e0; border: 1px solid #5a5a5d; } } body.litespeed-darkmode .litespeed-switch input:not(:checked) + label { background-color: #3e3e42; color: #e0e0e0; border: 1px solid #5a5a5d; } /* Column with boxes layout */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-column-with-boxes .postbox { border-color: #3e3e42; } } body.litespeed-darkmode .litespeed-column-with-boxes .postbox { border-color: #3e3e42; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-column-with-boxes > div.litespeed-column-right { background-color: #2d2d30; } } body.litespeed-darkmode .litespeed-column-with-boxes > div.litespeed-column-right { background-color: #2d2d30; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-image-optim-summary, body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-column-left-inside { background-color: #1e1e1e; border: 1px solid #3e3e42; color: #e0e0e0; } } body.litespeed-darkmode .litespeed-image-optim-summary, body.litespeed-darkmode .litespeed-column-left-inside { background-color: #1e1e1e; border: 1px solid #3e3e42; color: #e0e0e0; } @media (prefers-color-scheme: dark) { body[class*="litespeed"]:not(.litespeed-lightmode).litespeed-cache_page_litespeed-img_optm [data-litespeed-layout='summary'], body[class*="litespeed"]:not(.litespeed-lightmode).litespeed-cache_page_litespeed-img_optm .litespeed-column-with-boxes > div.litespeed-column-right, body[class*="litespeed"]:not(.litespeed-lightmode).litespeed-cache_page_litespeed-cdn .litespeed-column-with-boxes > div.litespeed-column-right, body[class*="litespeed"]:not(.litespeed-lightmode).litespeed-cache_page_litespeed-cdn [data-litespeed-layout='qc'] { background-color: #181818; } } body.litespeed-darkmode.litespeed-cache_page_litespeed-img_optm [data-litespeed-layout='summary'], body.litespeed-darkmode.litespeed-cache_page_litespeed-img_optm .litespeed-column-with-boxes > div.litespeed-column-right, body.litespeed-darkmode.litespeed-cache_page_litespeed-cdn .litespeed-column-with-boxes > div.litespeed-column-right, body.litespeed-darkmode.litespeed-cache_page_litespeed-cdn [data-litespeed-layout='qc'] { background-color: #181818; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-image-optim-summary-footer, body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-column-with-boxes-footer { border-top: 1px solid #373737; background: revert; } } body.litespeed-darkmode .litespeed-image-optim-summary-footer, body.litespeed-darkmode .litespeed-column-with-boxes-footer { border-top: 1px solid #373737; background: revert; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-column-with-boxes-footer { border-top: 1px solid #3e3e42; background-color: #353539; color: #e0e0e0; } } body.litespeed-darkmode .litespeed-column-with-boxes-footer{ border-top: 1px solid #3e3e42; background-color: #353539; color: #e0e0e0; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .nav-tab:focus:not(.nav-tab-active), body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .nav-tab:hover:not(.nav-tab-active) { background-color: #4d4d51; border-color: #666; color: #fff } } body.litespeed-darkmode .nav-tab:focus:not(.nav-tab-active), body.litespeed-darkmode .nav-tab:hover:not(.nav-tab-active) { background-color: #4d4d51; border-color: #666; color: #fff } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .button:hover, body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .button-secondary:hover { background-color: #484851; border-color: #666; } } body.litespeed-darkmode .button:hover, body.litespeed-darkmode .button-secondary:hover { background-color: #484851; border-color: #666; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .button-primary { background-color: #0073aa; border-color: #005a87; color: #ffffff; } } body.litespeed-darkmode .button-primary { background-color: #0073aa; border-color: #005a87; color: #ffffff; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .button-primary:hover { background-color: #005a87; border-color: #004a73; } } body.litespeed-darkmode .button-primary:hover { background-color: #005a87; border-color: #004a73; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .button-primary:disabled, body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .button-primary[disabled] { background-color: #3e3e42; border-color: #5a5a5d; color: #8c8f94; cursor: not-allowed; opacity: 0.6; } } body.litespeed-darkmode .button-primary:disabled, body.litespeed-darkmode .button-primary[disabled] { background-color: #3e3e42; border-color: #5a5a5d; color: #8c8f94; cursor: not-allowed; opacity: 0.6; } /* Danger buttons */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-btn-danger-bg, body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed .litespeed-btn-danger-bg, body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-wrap .litespeed-btn-danger-bg { background-color: #dc3232; border-color: #b32d2e; color: #ffffff; box-shadow: 0 1px 0 rgba(179, 45, 46, 0.5); } } body.litespeed-darkmode .litespeed-btn-danger-bg, body.litespeed-darkmode .litespeed .litespeed-btn-danger-bg, body.litespeed-darkmode .litespeed-wrap .litespeed-btn-danger-bg { background-color: #dc3232; border-color: #b32d2e; color: #ffffff; box-shadow: 0 1px 0 rgba(179, 45, 46, 0.5); } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-btn-danger-bg:hover, body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed .litespeed-btn-danger-bg:hover, body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-wrap .litespeed-btn-danger-bg:hover { background-color: #b32d2e; border-color: #a02622; } } body.litespeed-darkmode .litespeed-btn-danger-bg:hover, body.litespeed-darkmode .litespeed .litespeed-btn-danger-bg:hover, body.litespeed-darkmode .litespeed-wrap .litespeed-btn-danger-bg:hover { background-color: #b32d2e; border-color: #a02622; } /* Notices */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .notice { background-color: #262626; border-left: 4px solid #72a9d3; color: #e0e0e0; } } body.litespeed-darkmode .notice { background-color: #262626; border-left: 4px solid #72a9d3; color: #e0e0e0; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .notice-success { border-left-color: #46b450; } } body.litespeed-darkmode .notice-success { border-left-color: #46b450; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .notice-warning { border-left-color: #b28100; } } body.litespeed-darkmode .notice-warning { border-left-color: #b28100; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .notice-error { border-left-color: #dc3232; } } body.litespeed-darkmode .notice-error { border-left-color: #dc3232; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .notice-info { border-left-color: #00a0d2; } } body.litespeed-darkmode .notice-info { border-left-color: #00a0d2; } /* Striped table notices */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .striped > tbody > :nth-child(even) .notice { background-color: #3e3e42; box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.3); } } body.litespeed-darkmode .striped > tbody > :nth-child(even) .notice { background-color: #3e3e42; box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.3); } /* Postboxes */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .postbox { background-color: #2d2d30; border: 1px solid #767679; color: #e0e0e0; } } body.litespeed-darkmode .postbox { background-color: #2d2d30; border: 1px solid #767679; color: #e0e0e0; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .postbox h3, body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .postbox .hndle { color: #e0e0e0; } } body.litespeed-darkmode .postbox h3, body.litespeed-darkmode .postbox .hndle { color: #e0e0e0; } /* Tables */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) table, body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .wp-list-table { background-color: #2d2d30; color: #e0e0e0; } } body.litespeed-darkmode table, body.litespeed-darkmode .wp-list-table { background-color: #2d2d30; color: #e0e0e0; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .wp-list-table th { color: #e0e0e0; } } body.litespeed-darkmode .wp-list-table th { color: #e0e0e0; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .wp-list-table td { border-bottom: 1px solid #3e3e42; color: #e0e0e0; } } body.litespeed-darkmode .wp-list-table td { border-bottom: 1px solid #3e3e42; color: #e0e0e0; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .striped > tbody > :nth-child(odd) { background-color: #2d2d30; } } body.litespeed-darkmode .striped > tbody > :nth-child(odd) { background-color: #2d2d30; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .striped > tbody > :nth-child(even) { background-color: #353539; } } body.litespeed-darkmode .striped > tbody > :nth-child(even) { background-color: #353539; } /* Form tables */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .form-table { background: transparent; } } body.litespeed-darkmode .form-table { background: transparent; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .form-table th { background: transparent; color: #e0e0e0; } } body.litespeed-darkmode .form-table th { background: transparent; color: #e0e0e0; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .form-table td { background: transparent; color: #e0e0e0; } } body.litespeed-darkmode .form-table td { background: transparent; color: #e0e0e0; } /* Links */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) a { color: #72aee6; } } body.litespeed-darkmode a { color: #72aee6; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) a:hover, body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) a:focus { color: #9ec8f2; } } body.litespeed-darkmode a:hover, body.litespeed-darkmode a:focus { color: #9ec8f2; } /* Code blocks */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) code { background-color: #404043; color: #e0e0e0; } } body.litespeed-darkmode code { background-color: #404043; color: #e0e0e0; } /* Horizontal rules */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) hr { border-color: #5a5a5d; background-color: #5a5a5d; } } body.litespeed-darkmode hr { border-color: #5a5a5d; background-color: #5a5a5d; } /* Dashboard widgets */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .dashboard-widget { background-color: #2d2d30; border: 1px solid #3e3e42; } } body.litespeed-darkmode .dashboard-widget { background-color: #2d2d30; border: 1px solid #3e3e42; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .dashboard-widget .widget-top { background-color: #404043; border-bottom: 1px solid #5a5a5d; } } body.litespeed-darkmode .dashboard-widget .widget-top { background-color: #404043; border-bottom: 1px solid #5a5a5d; } /* Meta boxes */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .meta-box-sortables .postbox { background-color: #2d2d30; border: 1px solid #3e3e42; } } body.litespeed-darkmode .meta-box-sortables .postbox { background-color: #2d2d30; border: 1px solid #3e3e42; } /* LiteSpeed specific styles */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-header { border-bottom: 1px solid #5a5a5d; } } body.litespeed-darkmode .litespeed-header { border-bottom: 1px solid #5a5a5d; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-body { background-color: #1e1e1e; border-left: 1px solid #5a5a5d; border-right: 1px solid #5a5a5d; border-bottom: 1px solid #5a5a5d; color: #e0e0e0; } } body.litespeed-darkmode .litespeed-body { background-color: #1e1e1e; border-left: 1px solid #5a5a5d; border-right: 1px solid #5a5a5d; border-bottom: 1px solid #5a5a5d; color: #e0e0e0; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-panel { background-color: #2d2d30; border: 1px solid #3e3e42; color: #e0e0e0; } } body.litespeed-darkmode .litespeed-panel { background-color: #2d2d30; border: 1px solid #3e3e42; color: #e0e0e0; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-panel-wrapper { background: transparent; } } body.litespeed-darkmode .litespeed-panel-wrapper { background: transparent; } /* Dashboard titles */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-h1, body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) h1.litespeed-h1 { color: #e0e0e0; } } body.litespeed-darkmode .litespeed-h1, body.litespeed-darkmode h1.litespeed-h1 { color: #e0e0e0; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) h3 { color: #e0e0e0; } } body.litespeed-darkmode h3 { color: #e0e0e0; } /* LiteSpeed postboxes */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-postbox { background-color: #2d2d30; border: 1px solid #3e3e42; color: #e0e0e0; } } body.litespeed-darkmode .litespeed-postbox { background-color: #2d2d30; border: 1px solid #3e3e42; color: #e0e0e0; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-postbox .inside { color: #e0e0e0; } } body.litespeed-darkmode .litespeed-postbox .inside { color: #e0e0e0; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-postbox .inside .litespeed-title { border-bottom: 1px solid #5a5a5d; color: #e0e0e0; } } body.litespeed-darkmode .litespeed-postbox .inside .litespeed-title { border-bottom: 1px solid #5a5a5d; color: #e0e0e0; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-postbox .inside.litespeed-postbox-footer { background-color: #353539; border-top: 1px solid #5a5a5d; } } body.litespeed-darkmode .litespeed-postbox .inside.litespeed-postbox-footer { background-color: #353539; border-top: 1px solid #5a5a5d; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-postbox p.litespeed-dashboard-stats-total { border-top: 1px dashed #5a5a5d; } } body.litespeed-darkmode .litespeed-postbox p.litespeed-dashboard-stats-total { border-top: 1px dashed #5a5a5d; } /* Dashboard stats */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-dashboard-stats .litespeed-desc { color: #b3b3b3; } } body.litespeed-darkmode .litespeed-dashboard-stats .litespeed-desc { color: #b3b3b3; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-dashboard-stats-payg { color: #b3b3b3; } } body.litespeed-darkmode .litespeed-dashboard-stats-payg { color: #b3b3b3; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-dashboard-stats-payg strong { color: #e0e0e0; } } body.litespeed-darkmode .litespeed-dashboard-stats-payg strong { color: #e0e0e0; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-dashboard-stats-wrapper .litespeed-postbox:not(:first-child) { border-left-color: #3e3e42; } } body.litespeed-darkmode .litespeed-dashboard-stats-wrapper .litespeed-postbox:not(:first-child) { border-left-color: #3e3e42; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-postbox.litespeed-postbox-partner h3.litespeed-title { color: #b3b3b3; } } body.litespeed-darkmode .litespeed-postbox.litespeed-postbox-partner h3.litespeed-title { color: #b3b3b3; } /* QUIC.cloud postbox styling */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-postbox--quiccloud { border-color: #3e7cb3; } } body.litespeed-darkmode .litespeed-postbox--quiccloud { border-color: #3e7cb3; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-postbox--quiccloud.litespeed-postbox .inside .litespeed-title { background-color: #41464a; color: #ffffff; } } body.litespeed-darkmode .litespeed-postbox--quiccloud.litespeed-postbox .inside .litespeed-title { background-color: #41464a; color: #ffffff; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-postbox--quiccloud.litespeed-postbox .inside .litespeed-title a { color: #a5caf2; } } body.litespeed-darkmode .litespeed-postbox--quiccloud.litespeed-postbox .inside .litespeed-title a { color: #a5caf2; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-postbox--quiccloud.litespeed-postbox .inside .litespeed-title a:hover { color: #c2dcff; } } body.litespeed-darkmode .litespeed-postbox--quiccloud.litespeed-postbox .inside .litespeed-title a:hover { color: #c2dcff; } /* Dashboard unlock/promo styling */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-dashboard-unlock { background-color: #2d2d30; color: #e0e0e0; } } body.litespeed-darkmode .litespeed-dashboard-unlock { background-color: #2d2d30; color: #e0e0e0; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-dashboard-unlock-desc { color: #e0e0e0; } } body.litespeed-darkmode .litespeed-dashboard-unlock-desc { color: #e0e0e0; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-dashboard-unlock-desc span { color: #b3b3b3; } } body.litespeed-darkmode .litespeed-dashboard-unlock-desc span { color: #b3b3b3; } /* Navigation tabs */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .nav-tab { background-color: #3e3e42; border-color: #5a5a5d; color: #e0e0e0; } } body.litespeed-darkmode .nav-tab { background-color: #3e3e42; border-color: #5a5a5d; color: #e0e0e0; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .nav-tab:hover { background-color: #1e1e1e; } } body.litespeed-darkmode .nav-tab:hover { background-color: #1e1e1e; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .nav-tab-active { background-color: #1e1e1e; border-bottom-color: #1e1e1e; color: #e0e0e0; } } body.litespeed-darkmode .nav-tab-active { background-color: #1e1e1e; border-bottom-color: #1e1e1e; color: #e0e0e0; } /* Status indicators */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-success { color: #46b450; } } body.litespeed-darkmode .litespeed-success { color: #46b450; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-error { color: #dc3232; } } body.litespeed-darkmode .litespeed-error { color: #dc3232; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-warning { color: #ffb900; } } body.litespeed-darkmode .litespeed-warning { color: #ffb900; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-warning-bg { background-color: #584007 !important; color: #ffffff; } } body.litespeed-darkmode .litespeed-warning-bg { background-color: #584007 !important; color: #ffffff; } /* Footer */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) #wpfooter { background-color: #2d2d30; color: #e0e0e0; } } body.litespeed-darkmode #wpfooter { background-color: #2d2d30; color: #e0e0e0; } /* Checkbox and radio inputs */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) input[type="checkbox"], body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) input[type="radio"] { background-color: #2d2d30; border: 1px solid #3e3e42; } } body.litespeed-darkmode input[type="checkbox"], body.litespeed-darkmode input[type="radio"] { background-color: #2d2d30; border: 1px solid #3e3e42; } /* Progress bars */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-progress { background-color: #3e3e42; } } body.litespeed-darkmode .litespeed-progress { background-color: #3e3e42; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-progress-bar { background-color: #0073aa; } } body.litespeed-darkmode .litespeed-progress-bar { background-color: #0073aa; } /* Litespeed tick buttons */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-tick { background-color: #2d2d30; border: 1px solid #3e3e42; padding: 8px 10px; } } body.litespeed-darkmode .litespeed-tick { background-color: #2d2d30; border: 1px solid #3e3e42; padding: 8px 10px; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-tick:hover { background-color: #404043; border-color: #5a5a5d; } } body.litespeed-darkmode .litespeed-tick:hover { background-color: #404043; border-color: #5a5a5d; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-tick label { color: #e0e0e0; padding: 4px 8px; } } body.litespeed-darkmode .litespeed-tick label { color: #e0e0e0; padding: 4px 8px; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-tick label:hover { color: #72aee6; } } body.litespeed-darkmode .litespeed-tick label:hover { color: #72aee6; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-tick input[type='checkbox'] { background-color: #2d2d30; border: 2px solid #5a5a5d; } } body.litespeed-darkmode .litespeed-tick input[type='checkbox'] { background-color: #2d2d30; border: 2px solid #5a5a5d; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-tick input[type='checkbox']:checked { background-color: #0073aa; border-color: #0073aa; } } body.litespeed-darkmode .litespeed-tick input[type='checkbox']:checked { background-color: #0073aa; border-color: #0073aa; } /* Card headers */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-card-header { background-color: #404043; border-bottom: 1px solid #5a5a5d; color: #e0e0e0; } } body.litespeed-darkmode .litespeed-card-header { background-color: #404043; border-bottom: 1px solid #5a5a5d; color: #e0e0e0; } /* Card action buttons */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-card-action .button-secondary:hover { background-color: #484851; border-color: #666; color: #e0e0e0; } } body.litespeed-darkmode .litespeed-card-action .button-secondary:hover { background-color: #484851; border-color: #666; color: #e0e0e0; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-card-action .button-primary:hover { background-color: #005a87; border-color: #004a73; color: #ffffff; } } body.litespeed-darkmode .litespeed-card-action .button-primary:hover { background-color: #005a87; border-color: #004a73; color: #ffffff; } @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-h3, body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-panel-para { color: revert; } } body.litespeed-darkmode .litespeed-h3, body.litespeed-darkmode .litespeed-panel-para { color: revert; } /* Text gradient for QUIC.cloud branding */ @media (prefers-color-scheme: dark) { body[class*="litespeed-cache_page_litespeed"]:not(.litespeed-lightmode) .litespeed-qc-text-gradient { background: -webkit-linear-gradient(130deg, #ff69b4, #4db3e6 60%, #a5e7ff); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } } body.litespeed-darkmode .litespeed-qc-text-gradient { background: -webkit-linear-gradient(130deg, #ff69b4, #4db3e6 60%, #a5e7ff); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } assets/css/litespeed.css000064400000256425152075713260011354 0ustar00@font-face { font-family: "litespeedfont"; src: url(data:application/font-woff;base64,d09GRgABAAAAAAd8AAsAAAAABzAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAABCAAAAGAAAABgDxIFKmNtYXAAAAFoAAAAVAAAAFQXVtKHZ2FzcAAAAbwAAAAIAAAACAAAABBnbHlmAAABxAAAAywAAAMsC7+w5mhlYWQAAATwAAAANgAAADYNxQCSaGhlYQAABSgAAAAkAAAAJAe+A8ZobXR4AAAFTAAAABQAAAAUCgAABWxvY2EAAAVgAAAADAAAAAwAKAGqbWF4cAAABWwAAAAgAAAAIAAOAX5uYW1lAAAFjAAAAc4AAAHOiN8uy3Bvc3QAAAdcAAAAIAAAACAAAwAAAAMDAAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAA6QADwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADgAAAAKAAgAAgACAAEAIOkA//3//wAAAAAAIOkA//3//wAB/+MXBAADAAEAAAAAAAAAAAAAAAEAAf//AA8AAQAAAAAAAAAAAAIAADc5AQAAAAABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAACAAF/8QD/AO7AIAAxAFEAWkBbgFyAXcBewAAATA0MTQmMTA0JzgBNSImOQEBOAExLgEjIgYHOAE5AQEwBiMUMDEGFDEwBhUwFDEcARUcARUwFDEUFjEwFBc4ARUyFjkBATAWFTAyMTAWMzAyFTAyMzIWMzI2MzoBMTQyMTI2MTAyMTQ2OQEBMDYzNDAxNjQxMDY1MDQxNDY1JjQ1BzEVBzgBMQE4ATEwBjEjMCIxMCIxMCIxMCYxOAExATgBMSc1MDQxMDQ5ATUxNzgBMQEwNjMyFjEBOAExFxUwFDEwFDEnMCIxMDQxMDQxMCIxNDAnMSc4ATEuASMiBgc4ATEHBjAVMCIxMBQxMBQxMCIxHAExMBQVMDIxMBQxMBQxMDIxFDAXMRcWMDM4ARUwMjE4ATEyMBU6ATEwMjM0MDM4ATEwMjE0MDEyMDcxNzYwNTAyMTA0MTA0MTgBMzwBMTA0NScHFzgBMRYUFxYGDwEOASMiJicmNj8BJyY2PwE+ATMyFhcWBgcFFxUBMxMHIwEBMwE1NzUnNQED+wEBAQH+FAIGAwMGAv4UAQEBAQEBAQEB7AIBAQEBAQEBAQEBAQEBAQEBAQECAewBAQEBAQFOAf5XAQEBAQEB/lcBAQGpAgEBAgGpAbABAQH0AgICAgIC9AEBAQEBAfQBAQEBAQEBAQEBAQH0AQEBoE8rAQEBAgSBAgQDBAYBAgEDTysFAwWBAgQEAwYBAgED/oz6/sw6+vo6ATQBNDb+zP7+ATgBwwEBAQEBAQIB7AICAgL+FAIBAQEBAQEBAQEBAQEBAQEBAQEC/hQBAQEBAQEBAQEBAewCAQEBAQEBAQEBAQEBBAEB/lcBAQGpAQEBAQEBAakBAf5XAQEBAQMBAQEB9AECAgH0AQEBAQEBAQEBAQEB9AEBAQEBAfQBAQEBAQEBAYRkPQECAQcLA2MCAgQDAwgDZD4GDgViAgIEAwMIA6P5OgEzATP6ATT+lP7MNv44/jb+zAABAAAAAQAAiK6LiV8PPPUACwQAAAAAANVU3gsAAAAA1VTeCwAA/8QD/AO7AAAACAACAAAAAAAAAAEAAAPA/8AAAAQAAAAAAAP8AAEAAAAAAAAAAAAAAAAAAAAFBAAAAAAAAAAAAAAAAgAAAAQAAAUAAAAAAAoAFAAeAZYAAQAAAAUBfAAIAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAA4ArgABAAAAAAABAA0AAAABAAAAAAACAAcAlgABAAAAAAADAA0ASAABAAAAAAAEAA0AqwABAAAAAAAFAAsAJwABAAAAAAAGAA0AbwABAAAAAAAKABoA0gADAAEECQABABoADQADAAEECQACAA4AnQADAAEECQADABoAVQADAAEECQAEABoAuAADAAEECQAFABYAMgADAAEECQAGABoAfAADAAEECQAKADQA7GxpdGVzcGVlZGZvbnQAbABpAHQAZQBzAHAAZQBlAGQAZgBvAG4AdFZlcnNpb24gMS4wAFYAZQByAHMAaQBvAG4AIAAxAC4AMGxpdGVzcGVlZGZvbnQAbABpAHQAZQBzAHAAZQBlAGQAZgBvAG4AdGxpdGVzcGVlZGZvbnQAbABpAHQAZQBzAHAAZQBlAGQAZgBvAG4AdFJlZ3VsYXIAUgBlAGcAdQBsAGEAcmxpdGVzcGVlZGZvbnQAbABpAHQAZQBzAHAAZQBlAGQAZgBvAG4AdEZvbnQgZ2VuZXJhdGVkIGJ5IEljb01vb24uAEYAbwBuAHQAIABnAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAEkAYwBvAE0AbwBvAG4ALgAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=); font-weight: normal; font-style: normal; } #adminmenu #toplevel_page_lscache-settings .menu-icon-generic div.wp-menu-image:before, #adminmenu #toplevel_page_litespeed .menu-icon-generic div.wp-menu-image:before, .litespeed-top-toolbar .ab-icon::before { content: '\e900'; font-family: 'litespeedfont' !important; speak: none; font-style: normal; font-weight: normal; font-variant: normal; text-transform: none; line-height: 1; /* Better Font Rendering =========== */ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } #wpadminbar .litespeed-top-toolbar .ab-icon.icon_disabled::before { color: #D9534F; } *[litespeed-accesskey]:not([data-litespeed-noprefix]):before { content: '[' attr(litespeed-accesskey) '] '; } /* ======================================= UTILITIES - toggle UI ======================================= */ input[type='checkbox'].litespeed-tiny-toggle { -webkit-appearance: none; -moz-appearance: none; appearance: none; -webkit-tap-highlight-color: transparent; width: auto; height: auto; vertical-align: middle; position: relative; border: 0; outline: 0; cursor: pointer; margin: 0 4px; background: none; box-shadow: none; } input[type='checkbox'].litespeed-tiny-toggle:focus { box-shadow: none; } input[type='checkbox'].litespeed-tiny-toggle:after { content: ''; font-size: 8px; font-weight: 400; line-height: 18px; text-indent: -14px; color: #ffffff; width: 36px; height: 18px; display: inline-block; background-color: #a7aaad; border-radius: 72px; box-shadow: 0 0 12px rgb(0 0 0 / 15%) inset; } input[type='checkbox'].litespeed-tiny-toggle:before { content: ''; width: 14px; height: 14px; display: block; position: absolute; top: 2px; left: 2px; margin: 0; border-radius: 50%; background-color: #ffffff; } input[type='checkbox'].litespeed-tiny-toggle:checked:before { left: 20px; margin: 0; background-color: #ffffff; } input[type='checkbox'].litespeed-tiny-toggle, input[type='checkbox'].litespeed-tiny-toggle:before, input[type='checkbox'].litespeed-tiny-toggle:after, input[type='checkbox'].litespeed-tiny-toggle:checked:before, input[type='checkbox'].litespeed-tiny-toggle:checked:after { transition: ease 0.15s; } input[type='checkbox'].litespeed-tiny-toggle:checked:after { /*content: 'ON';*/ background-color: #2271b1; } .block-editor__container input[type='checkbox'].litespeed-tiny-toggle { border: 0 !important; } .block-editor__container input[type='checkbox'].litespeed-tiny-toggle:before { top: 5px; left: 7px; } .block-editor__container input[type='checkbox'].litespeed-tiny-toggle:checked:before { left: 23px; } /* ======================================= UTILITIES - structure ======================================= */ .litespeed_icon:before { /* content: "\e900"; font-family: 'litespeedfont' !important; */ content: ''; background-image: url('../img/lscwp_grayscale_font-icon_22px.svg'); /* filter: grayscale(1); */ background-size: 22px; background-repeat: no-repeat; width: 22px; height: 22px; vertical-align: middle; display: inline-block; position: absolute; left: 5px; top: 8px; } .rtl .litespeed_icon:before { left: initial; right: 5px; } .litespeed_icon { padding-left: 30px !important; position: relative; } .rtl .litespeed_icon { padding-right: 40px; } .litespeed-quic-icon { background-image: url('../img/quic-cloud-icon-16x16.svg'); background-repeat: no-repeat; width: 16px; height: 16px; vertical-align: middle; display: inline-block; } .litespeed-row { margin-top: 5px; } .litespeed-reset { width: initial; } .litespeed-inline { display: inline-block; } .litespeed-flex { display: flex; } .litespeed-flex-container { display: flex; flex-wrap: wrap; width: 100%; height: auto; } .litespeed-flex-align-center { align-items: center; } .litespeed-flex-container > * { box-sizing: border-box; } .litespeed-flex-container--reverse { flex-direction: row-reverse; } .litespeed-flex-container .litespeed-icon-vertical-middle { margin-left: 0; } .litespeed-row-flex { display: inline-flex; } .litespeed-flex-wrap { flex-wrap: wrap; } .litespeed-align-right { margin-left: auto !important; } .litespeed-width-1-2 { width: 45%; padding: 20px; } .litespeed-width-1-3 { width: 30%; padding: 25px; } .litespeed-width-7-10 { width: 65%; padding: 20px; } .litespeed-width-3-10 { width: 35%; padding: 20px; } @media screen and (max-width: 814px) { .litespeed-width-7-10 { width: 100%; } .litespeed-width-3-10 { width: 100%; padding: 0; } } .litespeed-hide { display: none !important; } .litespeed-right { float: right !important; } .litespeed-relative { position: relative; } .litespeed-align-center { margin-left: auto; margin-right: auto; } /* ======================================= UTILITIES - spacing ======================================= */ .litespeed-left10 { margin-left: 10px !important; } .litespeed-left20 { margin-left: 20px !important; } .litespeed-right10 { margin-right: 10px !important; } .litespeed-right20 { margin-right: 20px !important; } .litespeed-right30 { margin-right: 30px !important; } .litespeed-right50 { margin-right: 50px !important; } .litespeed-top10 { margin-top: 10px !important; } .litespeed-top15 { margin-top: 15px !important; } .litespeed-top20 { margin-top: 20px !important; } .litespeed-top30 { margin-top: 30px !important; } .litespeed-margin-y5 { margin-top: 5px !important; margin-bottom: 5px !important; } .litespeed-margin-x5 { margin-left: 5px !important; margin-right: 5px !important; } .litespeed-wrap .litespeed-left20, .litespeed-left20 { margin-left: 20px; } .litespeed-wrap .litespeed-bg-quic-cloud { background: linear-gradient(rgba(230, 242, 242, 1) 10%, rgba(250, 255, 255, 1) 30%); } .litespeed-left50 { margin-left: 50px; } .litespeed-padding-space { padding: 5px 10px; } .litespeed-margin-bottom10 { margin-bottom: 10px !important; } .litespeed-margin-bottom20 { margin-bottom: 20px !important; } .litespeed-margin-bottom-remove { margin-bottom: 0px !important; } .litespeed-margin-top-remove { margin-top: 0px !important; } .litespeed-margin-left-remove { margin-left: 0px !important; } .litespeed-margin-y-remove { margin-top: 0px !important; margin-bottom: 0px !important; } .litespeed-empty-space-xlarge { margin-top: 8em; } .litespeed-empty-space-large { margin-top: 6em; } .litespeed-empty-space-medium { margin-top: 3em; } .litespeed-empty-space-small { margin-top: 2em; } .litespeed-empty-space-tiny { margin-top: 1em; } /* ======================================= UTILITIES - typography ======================================= */ .litespeed-text-jumbo { font-size: 3em !important; } .litespeed-text-large { font-size: 0.75em !important; } .litespeed-text-md { font-size: 1.2em; } .litespeed-text-right { text-align: right; } .litespeed-text-center { text-align: center; } .litespeed-text-bold, .litespeed-bold { font-weight: 600; } /* ======================================= COLORS ======================================= */ .litespeed-default { color: #a7a7a7 !important; } .litespeed-primary { color: #3366cc !important; } .litespeed-info { color: #3fbfbf !important; } .litespeed-success { color: #73b38d !important; } .litespeed-warning { color: #ff8c00 !important; } .litespeed-danger { color: #dc3545 !important; } a.litespeed-danger:hover, button.litespeed-danger:hover { color: #a00 !important; } .litespeed-text-success { color: #34b15d; } .litespeed-form-action { color: #1a9292 !important; } a.litespeed-form-action:hover, button.litespeed-form-action:hover { color: #36b0af !important; } .litespeed-bg-default { background-color: #a7a7a7 !important; } .litespeed-bg-primary { background-color: #3366cc !important; } .litespeed-bg-info { background-color: #d1ecf1 !important; } .litespeed-bg-success { background-color: #73b38d !important; } .litespeed-bg-warning { background-color: #ff8c00 !important; } .litespeed-bg-danger { background-color: #dc3545 !important; } .litespeed-bg-text-success { background-color: #34b15d; } /* ======================================= LAYOUT ======================================= */ .litespeed-wrap { margin: 10px 20px 0 2px; } @media screen and (max-width: 600px) { .litespeed-wrap h2 .nav-tab { border-bottom: 1px solid #c3c4c7; margin: 10px 10px 0 0; } .litespeed-wrap .nav-tab-wrapper { margin-bottom: 15px; } .litespeed-desc a, .litespeed-body p > a:not(.button) { word-break: break-word; } } .litespeed-wrap .nav-tab { border-bottom-color: inherit; border-bottom-style: solid; border-bottom-width: 1px; margin: 11px 10px -1px 0; } .litespeed-wrap .nav-tab-active { background: #fff; border-bottom-color: #fff; } .litespeed-wrap .nav-tab:focus:not(.nav-tab-active), .litespeed-wrap .nav-tab:hover:not(.nav-tab-active) { background-color: #f1f1f1; color: #444; } .litespeed-body { background: #fff; border: 1px solid #e5e5e5; box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04); padding: 1px 20px 20px 20px; } @media screen and (min-width: 681px) { .litespeed-header + .litespeed-body { border-top: none; } } .litespeed-body table { border-collapse: collapse; width: 100%; } .litespeed-body .litespeed-width-auto { width: auto; } /* outside stripped table */ .litespeed-description { color: #666; font-size: 13px; margin: 1.5rem 0; max-width: 960px; } .litespeed-desc-wrapper{ display: inline-block; margin-left: 10px; } /* inside stripped table */ .litespeed-desc { font-size: 12px; font-weight: normal; color: #7a919e; margin: 10px 0; line-height: 1.7; /*max-width: 840px;*/ } .litespeed-desc + .litespeed-desc { margin-top: -5px; } td > .litespeed-desc:first-child { margin-top: 0px; line-height: 2.24; } .litespeed-h3 { line-height: 18px; color: #264d73; font-size: 18px; font-weight: 600; margin: 2px 0; } .litespeed-div .submit { margin-top: 0; } @media screen and (min-width: 681px) { .litespeed-div { display: inline-block; min-width: 100px; } .litespeed-div .submit { margin: 5px; padding: 5px; } } @media screen and (max-width: 680px) { .litespeed-desc + .litespeed-desc.litespeed-left20 { margin-left: 0 !important; } .litespeed-desc .litespeed-callout.notice-warning.inline { word-break: break-word; } } .litespeed-h1 { display: inline-block; } h3 .litespeed-learn-more { font-size: 12px; font-weight: normal; color: #7a919e; margin-left: 30px; } .litespeed-wrap code { color: #666; background-color: #dde9f5; border-radius: 3px; font-size: 11px; font-style: normal; } .litespeed-wrap ul { margin-left: 2em; } .litespeed-wrap i { font-size: 13px; line-height: 16px; } .litespeed-wrap .litespeed-desc i { font-size: 12px; } .litespeed-wrap p { margin: 1em 0; } .litespeed-wrap p.submit { margin-bottom: 0; } .litespeed-desc p { margin-left: 0; } .litespeed-title, .litespeed-title-short { font-size: 18px; border-bottom: 1px solid #cccccc; margin: 2.5em 0px 1.5em 0; display: table; padding-right: 50px; padding-left: 3px; padding-bottom: 3px; } .litespeed-title .button { margin-left: 1rem; margin-bottom: 5px; vertical-align: middle; } .litespeed-title .litespeed-quic-icon { margin-right: 6px; } .litespeed-title a, .litespeed-title-short a { text-decoration: none; } .litespeed-title-short { padding-right: 20px; } .litespeed-title-section { margin: 2em -20px 12px -20px; padding: 12px 20px 12px 20px; border-bottom: 1px solid #eee; font-size: 1.2em; display: block; border-top: 1px solid #f1f1f1; } .litespeed-postbox .litespeed-title { display: flex; align-items: center; } .litespeed-title-right-icon { margin-left: auto; font-weight: normal; } .litespeed-list li:before { content: '>'; color: #cc3d6a; } .litespeed-wrap a.disabled { cursor: not-allowed; opacity: 0.5; text-decoration: none; color: #72777c; } /* ======================================= LAYOUT - table ======================================= */ .litespeed-table { font-size: 14px; } .litespeed-body tbody > tr > th { padding-left: 20px; } .litespeed-body tbody th { vertical-align: top; text-align: left; padding: 18px 10px 20px 0; width: 200px; font-weight: 600; } .litespeed-body td { padding: 15px 10px; line-height: 1.3; vertical-align: middle; } .litespeed-body .widefat td input + p { margin-top: 0.8em; } .litespeed-body .striped > tbody > :nth-child(even) .notice { background-color: #f9f9f9; box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.05); border-top: 1px solid #e5e5e5; border-bottom: 1px solid #e5e5e5; border-right: 1px solid #e5e5e5; } .litespeed-body .striped > tbody > :nth-child(even) .notice:first-child { margin-top: 0; } /* small table inside */ .litespeed-body .litespeed-vary-table { margin-top: -5px; width: 250px; margin-bottom: 20px; } .litespeed-body .litespeed-vary-table td { width: 50%; padding: 5px 0px; } .litespeed-table-compact td, .litespeed-table-compact th { padding: 0.5rem 0.75rem; } /* ======================================= LAYOUT - block ======================================= */ .litespeed-block, .litespeed-block-tiny { border: 1px dotted #cccccc; border-radius: 5px; display: flex; flex-wrap: wrap; padding: 0.75rem 1.25rem; margin-bottom: 5px; } .litespeed-block-tiny { max-width: 670px; } .litespeed-col { flex: 0 0 30%; padding-right: 2rem; } .litespeed-col:last-child, .litespeed-col-auto:last-child { padding-right: 0; } .litespeed-col-auto { padding-right: 2rem; } .litespeed-col-br { flex: 0 0 100%; border-top: 1px dotted #cccccc; } .litespeed-col-inc { display: inline-block; margin-top: 16px; min-width: 150px; font-weight: bold; } .litespeed-block h4:first-child, .litespeed-block .litespeed-form-label:not(.litespeed-form-label--toggle):first-child { margin-top: 0.5rem; } .litespeed-block .litespeed-callout:last-child { margin-bottom: 0; } @media screen and (max-width: 600px) { .litespeed-block { flex-direction: column; } .litespeed-block .litespeed-col { padding-right: 0; } } /* ======================================= CARDS LINKS ======================================= */ .litespeed-cards-wrapper, .litespeed-panel-wrapper { display: flex; width: 100%; flex-flow: row wrap; justify-content: flex-start; } .litespeed-cards-wrapper { margin: -10px -15px -10px -15px; } .litespeed-panel { text-decoration: none; display: flex; justify-content: space-between; padding: 6px 8px 4px 5px; width: 322px; margin: 15px 5px 15px 15px; min-height: 75px; box-sizing: border-box; background: #f9fafc; transition: 0.25s; } .litespeed-panel:hover { border: 1px solid #6699cc; box-shadow: none; } .litespeed-panel-wrapper-icon { width: 25%; height: 100%; } [class*='litespeed-panel-icon-'] { background-size: contain; width: 60px; height: 60px; margin: 5px; background-repeat: no-repeat; display: inline-block; } .litespeed-panel-icon-all { background-image: url('../img/icons/all.svg'); } .litespeed-panel-icon-revision { background-image: url('../img/icons/revision.svg'); } .litespeed-panel-icon-orphaned_post_meta { background-image: url('../img/icons/revision.svg'); } .litespeed-panel-icon-auto_draft { background-image: url('../img/icons/auto_draft.svg'); } .litespeed-panel-icon-trash_post { background-image: url('../img/icons/trash_post.svg'); } .litespeed-panel-icon-spam_comment { background-image: url('../img/icons/spam_comment.svg'); } .litespeed-panel-icon-trash_comment { background-image: url('../img/icons/trash_comment.svg'); } .litespeed-panel-icon-trackback-pingback { background-image: url('../img/icons/trackback-pingback.svg'); } .litespeed-panel-icon-expired_transient { background-image: url('../img/icons/expired_transient.svg'); } .litespeed-panel-icon-all_transients { background-image: url('../img/icons/all_transients.svg'); } .litespeed-panel-icon-optimize_tables { background-image: url('../img/icons/optimize_tables.svg'); } .litespeed-panel-icon-purge-front { background-image: url('../img/icons/purge-front.svg'); } .litespeed-panel-icon-purge-pages { background-image: url('../img/icons/purge-pages.svg'); } .litespeed-panel-icon-purge-cssjs { background-image: url('../img/icons/purge-cssjs.svg'); } .litespeed-panel-icon-purge-object { background-image: url('../img/icons/purge-object.svg'); } .litespeed-panel-icon-purge-opcache { background-image: url('../img/icons/purge-opcache.svg'); } .litespeed-panel-icon-purge-all { background-image: url('../img/icons/purge-all.svg'); } .litespeed-panel-icon-empty-cache { background-image: url('../img/icons/empty-cache.svg'); } .litespeed-panel-icon-purge-403 { background-image: url('../img/icons/purge-403.svg'); } .litespeed-panel-icon-purge-404 { background-image: url('../img/icons/purge-404.svg'); } .litespeed-panel-icon-purge-500 { background-image: url('../img/icons/purge-500.svg'); } .litespeed-panel-top-right-icon-cross { background-image: url('../img/icons/cross_icon.svg'); } .litespeed-panel-top-right-icon-tick { background-image: url('../img/icons/success_icon.svg'); } .litespeed-panel-content { width: 75%; height: 100%; margin-top: 7px; } .litespeed-panel-para { color: #264d73; font-size: 12px; line-height: 1.45; } .litespeed-panel .litespeed-h3 { font-size: 14px; } .litespeed-panel-counter { color: #3abfbf; } .litespeed-panel-counter-red { color: #cc3d6a; } .litespeed-panel-wrapper-top-right { width: 10%; height: 100%; text-align: right; } .litespeed-panel-top-right-icon-tick, .litespeed-panel-top-right-icon-cross { background-size: contain; width: 20px; height: 20px; background-repeat: no-repeat; display: inline-block; } /* ======================================= BUTTONS ======================================= */ /* .litespeed-wrap .button{ background:#fff; } */ .litespeed-wrap .button-link { height: auto; line-height: inherit; font-size: inherit; box-shadow: none; } .litespeed-wrap .button-link:hover, .litespeed-wrap .button-link:focus { box-shadow: none; background: none; } .litespeed .litespeed-btn-danger-bg, .litespeed-wrap .litespeed-btn-danger-bg, .litespeed-btn-danger-bg { background: #dc3545; color: #fff; border: 1px solid #cc3d6a; box-shadow: 0 1px 0 rgba(177, 93, 93, 0.5); } .litespeed .litespeed-btn-danger, .litespeed-wrap .litespeed-btn-danger, .litespeed-btn-danger { background: #fff; color: #cc3d6a; border: 1px solid #cc3d6a; box-shadow: 0 1px 0 rgba(177, 93, 93, 0.5); } .litespeed .litespeed-btn-danger:hover, .litespeed-wrap .litespeed-btn-danger:hover, .litespeed-btn-danger:hover { border-color: #ab244e; background: #cc3d6a; color: #fff; } .litespeed .litespeed-btn-warning, .litespeed-wrap .litespeed-btn-warning, .litespeed-btn-warning { background: #fff; color: #e59544; border: 1px solid #e59544; box-shadow: 0 1px 0 rgba(249, 166, 82, 0.55); } .litespeed .litespeed-btn-warning:hover, .litespeed-wrap .litespeed-btn-warning:hover, .litespeed-btn-warning:hover { border-color: #e59544; background: #e59544; color: #fff; } .litespeed .litespeed-btn-success, .litespeed-wrap .litespeed-btn-success, .litespeed-btn-success { background: #fff; color: #36b0b0; border: 1px solid #36b0b0; box-shadow: 0 1px 0 rgba(73, 160, 160, 0.55); } .litespeed .litespeed-btn-success:hover, .litespeed-wrap .litespeed-btn-success:hover, .litespeed-btn-success:hover { border-color: #36b0b0; background: #36b0b0; color: #fff; } .litespeed-wrap .button-primary { background: #528ac6; border-color: #538ac6 #2264ad #2264ad; color: #fff; box-shadow: 0 1px 0 #2264ad; text-shadow: 0 -1px 1px #2264ad, 1px 0 1px #2264ad, 0 1px 1px #2264ad, -1px 0 1px #2264ad; } .litespeed-wrap .button-primary:focus, .litespeed-wrap .button-primary:hover { background: #5891ce; border-color: #2264ad; color: #fff; } .litespeed-wrap .button-primary:hover { box-shadow: 0 1px 0 #2264ad; } .litespeed-wrap .button-primary:focus { background: #5891ce; border-color: #2264ad; color: #fff; box-shadow: 0 1px 0 #0073aa, 0 0 2px 1px #33b3db; } .litespeed .litespeed-btn-primary, .litespeed-wrap .litespeed-btn-primary, .litespeed-btn-primary { color: #538ac6; border: 1px solid #538ac6; -moz-box-shadow: 0 0 0 1px rgba(83, 138, 198, 0.25); -webkit-box-shadow: 0 0 0 1px rgba(83, 138, 198, 0.25); box-shadow: 0 0 0 1px rgba(83, 138, 198, 0.25); } .litespeed .litespeed-btn-primary:hover, .litespeed-wrap .litespeed-btn-primary:hover, .litespeed-btn-primary:hover { background: #538ac6; border-color: #538ac6; color: #fff; } .litespeed-wrap .button:not(.litespeed-btn-large) .dashicons { position: relative; top: -0.075em; vertical-align: middle; } .litespeed-wrap .button:not(:first-child) { margin-left: 5px; } .litespeed-wrap .button + .button { margin-left: 10px; } .litespeed-info-button { color: #c8c8c8; padding: 0; -webkit-appearance: none; border: none; background: none; vertical-align: middle; line-height: inherit; text-decoration: none; } .litespeed-info-button .dashicons { font-size: 16px; vertical-align: middle; } .litespeed-btn-pie { -webkit-appearance: none; background: none; border: none; border-radius: 0; box-shadow: none; padding: 0; margin: 0; top: -0.125em; } /* ======================================= BUTTONS - sizes ======================================= */ .litespeed-wrap .litespeed-btn-tiny { padding: 2px 8px; line-height: 1.5; height: auto; } .litespeed-wrap .litespeed-btn-mini { padding: 0 8px 1px; font-size: 12px; font-weight: 600; margin: 5px 0; } .litespeed-wrap .litespeed-btn-mini .dashicons.dashicons-image-rotate { padding-top: 3px; font-size: 18px; } .litespeed-wrap .litespeed-btn-mini .dashicons { padding-top: 2px; } .litespeed-wrap .litespeed-btn-large { font-size: 1.5em; padding: 0.75em 1.5em; margin: 0 0.25em; height: auto; } .litespeed-wrap .litespeed-btn-large .dashicons { font-size: 1.25em; width: auto; } /* ======================================= SWITCH ======================================= */ .litespeed-switch { font-size: 14px; font-weight: 600; margin: 0 0 0; display: inline-flex; position: relative; } .rtl .litespeed-switch { flex-direction: row-reverse; } .litespeed-switch input:checked:active + label { box-shadow: 0 2px 0 rgba(27, 146, 146, 0.7), inset 0 2px 5px -3px rgba(0, 0, 0, 0.5); } .litespeed-switch input:checked + label { background-color: #36b0b0; color: #fff; border: 1px solid #36b0b0; box-shadow: 0 2px 0 #1b9292; z-index: 2; text-shadow: 0 -1px 1px #1b9292, 1px 0 1px #1b9292, 0 1px 1px #1b9292, -1px 0 1px #1b9292; } .litespeed-switch label { font-size: 14px; display: inline-block; min-width: 72px; background-color: #f9fafc; font-weight: 400; text-align: center; padding: 6px 12px 5px 12px; cursor: pointer; border: 1px solid #ccc; border-bottom: none; box-shadow: 0 2px 0 #ccc; position: relative; } .litespeed-switch label:not(:last-child) { margin-right: -1px; } .litespeed-switch label:last-child { border-top-right-radius: 3px; border-bottom-right-radius: 3px; } .litespeed-switch label:first-of-type { border-top-left-radius: 3px; border-bottom-left-radius: 3px; } .litespeed-switch input:hover + label { border-color: #1a9292; box-shadow: 0 2px 0 #1a9292; z-index: 2; color: #117171; } .litespeed-switch input:focus + label { color: #117171; box-shadow: 0 0px 0px 2px rgba(28, 138, 128, 0.85); border-color: transparent; z-index: 2; } .litespeed-switch input:focus + label + input + input:hover + label, .litespeed-switch input:focus + label + input:hover + label { z-index: 1; } .litespeed-switch input:active + label { box-shadow: 0 2px 0 #1b9292, inset 0 2px 5px -3px rgba(0, 0, 0, 0.5); } .litespeed-switch input:checked:hover + label, .litespeed-switch input:checked:focus + label { background-color: #36b0b0; color: #fff; } .litespeed-switch input { display: inline-block; position: absolute; z-index: -1; margin: 0; } .litespeed-cache-purgeby-text { margin: 0; display: inline-block; } /* ======================================= TOGGLE ======================================= */ .litespeed-toggle-stack { display: flex; flex-direction: column; } .litespeed-toggle-stack .litespeed-toggle-wrapper { justify-content: space-between; } .litespeed-toggle-wrapper { display: flex; align-items: center; } .litespeed-toggle-wrapper + .litespeed-toggle-wrapper { margin-top: 0.75rem; } .litespeed-toggle { position: relative; overflow: hidden; min-width: 58px; height: 21px; /*margin-left: 1.2rem;*/ } .litespeed-toggle-group { position: absolute; width: 200%; top: 0; bottom: 0; left: 0; transition: left 0.35s; -webkit-transition: left 0.35s; -moz-user-select: none; -webkit-user-select: none; } .litespeed-toggle-on { position: absolute; top: 0; bottom: 0; left: 0; right: 50%; margin: 0; border: 0; border-radius: 0; } .litespeed-toggle-on.litespeed-toggle-btn { padding-right: 24px; } .litespeed-toggle-off.litespeed-toggle-btn { padding-left: 24px; } .litespeed-toggle-handle { position: relative; margin: 0 auto; padding-top: 0px; padding-bottom: 0px; height: 100%; width: 0px; border-width: 0 1px; } .litespeed-toggle-off { position: absolute; top: 0; bottom: 0; left: 50%; right: 0; margin: 0; border: 0; border-radius: 0; } .litespeed-toggleoff .litespeed-toggle-group { left: -100%; } .litespeed-toggle-btn { display: inline-block; padding: 5px 10px; margin-bottom: 0; font-size: 14px; font-weight: 400; line-height: 1.42857143; text-align: center; white-space: nowrap; vertical-align: middle; cursor: pointer; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; background-image: none; border: 1px solid transparent; border-radius: 4px; } .litespeed-toggle-btn-primary { color: #fff; background-color: #36b0b0; border-color: #36b0b0; } .litespeed-toggle-btn-default { color: #333; background-color: #fff; border-color: #ccc; } .litespeed-toggle-btn-success:hover, .litespeed-toggle-btn-success:focus, .litespeed-toggle-btn-success:active, .litespeed-toggle-btn-success.litespeed-toggle-active { color: #fff; background-color: #00bfbf; border-color: #6699cc; } .litespeed-toggle-btn-default:hover, .litespeed-toggle-btn-default:focus, .litespeed-toggle-btn-default:active, .litespeed-toggle-btn-default.litespeed-toggle-active { color: #333; background-color: #e6e6e6; border-color: #adadad; } .litespeed-toggle-btn:active, .litespeed-toggle-btn.litespeed-toggle-active { background-image: none; outline: 0; -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); } .litespeed-toggle-btn-default:active, .litespeed-toggle-btn-default.litespeed-toggle-active { background-image: none; } /* ======================================= LABEL/TAG ======================================= */ [class*='litespeed-label-'] { display: inline; padding: 0.2em 0.6em 0.3em; font-size: 75%; font-weight: bold; line-height: 1; color: #fff; text-align: center; white-space: nowrap; vertical-align: baseline; border-radius: 0.25em; } [class*='litespeed-label-']:hover, [class*='litespeed-label-']:focus { color: #fff; text-decoration: none; cursor: pointer; } [class*='litespeed-label-']:empty { display: none; } .litespeed-label-regular { font-size: 1em; } .litespeed-label-default { background-color: #777; } .litespeed-label-default[href]:hover, .litespeed-label-default[href]:focus { background-color: #5e5e5e; } .litespeed-label-primary { background-color: #337ab7; } .litespeed-label-primary[href]:hover, .litespeed-label-primary[href]:focus { background-color: #286090; } .litespeed-label-success { background-color: #5cb85c; } .litespeed-label-success[href]:hover, .litespeed-label-success[href]:focus { background-color: #449d44; } .litespeed-label-info { background-color: #5bc0de; } .litespeed-label-info[href]:hover, .litespeed-label-info[href]:focus { background-color: #31b0d5; } .litespeed-label-warning { background-color: #f0ad4e; } .litespeed-label-warning[href]:hover, .litespeed-label-warning[href]:focus { background-color: #ec971f; } .litespeed-label-danger { background-color: #d9534f; } .litespeed-label-danger[href]:hover, .litespeed-label-danger[href]:focus { background-color: #c9302c; } /* ======================================= SHELL ======================================= */ .litespeed-shell { width: 98%; background: #141414; margin: 20px auto 0 10px; box-shadow: 0 0 5px rgba(0, 0, 0, 0.4); -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; position: relative; height: 224px; } .litespeed-shell-header { z-index: 999; position: absolute; top: 0; right: 0; width: 50px; height: 34px; padding: 5px 0; } .litespeed-shell-header-bg { opacity: 0.4; background-color: #cccccc; position: absolute; top: 0; bottom: 0; right: 0; left: 0; z-index: 4; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-top-radius: 3px; } .litespeed-shell-header-bar { position: absolute; top: 0; left: 0; z-index: 10; height: 2px; background-color: #f48024; } .litespeed-shell-header-icon-container { position: absolute; top: 10px; right: 10px; width: 29px; height: 34px; z-index: 6; } ul.litespeed-shell-body { position: absolute; top: 0; left: 0; right: 0; bottom: 0; overflow-y: scroll; margin: 0; padding: 5px; list-style: none; background: #141414; color: #45d40c; font: 0.8em 'Andale Mono', Consolas, 'Courier New'; line-height: 1.6em; -webkit-border-bottom-right-radius: 3px; -webkit-border-bottom-left-radius: 3px; -moz-border-radius-bottomright: 3px; -moz-border-radius-bottomleft: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; } .litespeed-shell-body li:before { content: '>'; position: absolute; left: 0; top: 0; } .litespeed-shell-body li { word-wrap: break-word; position: relative; padding: 0 0 0 15px; margin: 0; } .litespeed-widget-setting { background-color: #ecebdc; padding: 5px 14px; margin: 5px -15px; } /* ======================================= CALLOUT / NOTICE ======================================= */ .litespeed-callout { margin: 1.5rem 0; border-right: 1px solid #e5e5e5; border-top: 1px solid #e5e5e5; border-bottom: 1px solid #e5e5e5; background: #f9f9f9; } .litespeed-callout h4:not(:last-child) { margin-bottom: 0.5rem; margin-top: 1em; } .litespeed-callout p { margin-left: 0; } .litespeed-callout ol, .litespeed-callout ul { margin-left: 1em; } .litespeed-callout.notice-warning h4 { color: #e59544; } .litespeed-callout.notice-error h4 { color: #dc3232; } .litespeed-callout-bg { margin: 1.5rem 0; background: #f9f9f9; border-top: none; border-bottom: none; border-right: none; } /* ======================================= TICK / CHECKBOX ======================================= */ .litespeed-tick-wrapper { margin-left: -5px; } .litespeed-tick { display: inline-block; /* min-width: 125px; */ background: #f2f9ff; padding: 5px 0 5px 0px; border-radius: 3px; cursor: pointer; margin: 5px 5px 5px 0; } .litespeed-tick-list .litespeed-tick { display: block; margin-bottom: 3px; margin-top: 0; background: none; } .litespeed-tick-list .litespeed-tick input[type='checkbox'] { margin-left: 0; } .litespeed-tick-list .litespeed-tick label { color: inherit; } .litespeed-tick input[type='checkbox'] { height: 18px; width: 18px; vertical-align: middle; margin: 0 10px; -webkit-appearance: none; -moz-appearance: none; appearance: none; -webkit-border-radius: 3px; border-radius: 3px; cursor: pointer; } .litespeed-tick input[type='checkbox']:not(:disabled):hover { border-color: #538ac6; } .litespeed-tick input[type='checkbox']:active:not(:disabled) { border-color: #538ac6; } .litespeed-tick input[type='checkbox']:focus { outline: none; } .litespeed-tick input[type='checkbox']:checked { border-color: #538ac6; background-color: #538ac6; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; } .litespeed-tick input[type='checkbox']:checked:before { content: ''; display: block; width: 5px; height: 11px; border: solid #fff; border-width: 0 2px 2px 0; -webkit-transform: rotate(45deg); transform: rotate(45deg); margin-left: 5px; margin-top: -1px; cursor: pointer; } .litespeed-tick label { padding: 2px 0px 2px 0; font-size: 14px; color: #264d73; } .litespeed-tick label:hover { min-width: 115px; color: #6699cc; } /* ======================================= RADIO - vertical ======================================= */ .litespeed-radio-row { margin-bottom: 12px; position: relative; padding-left: 1.5rem; } .litespeed-radio-row input[type='radio'] { margin-top: 0; margin-bottom: 0; position: absolute; line-height: 1; left: 0; top: 0.7em; transform: translateY(-50%); } .litespeed-radio-row label { vertical-align: text-bottom; line-height: 1.4; } @media screen and (max-width: 782px) { .litespeed-radio-row { padding-left: 2rem; } } /* ======================================= FORM - layout ======================================= */ .litespeed-wrap .litespeed-float-submit { position: absolute; right: 0; top: -5px; margin-top: 0; } .rtl .litespeed-wrap .litespeed-float-submit { left: 10px; right: unset; } .litespeed-wrap .litespeed-float-resetbtn { position: absolute; right: 0; bottom: 20px; } .rtl .litespeed-wrap .litespeed-float-resetbtn { left: 10px; right: unset; } /* ======================================= FORM - utilities ======================================= */ .litespeed .litespeed-input-large { font-size: 20px; } .litespeed-input-long { width: 87%; } .litespeed-input-short2 { width: 150px; } .litespeed-input-short { width: 45px; } @media screen and (max-width: 680px) { .litespeed-input-short2 { width: 160px; } .litespeed-input-short { width: 50px; } } /* ======================================= FORM - elements ======================================= */ .litespeed-form-label { font-size: 1em; margin: 0.65rem 0; display: block; font-weight: 600; } .litespeed-form-label--toggle { margin: 0; display: inline-block; min-width: 110px; } input.litespeed-input[type='file'] { padding: 9px; min-width: 500px; border: 1px solid #ddd; box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.07); background-color: #fff; color: #32373c; outline: 0; transition: 50ms border-color ease-in-out; } .litespeed-body .litespeed-textarea-success { border-color: #6699cc; } input.litespeed-input-success { border-color: #28a745; } input.litespeed-input-warning { border-color: #e59544; } .litespeed-textarea { width: 60%; } .litespeed-textarea-recommended { display: flex; margin-top: -5px; } .litespeed-textarea-recommended .litespeed-desc { margin: 0; } .litespeed-textarea-recommended > div:first-child { margin-top: 1.7em; font-size: 12px; margin-right: 25px; } .litespeed-wrap .litespeed-collection-button { text-decoration: none; min-width: 30px; text-align: center; } .litespeed-collection-button[data-action='add'] { margin-top: -5px; margin-left: -5px; } .litespeed-collection-button .dashicons { vertical-align: baseline; } .litespeed-wrap .button:not(.litespeed-btn-large).litespeed-form-action .dashicons { font-size: 1.2em; vertical-align: middle; top: 0; } @media screen and (max-width: 680px) { .litespeed-body tbody > tr > th { display: block; padding: 18px 0 5px 12px; } .litespeed-body .litespeed-table td { display: block; max-width: 100%; } .litespeed-body .litespeed-table textarea, .litespeed-body .litespeed-table input.litespeed-regular-text { width: 100% !important; } .litespeed-wrap .litespeed-float-submit { display: none; } .litespeed-body { padding: 1px 10px 20px 15px; } .litespeed-body .regular-text:not(.litespeed-input-short) { width: 100%; } .litespeed-textarea-recommended { flex-direction: column; } .litespeed-textarea-recommended > div:first-child { margin-bottom: 1.7em; margin-top: 0; margin-right: 0; } .litespeed-switch { max-width: 100%; flex-wrap: wrap; } .litespeed-switch + .litespeed-warning { display: block; margin-top: 10px; } input.litespeed-input[type='file'] { max-width: calc(100% - 24px); min-width: 0; } .litespeed-body .litespeed-table .litespeed-row-flex { flex-direction: column; } } /* ======================================= ENTERPRISE NOTICE ======================================= */ .litespeed-ent-notice { position: absolute; left: 0; top: 0; right: 0; bottom: 0; background-color: #333; z-index: 999; opacity: 0.8; text-align: center; font-size: 3rem; color: #1865c5; } .litespeed-ent-notice-desc { position: relative; top: 30%; transform: rotate(-20deg); text-shadow: 2px 2px 4px #000000; } /* ======================================= PROMO BANNER ======================================= */ .litespeed-banner-promo, .litespeed-banner-promo-full { display: flex; padding: 0px; } .litespeed-banner-promo-full { margin: 0px; padding: 0px; } .litespeed-banner-promo-logo { background-image: url(../img/lscwp-logo_90x90.png); background-size: contain; width: 90px; background-repeat: no-repeat; display: inline-block; } .litespeed-banner-promo-full .litespeed-banner-promo-logo { margin: 0px; width: 90px; height: 90px; } .litespeed-banner-promo-content { margin-left: 25px; } .litespeed-banner-promo-full .litespeed-banner-promo-content { width: 75%; } .litespeed-banner-promo-content h1 { font-weight: 600; color: #538ac6; margin-top: 10px; } .litespeed-banner-title { font-size: 1.3em; margin: 8px 0px 5px 0px; } .litespeed-banner-promo-slacklogo { background-image: url('../img/slack-logo.png'); background-size: contain; width: 75px; height: 75px; background-repeat: no-repeat; display: inline-block; padding: 0px; flex: 0 0 5%; } .litespeed-banner-promo .litespeed-banner-promo-slack-line1 { font-size: 18px; margin-top: 0px; line-height: 21px; } .litespeed-banner-promo .litespeed-banner-promo-slack-textlink { color: #e59544; text-decoration: none; } .litespeed-banner-promo .litespeed-banner-promo-slack-textlink:hover { opacity: 0.8; } .litespeed-banner-promo-slack-line2 { font-size: 15px; margin: 0px; line-height: 0.75em; } .litespeed-banner-promo-slack-link { color: #888888; } a.litespeed-btn-xs.litespeed-banner-promo-slack-btn { margin: 0px 5px; } /* ======================================= PROMO BANNER - QC ======================================= */ .litespeed-banner-promo-qc { display: flex; } .litespeed-banner-promo-qc h2 { line-height: 1.4; } .litespeed-banner-promo-qc-content { display: flex; align-items: center; } .litespeed-banner-promo-qc-description { flex-basis: 50%; padding-right: 2rem; } .litespeed-banner-promo-qc-description p { font-size: 14px; } .litespeed-banner-promo-qc-description .button { margin-right: 1.5rem; } .litespeed-tweet-preview { border-radius: 5px; line-height: 1.3125; box-shadow: 1px 1px 0.5em rgba(0, 0, 0, 0.3); margin: 0.5em 1em 1em 0; padding: 1em; max-width: 480px; display: flex; } .litespeed-tweet-preview:after { content: ''; display: block; clear: both; } .litespeed-tweet-preview p:first-child { margin-top: 0; } .litespeed-tweet-preview-title { color: #777; margin-top: 0.9em; font-weight: normal; font-size: 12px; margin-bottom: 0; margin-top: 0.9em; } .litespeed-tweet-text { font: 14px system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Ubuntu, 'Helvetica Neue', sans-serif; line-height: 1.3125; } .litespeed-tweet-cta { text-align: right; margin-top: 1em; } .litespeed-tweet-cta a { background-color: #1da1f2; line-height: 1.3125; color: #fff; font-weight: bold; display: inline-flex; padding: 0.55em 1em; font-size: 14px; border-radius: 99em; text-decoration: none; } .litespeed-tweet-cta a:hover { background-color: #1e98e1; } .litespeed-tweet-cta a svg { width: 16px; height: 18px; margin-right: 0.5em; } .litespeed-tweet-cta a svg path { fill: currentColor; } .litespeed-tweet-img { width: calc(240px + 1rem); padding-right: 1rem; box-sizing: border-box; } .litespeed-tweet-img img { max-width: 100%; vertical-align: middle; } .litespeed-tweet-img + p { margin-top: 0; } /* ======================================= admin -> media lib icon ======================================= */ .litespeed-media-href { display: inline-table; } [class*='litespeed-icon-media-'] { background-size: contain; width: 25px; height: 25px; vertical-align: middle; margin: 0; background-repeat: no-repeat; display: inline-block; } [class*='litespeed-icon-media-']:hover { opacity: 0.7; } .litespeed-icon-media-webp { background-image: url('../img/icons/img_webp.svg'); } .litespeed-icon-media-webp-disabled { background-image: url('../img/icons/img_webp_disabled.svg'); } .litespeed-icon-media-optm { background-image: url('../img/icons/img_optm.svg'); } .litespeed-icon-media-optm-disabled { background-image: url('../img/icons/img_optm_disabled.svg'); } p.litespeed-media-p { margin-bottom: 1px !important; } p.litespeed-txt-webp { color: #83b04a; } p.litespeed-txt-ori { color: #5967b3; } p.litespeed-txt-disabled { color: #ced2d9; } .litespeed-media-svg { vertical-align: middle; margin: 5px; width: 25px; height: auto; } @keyframes litespeed-circle-chart-fill { to { stroke-dasharray: 0 100; } } /* ======================================= PIE chart ======================================= */ .litespeed-pie { vertical-align: middle; margin: 5px 5px 5px 0; } circle.litespeed-pie_bg { stroke: #efefef; stroke-width: 2; fill: none; } circle.litespeed-pie_circle { animation: litespeed-circle-chart-fill 2s reverse; transform: rotate(-90deg); transform-origin: center; animation: litespeed-pie-fill 2s reverse; /* 1 */ stroke: #28a745; stroke-width: 2; stroke-linecap: round; fill: none; } .litespeed-pie.litespeed-pie-tiny { margin: 0 2px 0 0; } .litespeed-pie.litespeed-pie-tiny text { font-weight: bold; fill: #828282; } .litespeed-pie.litespeed-pie-tiny circle { stroke-linecap: initial; } .litespeed-pie-tiny circle.litespeed-pie_bg, .litespeed-pie-tiny circle.litespeed-pie_circle { stroke-width: 3; } .litespeed-pie-tiny circle.litespeed-pie_bg { stroke: #eee; } .litespeed-pie-success circle.litespeed-pie_circle { stroke: #28a745; } .litespeed-pie-warning circle.litespeed-pie_circle { stroke: #e67700; } .litespeed-pie-danger circle.litespeed-pie_circle { stroke: #c7221f; } g.litespeed-pie_info text { dominant-baseline: central; text-anchor: middle; font-size: 11px; } .litespeed-promo-score g.litespeed-pie_info text { font-size: 14px; font-weight: 600; } .litespeed-pie-success g.litespeed-pie_info text { fill: #28a745; } .litespeed-pie-warning g.litespeed-pie_info text { fill: #e67700; } .litespeed-pie-danger g.litespeed-pie_info text { fill: #c7221f; } g.litespeed-pie_info .litespeed-pie-done { fill: #28a745; font-size: 15px; } /* ======================================= VIEW - multiple cdn mapping ======================================= */ [data-litespeed-cdn-mapping]:first-child [data-litespeed-cdn-mapping-del] { display: none; } .litespeed-cdn-mapping-col1 { padding-right: 2rem; max-width: 35%; } .litespeed-cdn-mapping-col1 .litespeed-input-long { width: 100%; } .litespeed-cdn-mapping-col2 { padding-top: 0.25rem; } .litespeed-cdn-mapping-col1 label { position: relative; } [data-litespeed-cdn-mapping-del] { position: absolute; right: -6px; top: -6px; } @media screen and (max-width: 600px) { .litespeed-cdn-mapping-col1 { max-width: 100%; } } /* ======================================= VIEW - crawler ======================================= */ .litespeed-crawler-curr { vertical-align: middle; height: 20px; margin-left: 10px; } #cookie_crawler > p:first-child { margin-top: 5px; } .litespeed-crawler-sitemap-nav { display: flex; justify-content: space-between; } .litespeed-crawler-sitemap-nav > div { margin-top: 10px; } @media screen and (max-width: 680px) { .litespeed-crawler-sitemap-nav { display: block; } .litespeed-table-responsive { clear: both; overflow-x: auto; -webkit-overflow-scrolling: touch; } .litespeed-table-responsive table { width: 100%; } .litespeed-table-responsive th { text-wrap: nowrap; } .litespeed-table-responsive [data-crawler-list].wp-list-table td:nth-child(2) { min-width: 115px; } .litespeed-wrap input[name='kw'] { width: 100% !important; } } /* ======================================= PROGRESS BAR ======================================= */ .litespeed-progress-bar { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-orient: vertical; -webkit-box-direction: normal; -ms-flex-direction: column; flex-direction: column; -webkit-box-pack: center; -ms-flex-pack: center; justify-content: center; color: #fff; text-align: center; background-color: #007bff; transition: width 0.6s ease; } .litespeed-progress-bar-yellow { background-color: #fbe100; } .litespeed-progress { display: -webkit-box; display: -ms-flexbox; display: flex; height: 12px; overflow: hidden; font-size: 0.75rem; background-color: #e9ecef; border: 1px solid #dddddd; border-radius: 8px; width: 75%; margin: 5em 1em 1.5em 1em !important; } /* ======================================= PROGRESS BAR - modal ======================================= */ .litespeed-modal { margin-top: -8px; } .litespeed-modal .litespeed-progress { margin-left: -8px; margin-right: -8px; } /* ======================================= GUIDANCE ======================================= */ .litespeed-guide { border: 1px solid #73b38d; max-width: 50%; padding: 20px; } .litespeed-guide h2 { color: #73b38d; border-bottom: 1px solid #73b38d; display: table; padding-right: 50px; padding-left: 3px; padding-bottom: 3px; } .litespeed-guide li { font-size: 15px; line-height: 30px; margin: 10px 10px 10px 16px; } .litespeed-guide li.litespeed-guide-done:before { content: '\2713'; font-size: 26px; color: #73b38d; margin-left: -37px; margin-right: 18px; opacity: 1; } .litespeed-guide li.litespeed-guide-done { opacity: 0.9; } /* ======================================= VIEW - image optimization ======================================= */ .litespeed-image-optim-summary-wrapper { padding: 0; } .litespeed-cache_page_litespeed-img_optm .nav-tab-wrapper, .litespeed-cache_page_litespeed-cdn .nav-tab-wrapper { border-bottom-color: #e5e5e5; } .litespeed-cache_page_litespeed-img_optm .litespeed-body, .litespeed-cache_page_litespeed-cdn .litespeed-body { box-shadow: none; } .litespeed-cache_page_litespeed-img_optm .litespeed-wrap .nav-tab:not(.nav-tab-active), .litespeed-cache_page_litespeed-cdn .litespeed-wrap .nav-tab:not(.nav-tab-active) { border-bottom-color: #e5e5e5; } .litespeed-cache_page_litespeed-img_optm .nav-tab-active, .litespeed-cache_page_litespeed-cdn .nav-tab-active { border-left-color: #e5e5e5; border-right-color: #e5e5e5; border-top-color: #e5e5e5; position: relative; z-index: 2; } .litespeed-cache_page_litespeed-img_optm [data-litespeed-layout='summary'], .litespeed-cache_page_litespeed-cdn [data-litespeed-layout='qc'] { margin: -2px -21px -21px -21px; background: #f0f0f1; } .litespeed-column-secondary { background: #f9fafc; } .litespeed-column-with-boxes .postbox { border-color: #e5e5e5; } .litespeed-column-with-boxes .litespeed-width-7-10 { padding: 0; } @media screen and (min-width: 815px) { .litespeed-column-with-boxes > div.litespeed-column-left { padding-right: 25px; } } .litespeed-column-with-boxes > div.litespeed-column-right { background: #f1f1f1; padding-top: 0; padding-right: 0; padding-left: 0; } .litespeed-column-with-boxes > div.litespeed-column-right .litespeed-postbox:last-child { margin-bottom: 0; } .litespeed-image-optim-summary, .litespeed-column-left-inside { box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04); position: relative; padding: 1px 20px 20px 20px; background: #fff; border: 1px solid #e5e5e5; } .litespeed-image-optim-summary-footer, .litespeed-column-with-boxes-footer { border-top: 1px solid #efefef; background: #f9f9f9; padding: 20px; margin: 20px -20px -20px; } .litespeed-help-btn-icon { text-decoration: none; margin-left: 10px; color: #c8c8c8; } .litespeed-postbox-imgopt-info .litespeed-flex-container { align-items: center; } .litespeed-postbox-imgopt-info .litespeed-flex-container:not(:last-child) { margin-bottom: 0.65em; } .litespeed-postbox-imgopt-info .litespeed-flex-container p:first-child { margin-top: 0; } .litespeed-image-optim-summary > h3:first-child, .litespeed-column-left-inside > h3:first-child { margin-top: 1.6em; font-size: 1.2em; } .litespeed-image-optim-summary > h3:first-child .litespeed-quic-icon, .litespeed-column-left-inside > h3:first-child .litespeed-quic-icon { width: 1.2em; height: 1.4em; background-size: contain; margin-right: 0.2rem; } .litespeed-img-optim-actions { margin-top: 1.65em; display: flex; align-items: flex-end; flex-wrap: wrap; } .litespeed-img-optim-actions .button-primary { font-size: 1.2em; margin-right: 1em; padding: 0.35em 0.85em; min-width: 210px; text-align: center; } @media screen and (max-width: 1079px) { .litespeed-postbox-imgopt-info svg { height: 50px; width: 50px; } } @media screen and (max-width: 814px) { .litespeed-column-with-boxes > div:first-child { padding-right: 0; margin-bottom: 1rem; } } @media screen and (max-width: 680px) { .litespeed-img-optim-actions .button + .button.button-secondary { margin-left: 0; margin-top: 10px; } } /* ======================================= VIEW - image optm media row ======================================= */ .imgoptm.column-imgoptm a[data-balloon-pos] { border-bottom: 1px dashed; } .imgoptm.column-imgoptm p { margin-bottom: 0.25em; margin-top: 0; } .imgoptm.column-imgoptm p + .row-actions { margin-top: 0.5em; } .fixed .column-lqip { width: 6rem; } .litespeed-media-lqip img { max-width: 62px; max-height: 62px; } .litespeed-media-href { font-size: 12px; } /* ======================================= VIEW - log view ======================================= */ .litespeed-log-view-wrapper { margin: 1.5em 0; } /* ======================================= VIEW - dashboard ======================================= */ .litespeed-dashboard-group { margin-bottom: 1rem; } .litespeed-dashboard-group > .litespeed-flex-container { margin: 0 -10px; min-width: 100%; width: auto; } .litespeed-dashboard .litespeed-postbox { margin: 10px; } .litespeed-dashboard-title a { text-decoration: none; margin-left: 0.25rem; } .litespeed-dashboard-title--w-btn { display: flex; align-items: center; } .litespeed-dashboard-title--w-btn .button { font-weight: normal; } .litespeed-postbox-footer .button-small { vertical-align: middle; } .litespeed-postbox .button.button-small .dashicons, .litespeed-dashboard-title--w-btn .button.button-small .dashicons { font-size: 1rem; top: 0.05em; vertical-align: middle; margin-left: -5px; } .litespeed-dashboard-header { display: flex; align-items: center; } .litespeed-postbox p.litespeed-dashboard-stats-total + p.litespeed-dashboard-stats-total { margin-top: 1.2em; } .litespeed-dashboard-header:first-child { margin-top: 1.5rem; } .litespeed-dashboard-header hr { align-self: center; flex-grow: 1; margin-left: 15px; margin-right: 15px; } .litespeed-dashboard-header hr:last-child { margin-right: 0; } .litespeed-dashboard-header .litespeed-learn-more { font-weight: normal; text-decoration: none; margin-top: -2px; color: #5e7380; } .litespeed-dashboard-stats h3 { text-transform: uppercase; font-size: 12px; font-weight: normal; margin-bottom: 0; margin-top: 1.2em; color: #777; } .litespeed-dashboard-stats h3 + p { margin-top: 0; margin-bottom: 0; } .litespeed-dashboard-stats .litespeed-desc { color: #777; } .litespeed-dashboard-stats p strong { font-size: 2em; font-weight: normal; margin-right: 5px; } .litespeed-dashboard-stats-wrapper { display: flex; position: relative; } .litespeed-dashboard-stats-wrapper .litespeed-postbox { margin: 0; min-width: 20%; } .litespeed-dashboard-stats-wrapper .litespeed-postbox .inside .litespeed-title, .litespeed-dashboard-group .litespeed-postbox .inside .litespeed-title { font-size: 14px; } .litespeed-postbox .inside .litespeed-title a { font-size: 13px; } .litespeed-dashboard-stats-wrapper .litespeed-postbox:not(:last-child) { margin-right: -1px; } .litespeed-dashboard-stats-wrapper .litespeed-postbox:not(:first-child) { border-left-color: #f9f9f9; } .litespeed-dashboard-stats-wrapper .litespeed-dashboard-stats p strong { font-size: 1.4rem; } .litespeed-dashboard-stats-wrapper .litespeed-pie { width: 60px; height: 60px; } .litespeed-dashboard-stats-wrapper .litespeed-flex-container + p:not(:last-child) { margin-bottom: 0.55em; } .litespeed-dashboard-stats-payg { color: #777; } .litespeed-dashboard-stats-payg strong { color: #444; } .postbox .inside > p.litespeed-dashboard-stats-payg { margin-top: 1.35em; } .postbox .inside > p.litespeed-dashboard-stats-payg:last-child { margin-bottom: -5px !important; } .litespeed-postbox p.litespeed-dashboard-stats-total { padding: 0.75em 20px 0 20px; border-top: 1px dashed #eee; margin-top: 0.55em; margin-left: -20px; margin-right: -20px; margin-bottom: -0.55em !important; } .litespeed-postbox.litespeed-postbox-partner .inside { margin: 11px 0; } .litespeed-dashboard-stats-wrapper .litespeed-postbox.litespeed-postbox-partner h3.litespeed-title { color: #777; font-weight: normal; font-size: 13px; } .litespeed-postbox.litespeed-postbox-partner a { font-size: 1.35rem; font-weight: bold; text-decoration: none; margin-top: 5px; max-width: 100%; display: inline-block; } .litespeed-postbox.litespeed-postbox-partner a:hover { text-decoration: underline; } .litespeed-postbox.litespeed-postbox-partner img { max-width: 12rem; } .litespeed-dashboard-group .litespeed-postbox { width: calc(25% - 20px); display: flex; flex-direction: column; justify-content: space-between; } .litespeed-dashboard-group .litespeed-postbox-double { min-width: calc(50% - 20px); display: flex; justify-content: space-between; } .litespeed-postbox-double-content { display: flex; align-items: flex-start; justify-content: space-between; } .litespeed-postbox-double-content .litespeed-postbox-double-col { width: 50%; } .litespeed-postbox-double-content .litespeed-postbox-double-col:nth-child(2) { padding-left: 10px; } .litespeed-dashboard-group hr { margin: 1.5rem 0 0.75rem 0; } .litespeed-postbox .litespeed-postbox-refresh { text-decoration: none; color: #36b0b0; line-height: 1; vertical-align: top; margin-left: 0.5rem; margin-bottom: 0; } .litespeed-postbox .litespeed-postbox-refresh.button .dashicons { font-size: 22px; top: 0.05em; } .litespeed-postbox p:last-child { margin-bottom: 0; } .litespeed-label-dashboard { font-size: 0.92em; padding: 0.3em 0.6em 0.35em 0.6em; font-weight: normal; display: inline-block; margin-left: 8px; min-width: 2em; } .litespeed-label-dashboard:first-child { margin-left: 0; margin-right: 0.35em; } .litespeed-postbox .inside { padding: 0 20px 5px; } .litespeed-postbox .inside .litespeed-title { margin: 0 -20px 12px -20px; padding: 0px 20px 7px 20px; border-bottom: 1px solid #eee; font-size: 1.2em; } .litespeed-postbox .inside.litespeed-postbox-footer { border-top: 1px solid #efefef; background: #f9f9f9; padding: 20px; margin-bottom: 0px; margin-top: 0; } .litespeed-postbox-footer a, a.litespeed-redetect { text-decoration: none; } .litespeed-postbox .inside.litespeed-postbox-footer--compact { padding: 7px 15px 8px 15px; font-size: 12px; } .litespeed-postbox-imgopt .litespeed-pie { width: 55px; height: 55px; } .litespeed-postbox-imgopt .litespeed-flex-container { align-items: center; margin-bottom: 10px; } .litespeed-postbox-imgopt .litespeed-flex-container .litespeed-icon-vertical-middle + div h3 { margin-top: 0; } .litespeed-postbox-imgopt .litespeed-flex-container .litespeed-icon-vertical-middle + div p { line-height: 1.2; } .litespeed-postbox-imgopt .litespeed-postbox-double-col:last-child > *:first-child { margin-top: 7px; } .litespeed-postbox-pagespeed p:first-child { margin-top: 0; margin-bottom: 0; } .litespeed-postbox-score-improve { line-height: 45px; margin-top: 7px; font-size: 42px; } .litespeed-postbox-pagespeed .litespeed-padding-space:first-child { padding-left: 5px; padding-right: 5px; } .litespeed-link-with-icon { text-decoration: underline; margin-right: 0.25em; } .litespeed-link-with-icon .dashicons { vertical-align: baseline; position: relative; top: 0.1em; font-size: 1em; text-decoration: none; width: auto; margin-right: 0.5em; } .litespeed-link-with-icon.litespeed-icon-right .dashicons { margin-left: 0.5em; margin-right: 0; } .litespeed-warning-bg { background-color: #b58a09; color: white; } .litespeed-links-group:not(:last-child) { margin-bottom: 1em; } .litespeed-links-group > span:not(:last-child):after { content: '|'; margin: 0 10px; color: #ddd; font-size: 13px; } .litespeed-wrap p.litespeed-qc-dashboard-link { margin-left: 1rem; } .litespeed-right.litespeed-qc-dashboard-link .dashicons { margin-left: 0.5em; margin-right: 0; } .litespeed-score-col { flex-grow: 1; padding-right: 15px; } .litespeed-score-col .litespeed-text-md { font-size: 1.35rem; } .litespeed-score-col.litespeed-score-col--imp { text-align: right; padding-right: 0; } .litespeed-score-col--imp .litespeed-text-jumbo { line-height: 1; } .litespeed-wrap span[data-balloon-pos] { border-bottom: 1px dashed; } .litespeed-wrap span[aria-label][data-balloon-pos] { cursor: default; } .litespeed-postbox--quiccloud { border-color: #253545; } .litespeed-postbox--quiccloud.litespeed-postbox .inside .litespeed-title { background: #253545; color: #e2e4e5; margin-top: -11px; padding: 10px 15px; margin-left: -15px; margin-right: -15px; } .litespeed-postbox--quiccloud.litespeed-postbox .inside .litespeed-title a { color: #8abff8; } .litespeed-postbox--quiccloud.litespeed-postbox .inside .litespeed-title a:hover { color: #a5caf2; } .litespeed-overwrite{ display: inline-block; margin-left: 10px; } @media screen and (min-width: 1401px) { .litespeed-postbox--quiccloud.litespeed-postbox .inside .litespeed-title { padding-left: 20px; padding-right: 20px; margin-left: -20px; margin-right: -20px; } .litespeed-postbox .inside.litespeed-postbox-footer--compact { padding-left: 20px; padding-right: 20px; } } @media screen and (max-width: 1400px) and (min-width: 1024px) { .litespeed-dashboard-stats-wrapper .litespeed-postbox { flex-grow: 1; } .litespeed-postbox .inside { padding: 0 15px 5px; } .litespeed-dashboard-group .litespeed-postbox { width: calc(33.3333% - 20px); } .litespeed-dashboard-group .litespeed-postbox-double { min-width: calc(66.6666% - 20px); } } @media screen and (max-width: 1023px) { .litespeed-dashboard-stats-wrapper { flex-wrap: wrap; } .litespeed-dashboard-stats-wrapper .litespeed-postbox:not(:first-child) { border-left-color: #ccd0d4; } .litespeed-dashboard-stats-wrapper .litespeed-postbox { margin-top: -1px; min-width: calc(33.3333% - 1px); } .litespeed-postbox .inside { padding: 0 15px 5px; } .litespeed-dashboard-group .litespeed-postbox { width: calc(50% - 20px); } .litespeed-dashboard-group .litespeed-postbox-double { min-width: calc(100% - 20px); } } @media screen and (max-width: 719px) and (min-width: 480px) { .litespeed-dashboard-stats-wrapper .litespeed-postbox { margin-top: -1px; min-width: calc(50% - 2px); } } @media screen and (max-width: 569px) { .litespeed-dashboard-stats-wrapper .litespeed-postbox { min-width: 100%; } .litespeed-dashboard-group .litespeed-postbox { width: 100%; } .litespeed-postbox-double-content .litespeed-postbox-double-col { width: 100%; } .litespeed-postbox-double-content .litespeed-postbox-double-col:nth-child(2) { padding-left: 0; margin-top: 7px; } .litespeed-postbox-double-content { flex-wrap: wrap; } } /* ======================================= VIEW - dashboard QC services ======================================= */ .litespeed-dashboard-qc { position: relative; } .litespeed-dashboard-unlock { text-align: center; background-color: #fff; box-shadow: 0 0.125rem 0.4rem -0.0625rem rgba(0, 0, 0, 0.03), 0px 3px 0px 0px rgba(0, 0, 0, 0.07); border-radius: 0.5rem; padding: 2rem; position: absolute; z-index: 5; left: 50%; transform: translate(-50%, 25%); top: 0; max-width: 96%; width: 540px; } .litespeed-dashboard-unlock.litespeed-dashboard-unlock--inline { position: relative; left: 50%; transform: translate(-50%, 0); border: 1px solid #e5e5e5; background: #fafafa; margin-top: 2rem; margin-bottom: 1rem; max-width: calc(100% - 4rem); } .litespeed-dashboard-unlock-title { font-size: 28px; } .litespeed-dashboard-unlock-desc { font-size: 17px; color: #000; } .litespeed-dashboard-unlock-desc span { font-size: 14px; color: #666; } p.litespeed-dashboard-unlock-footer { margin: 3em auto 0 auto; max-width: 500px; } .litespeed-qc-text-gradient { background: -webkit-linear-gradient(130deg, #ff2a91, #2295d8 60%, #161f29); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-weight: 800; } .litespeed-dashboard-unlock a.button.button-primary, .litespeed-wrap .button.litespeed-button-cta { font-size: 1.2em; padding: 0.35em 1em 0.35em 0.85em; min-width: 210px; text-align: center; } .litespeed-dashboard-unlock a.button.button-primary { margin-top: 10px; } .litespeed-dashboard-unlock a.button.button-primary .dashicons, .litespeed-wrap .button.litespeed-button-cta .dashicons { vertical-align: baseline; top: 0.25em; margin-right: 0.5em; } .litespeed-dashboard-unlock + .litespeed-dashboard-qc-enable { opacity: 0.75; filter: blur(2px); } .litespeed-dashboard-unlock + .litespeed-dashboard-qc-enable:before { content: ''; position: absolute; left: -10px; top: -5px; width: calc(100% + 20px); height: calc(100% + 10px); background: #161e29; z-index: 2; opacity: 0.55; filter: blur(2px); } @media screen and (min-width: 1400px) { .litespeed-dashboard-unlock { width: 800px; } } @media screen and (max-width: 640px) { .litespeed-dashboard-unlock { max-width: 80%; padding: 1rem 1.5rem 2rem 1.5rem; transform: translate(-50%, 10%); } .litespeed-dashboard-unlock-title { font-size: 22px; line-height: 1.2; } } @media screen and (max-width: 340px) { .litespeed-dashboard-unlock a.button.button-primary, .litespeed-wrap .button.litespeed-button-cta { padding: 0.35em 1em 0.35em 1em; } .litespeed-dashboard-unlock a.button.button-primary .dashicons, .litespeed-wrap .button.litespeed-button-cta .dashicons { display: none; } p.litespeed-dashboard-unlock-footer { margin-top: 2em; } } /********************************* todo *******************************/ /* image optimize page */ .litespeed-column-java { background: #5cadad !important; } .litespeed-text-shipgrey { color: #535342 !important; } .litespeed-text-dimgray { color: #666666 !important; } .litespeed-text-grey { color: #999999 !important; } .litespeed-text-whisper { color: #e6e6e6 !important; } .litespeed-text-malibu { color: #5cbdde !important; } .litespeed-text-morningglory { color: #99cccc !important; } .litespeed-text-fern { color: #66cc66 !important; } .litespeed-text-persiangreen { color: #009999 !important; } .litespeed-text-lead { font-size: 16px; } .litespeed-text-small { font-size: 12px; line-height: 14px; } .litespeed-text-thin { font-weight: 100; } .litespeed-contrast { color: white; } .litespeed-hr-dotted { border: 1px dotted #eeeeee; } .litespeed-hr { padding-bottom: 1.5em; border-bottom: 0.5px solid #97caca; } .litespeed-hr-with-space { border-top: 1px solid #eeeeee; margin: 2em 0; border-bottom: none; } .litespeed-icon-vertical-middle { vertical-align: middle; display: inline-block; margin: 0px 10px 0px 10px; } .litespeed-column-java .litespeed-danger { color: #c1c53a !important; } .litespeed-column-java .litespeed-desc { color: #bfbfbf; } .litespeed-column-java code { color: #c2f5bf; background-color: #238888; } .litespeed-column-java .litespeed-title { color: white; } .litespeed-width-7-10 .litespeed-progress { margin: 1em; } .litespeed-refresh:after { content: '⟳'; width: 20px; height: 20px; color: #40ad3a; } .litespeed-column-java .litespeed-refresh:after { color: #23ec17; } .litespeed-refresh:hover:after, .litespeed-refresh:focus:after, .litespeed-refresh:focus:active:after { color: #7ffbfb; } .litespeed-width-3-10 .litespeed-title { margin: 18px 0; } .litespeed-silence { color: #b1b1b1; } .litespeed-column-java .litespeed-congratulate { color: #c2f5bf; font-size: 20px; } .litespeed-light-code .litespeed-silence code { background-color: #f0f5fb; } .litespeed-column-java .litespeed-btn-danger { color: #f194a8; border-color: #f194a8; } .litespeed-column-java .litespeed-btn-danger:hover { background: #f194a8; } .litespeed-column-java svg.litespeed-pie circle.litespeed-pie_bg { stroke: #e8efe7; } .litespeed-column-java svg.litespeed-pie circle.litespeed-pie_circle { stroke: #97caca; } .litespeed-column-java svg .litespeed-pie_info text { fill: #f5ffeb; } .litespeed-column-java svg g.litespeed-pie_info .litespeed-pie-done { fill: #a5ffa0; } .litespeed-column-java a { color: #eaf8ff; } .litespeed-column-java a:hover { color: #ffffff; } .litespeed-progress-bar-blue { background-color: #33adff; } .litespeed-status-current { font-size: 3.5em; margin: 1.25em 0em 0.75em 0em; } /* .litespeed-title, .litespeed-title-short { margin: 18px 0; border-bottom: 1px solid #C1D5EA; margin: 2.5em 0px 1.5em 0 !important; } */ .litespeed-column-java .litespeed-desc { color: #cae4e4; } .litespeed-column-java .litespeed-warning { color: #ffd597 !important; } .litespeed-column-java .litespeed-btn-success { color: #ddf1e4; border: 1px solid #33ad5c; background: #33ad5c; } .litespeed-column-java .litespeed-btn-success:hover { color: #ffffff; border: 1px solid #7dca97; background: #009933; } .litespeed-column-java .litespeed-btn-warning { color: #fff1dd; border: 1px solid #ff9933; background-color: #ff9933; } .litespeed-column-java .litespeed-btn-warning:hover { color: #ffffff; border-color: #ffca7d; background: #ff9900; } .litespeed-column-java .litespeed-btn-danger { color: #ffeadd !important; border: 1px solid #ff6600 !important; background: #ff5c5c; } .litespeed-column-java .litespeed-btn-danger:hover { color: #ffffff; border: 1px solid #ff9797 !important; background: #ff0000; } .litespeed-column-java .litepseed-dash-icon-success, .litepseed-dash-icon-success { color: #5cdede; font-size: 2em; margin-top: -0.25em; } .litespeed-column-java .litepseed-dash-icon-success:hover, .litepseed-dash-icon-success:hover { color: #7de5e5; } .litespeed-dashicons-large { font-size: 2em; } .litespeed-column-java p { color: #ffffff; } .litespeed-body tbody > tr > th.litespeed-padding-left { padding-left: 3em; } @media screen and (max-width: 680px) { .litespeed-body tbody > tr > th.litespeed-padding-left { padding-left: 10px; } .litespeed-body tbody > tr > th.litespeed-padding-left:before { content: '\2014\2014'; color: #ccc; margin-right: 5px; } } .litespeed-txt-small { font-size: 12px; } .litespeed-txt-disabled .litespeed-text-dimgray { color: #aaaaaa; } .litespeed-txt-disabled svg { fill: #aaaaaa; } .litespeed-txt-disabled circle.litespeed-pie_circle { stroke: #cccccc; } .litespeed-txt-disabled g.litespeed-pie_info text { color: #cccccc; } a.litespeed-media-href svg:hover { border-radius: 50%; background: #f1fcff; fill: #5ccad7; box-shadow: 0 0 5px 1px #7dd5df; transition: all 0.2s ease-out; transform: scale(1.05); } .litespeed-media-p a .dashicons-trash { font-size: 2.25em; vertical-align: middle; display: inline; border-radius: 50%; line-height: 1.5em; } .litespeed-media-p a .dashicons-trash:hover { transition: all 0.2s ease-out; color: #ffa500 !important; background: #fff5e6; box-shadow: 0 0 10px 1px #ff8c00; } .litespeed-media-p div > svg circle.litespeed-pie_bg { stroke: #ecf2f9; } .litespeed-media-p div > svg circle.litespeed-pie_circle { stroke: #9fbfdf; } .litespeed-media-p div > svg { fill: #538cc6; background: rgba(236, 242, 249, 0.1); border-radius: 50%; } .litespeed-banner-description-padding-right-15 { padding-right: 15px; } .litespeed-banner-description { display: inline-flex; flex-wrap: wrap; } .litespeed-banner-description-content { margin: 0px; line-height: 1.25em; } .litespeed-banner-button-link { white-space: nowrap; margin: 0px; line-height: 1.5em; padding-bottom: 5px; } .litespeed-notice-dismiss { position: absolute; right: 25px; border: none; margin: 0; padding: 10px; background: none; cursor: pointer; color: #888888; display: block; height: 20px; text-align: center; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; font-weight: 600; text-decoration: none; } .litespeed-notice-dismiss:hover, .litespeed-notice-dismiss:active, .litespeed-notice-dismiss:focus { color: #cc2929; } .litespeed-dot { display: inline-block; border-radius: 50%; width: 20px; height: 20px; color: white; text-align: center; } .litespeed-badge { display: inline-block; border-radius: 20%; min-width: 50px; height: 20px; color: white; text-align: center; } /* ======================================= Comparison Cards - Presets ======================================= */ .litespeed-comparison-card { box-sizing: border-box; } .litespeed-comparison-card-rec .litespeed-card-content > div.litespeed-card-body { font-size: 14px; } .litespeed-comparison-card-rec .litespeed-card-action { margin-bottom: 0.25rem; } .litespeed-comparison-card-rec h3 { font-size: 20px; } .litespeed-card-content > div, .litespeed-card-action { padding: 0.85rem 1.25rem; } .litespeed-card-header { border-bottom: 1px solid #eee; background: #f9fafc; } .litespeed-card-content > div.litespeed-card-body { align-self: stretch; justify-content: flex-end; font-size: 15px; padding-bottom: 0.5rem; padding-top: 1rem; } .litespeed-card-content > div.litespeed-card-footer { align-self: stretch; justify-content: flex-end; padding-bottom: 0; padding-top: 0.25rem; } .litespeed-card-action { justify-content: flex-end; } .litespeed-comparison-card ul { padding-left: 20px; list-style: none; list-style-position: outside; margin: 0; } .litespeed-comparison-card li { margin-bottom: 0.5em; line-height: 1.4; } .litespeed-comparison-card li:last-child { margin-bottom: 0; } .litespeed-comparison-card ul li:before { content: '✓'; margin-left: -1em; margin-right: 0.35em; color: #329c74; } @media screen and (max-width: 1279px) { .litespeed-comparison-card { margin: 0 0 -1px 0; } } @media screen and (min-width: 640px) and (max-width: 1279px) { .litespeed-comparison-cards { max-width: 740px; } .litespeed-card-content { display: flex; flex-wrap: wrap; } .litespeed-card-content .litespeed-card-header { width: 100%; } .litespeed-card-content > div.litespeed-card-body { align-self: initial; width: 50%; box-sizing: border-box; } .litespeed-card-content > div.litespeed-card-footer { width: 50%; align-self: initial; box-sizing: border-box; } .litespeed-card-content > div.litespeed-card-footer h4 { margin-top: 1rem; } } @media screen and (min-width: 1280px) { .litespeed-comparison-cards { display: flex; margin: 3rem 0 2rem 0; max-width: 1720px; } .litespeed-comparison-card { width: 19%; min-width: 0; display: flex; flex-direction: column; margin-right: -1px; justify-content: space-between; } .litespeed-comparison-card:first-child { border-top-left-radius: 5px; border-bottom-left-radius: 5px; overflow: hidden; } .litespeed-comparison-card:last-child { border-top-right-radius: 5px; border-bottom-right-radius: 5px; overflow: hidden; } .litespeed-comparison-card-rec { width: 23%; padding-top: 1rem; padding-bottom: 0.75rem; margin-top: -1rem; margin-bottom: 0.25rem; border-radius: 5px; overflow: hidden; } .litespeed-comparison-card-rec .litespeed-card-header { margin-top: -1rem; padding-top: 1.75rem; padding-bottom: 0.95rem; } } /* ======================================= BALLOON PURE CSS TOOLTIPS ======================================= */ .litespeed-wrap { --balloon-color: rgba(16, 16, 16, 0.95); --balloon-font-size: 12px; --balloon-move: 4px; } .litespeed-wrap button[aria-label][data-balloon-pos] { overflow: visible; } .litespeed-wrap [aria-label][data-balloon-pos] { position: relative; cursor: pointer; } .litespeed-wrap [aria-label][data-balloon-pos]:after { opacity: 0; pointer-events: none; transition: all 0.2s ease 0.05s; text-indent: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; font-weight: normal; font-style: normal; text-shadow: none; font-size: var(--balloon-font-size); background: var(--balloon-color); border-radius: 2px; color: #fff; content: attr(aria-label); padding: 0.5em 1em; position: absolute; white-space: nowrap; z-index: 10; line-height: 1.4; } .litespeed-wrap [aria-label][data-balloon-pos]:before { width: 0; height: 0; border: 5px solid transparent; border-top-color: var(--balloon-color); opacity: 0; pointer-events: none; transition: all 0.2s ease 0.05s; content: ''; position: absolute; z-index: 10; } .litespeed-wrap [aria-label][data-balloon-pos]:hover:before, .litespeed-wrap [aria-label][data-balloon-pos]:hover:after, .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-visible]:before, .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-visible]:after, .litespeed-wrap [aria-label][data-balloon-pos]:not([data-balloon-nofocus]):focus:before, .litespeed-wrap [aria-label][data-balloon-pos]:not([data-balloon-nofocus]):focus:after { opacity: 1; pointer-events: none; } .litespeed-wrap [aria-label][data-balloon-pos].font-awesome:after { font-family: FontAwesome, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-break]:after { white-space: pre; } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-break][data-balloon-length]:after { white-space: pre-line; word-break: break-word; } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-blunt]:before, .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-blunt]:after { transition: none; } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='up']:after { bottom: 100%; left: 50%; margin-bottom: 10px; transform: translate(-50%, var(--balloon-move)); transform-origin: top; } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='up']:before { bottom: 100%; left: 50%; transform: translate(-50%, var(--balloon-move)); transform-origin: top; } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='up']:hover:after, .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='up'][data-balloon-visible]:after { transform: translate(-50%, 0); } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='up']:hover:before, .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='up'][data-balloon-visible]:before { transform: translate(-50%, 0); } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='up-left']:after { bottom: 100%; left: 0; margin-bottom: 10px; transform: translate(0, var(--balloon-move)); transform-origin: top; } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='up-left']:before { bottom: 100%; left: 5px; transform: translate(0, var(--balloon-move)); transform-origin: top; } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='up-left']:hover:after, .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='up-left'][data-balloon-visible]:after { transform: translate(0, 0); } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='up-left']:hover:before, .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='up-left'][data-balloon-visible]:before { transform: translate(0, 0); } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='up-right']:after { bottom: 100%; right: 0; margin-bottom: 10px; transform: translate(0, var(--balloon-move)); transform-origin: top; } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='up-right']:before { bottom: 100%; right: 5px; transform: translate(0, var(--balloon-move)); transform-origin: top; } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='up-right']:hover:after, .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='up-right'][data-balloon-visible]:after { transform: translate(0, 0); } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='up-right']:hover:before, .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='up-right'][data-balloon-visible]:before { transform: translate(0, 0); } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='down']:after { left: 50%; margin-top: 10px; top: 100%; transform: translate(-50%, calc(var(--balloon-move) * -1)); } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='down']:before { width: 0; height: 0; border: 5px solid transparent; border-bottom-color: var(--balloon-color); left: 50%; top: 100%; transform: translate(-50%, calc(var(--balloon-move) * -1)); } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='down']:hover:after, .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='down'][data-balloon-visible]:after { transform: translate(-50%, 0); } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='down']:hover:before, .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='down'][data-balloon-visible]:before { transform: translate(-50%, 0); } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='down-left']:after { left: 0; margin-top: 10px; top: 100%; transform: translate(0, calc(var(--balloon-move) * -1)); } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='down-left']:before { width: 0; height: 0; border: 5px solid transparent; border-bottom-color: var(--balloon-color); left: 5px; top: 100%; transform: translate(0, calc(var(--balloon-move) * -1)); } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='down-left']:hover:after, .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='down-left'][data-balloon-visible]:after { transform: translate(0, 0); } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='down-left']:hover:before, .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='down-left'][data-balloon-visible]:before { transform: translate(0, 0); } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='down-right']:after { right: 0; margin-top: 10px; top: 100%; transform: translate(0, calc(var(--balloon-move) * -1)); } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='down-right']:before { width: 0; height: 0; border: 5px solid transparent; border-bottom-color: var(--balloon-color); right: 5px; top: 100%; transform: translate(0, calc(var(--balloon-move) * -1)); } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='down-right']:hover:after, .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='down-right'][data-balloon-visible]:after { transform: translate(0, 0); } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='down-right']:hover:before, .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='down-right'][data-balloon-visible]:before { transform: translate(0, 0); } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='left']:after { margin-right: 10px; right: 100%; top: 50%; transform: translate(var(--balloon-move), -50%); } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='left']:before { width: 0; height: 0; border: 5px solid transparent; border-left-color: var(--balloon-color); right: 100%; top: 50%; transform: translate(var(--balloon-move), -50%); } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='left']:hover:after, .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='left'][data-balloon-visible]:after { transform: translate(0, -50%); } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='left']:hover:before, .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='left'][data-balloon-visible]:before { transform: translate(0, -50%); } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='right']:after { left: 100%; margin-left: 10px; top: 50%; transform: translate(calc(var(--balloon-move) * -1), -50%); } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='right']:before { width: 0; height: 0; border: 5px solid transparent; border-right-color: var(--balloon-color); left: 100%; top: 50%; transform: translate(calc(var(--balloon-move) * -1), -50%); } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='right']:hover:after, .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='right'][data-balloon-visible]:after { transform: translate(0, -50%); } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='right']:hover:before, .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-pos='right'][data-balloon-visible]:before { transform: translate(0, -50%); } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-length='small']:after { white-space: normal; width: 80px; } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-length='medium']:after { white-space: normal; width: 150px; } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-length='large']:after { white-space: normal; width: 260px; } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-length='xlarge']:after { white-space: normal; width: 380px; } @media screen and (max-width: 768px) { .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-length='xlarge']:after { white-space: normal; width: 90vw; } } .litespeed-wrap [aria-label][data-balloon-pos][data-balloon-length='fit']:after { white-space: normal; width: 100%; } /* ======================================= Misc Mobile TWEAKS ======================================= */ @media screen and (max-width: 680px) { .litespeed-wrap .litespeed-body .field-col { margin-left: 0; } .litespeed-width-auto.litespeed-table-compact td { font-size: 12px; word-break: break-word; } .litespeed-body .litespeed-table td .litespeed-right { float: none !important; } .litespeed-title a.litespeed-learn-more, .litespeed-title-short a.litespeed-learn-more { display: block; margin-left: 0; margin-top: 5px; } } .litespeed-wrap .litespeed-redetect[aria-label][data-balloon-pos][data-balloon-pos='up']:after { left: auto; right: 0; transform: translate(0%, var(--balloon-move)); } .litespeed-wrap .litespeed-redetect[aria-label][data-balloon-pos][data-balloon-pos='up']:hover:after, .litespeed-wrap .litespeed-redetect[aria-label][data-balloon-pos][data-balloon-pos='up'][data-balloon-visible]:after { transform: translate(0, 0); } /* ======================================= QC ======================================= */ .litespeed-col-status-data h3, .litespeed-col-status-data h4 { margin-bottom: 0; margin-top: 20px; } .litespeed-col-status-data h3 .dashicons { vertical-align: bottom; } .litespeed-col-status-data h4 .dashicons { vertical-align: sub; } /* To use on dark bg */ .litespeed-wrap .litespeed-qc-button { background-color: #5efffc; border: 1px solid #00d0cb; box-shadow: 0px 2px 0px 0px #00d0cb; color: #161f29; font-weight: 600; font-size: 15px; padding: 12px 24px; border-radius: 3px; line-height: 1; display: inline-flex; align-items: center; transition: 0.25s; } .litespeed-wrap .litespeed-qc-button:hover { background: #21a29f21; color: #5efffc; border-color: #00d0cb; } .litespeed-wrap .litespeed-qc-button .dashicons { top: auto; } .litespeed-postbox.litespeed-qc-promo-box { background: #161e29 linear-gradient(110deg, #171c2fbd, #252766ab); border-radius: 5px; box-shadow: 0px 4px 0px 0px #161d2e; border: none; } .litespeed-postbox.litespeed-qc-promo-box .inside { padding: 25px; margin: 0; } .litespeed-dashboard-group .litespeed-postbox.litespeed-qc-promo-box { box-shadow: none; } .litespeed-dashboard-group .litespeed-postbox.litespeed-qc-promo-box .inside { padding: 20px 25px; } .litespeed-postbox.litespeed-qc-promo-box h3 { margin-top: 0; color: #fff; font-size: 24px; font-weight: 800; line-height: 1.4em; } .litespeed-postbox.litespeed-qc-promo-box h3 .litespeed-quic-icon { width: 24px; height: 28px; background-size: contain; margin-right: 10px; } .litespeed-postbox.litespeed-qc-promo-box p { color: #dbdbdb; font-size: 1rem; } /* ======================================= Deactivate modal ======================================= */ #litespeed-modal-deactivate { padding: 20px; } #litespeed-modal-deactivate h2 { margin: 0px; } #litespeed-modal-deactivate .litespeed-wrap { margin: 10px 0px; } #litespeed-modal-deactivate .deactivate-clear-settings-wrapper, #litespeed-modal-deactivate .deactivate-actions { margin-top: 30px; } #litespeed-modal-deactivate .deactivate-reason-wrapper label, #litespeed-modal-deactivate .deactivate-clear-settings-wrapper label { width: 100%; display: block; margin-bottom: 5px; } #litespeed-modal-deactivate .deactivate-actions { display: flex; justify-content: space-between; } assets/css/litespeed-dummy.css000064400000000074152075713260012470 0ustar00/* To be replaced in `head` to control optm data location */assets/css/iziModal.min.css000064400000247556152075713260011735 0ustar00/* * iziModal | v1.5.1 * http://izimodal.marcelodolce.com * by Marcelo Dolce. */ .iziModal{display:none;position:fixed;top:0;bottom:0;left:0;right:0;margin:auto;background:#fff;box-shadow:0 0 8px rgba(0,0,0,.3);transition:margin-top .3s ease,height .3s ease;transform:translateZ(0)}.iziModal *{-webkit-font-smoothing:antialiased}.iziModal::after{content:'';width:100%;height:0;opacity:0;position:absolute;left:0;bottom:0;z-index:1;background:-moz-linear-gradient(top,transparent 0%,rgba(0,0,0,.35) 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,transparent),color-stop(100%,rgba(0,0,0,.35)));background:-webkit-linear-gradient(top,transparent 0%,rgba(0,0,0,.35) 100%);background:-o-linear-gradient(top,transparent 0%,rgba(0,0,0,.35) 100%);background:-ms-linear-gradient(top,transparent 0%,rgba(0,0,0,.35) 100%);background:linear-gradient(to bottom,transparent 0%,rgba(0,0,0,.35) 100%);filter:progid:DXImageTransform.Microsoft.gradient( startColorstr='#00000000', endColorstr='#59000000',GradientType=0 );transition:height .3s ease-in-out,opacity .3s ease-in-out;pointer-events:none}.iziModal.hasShadow::after{height:30px;opacity:1}.iziModal .iziModal-progressbar{position:absolute;left:0;top:0;width:100%;z-index:1}.iziModal .iziModal-progressbar>div{height:2px;width:100%}.iziModal .iziModal-header{background:#88a0b9;padding:14px 18px 15px;box-shadow:inset 0 -10px 15px -12px rgba(0,0,0,.3),0 0 0 #555;overflow:hidden;position:relative;z-index:10}.iziModal .iziModal-header-icon{font-size:40px;color:rgba(255,255,255,.5);padding:0 15px 0 0;margin:0;float:left}.iziModal .iziModal-header-title{color:#fff;font-size:18px;font-weight:600;line-height:1.3}.iziModal .iziModal-header-subtitle{color:rgba(255,255,255,.6);font-size:12px;line-height:1.45}.iziModal .iziModal-header-subtitle,.iziModal .iziModal-header-title{display:block;margin:0;padding:0;font-family:'Lato',Arial;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:left}.iziModal .iziModal-header-buttons{position:absolute;top:50%;right:10px;margin:-17px 0 0}.iziModal .iziModal-button{display:block;float:right;z-index:2;outline:0;height:34px;width:34px;border:0;padding:0;margin:0;opacity:.3;border-radius:50%;transition:transform .5s cubic-bezier(.16,.81,.32,1),opacity .5s ease;background-size:67%!important;-webkit-tap-highlight-color:transparent}.iziModal .iziModal-button-close{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTMyIDc5LjE1OTI4NCwgMjAxNi8wNC8xOS0xMzoxMzo0MCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUuNSAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODZCQkIzQ0I0RTg0MTFFNjlBODI4QTFBRTRBMkFCMDQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODZCQkIzQ0M0RTg0MTFFNjlBODI4QTFBRTRBMkFCMDQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4NkJCQjNDOTRFODQxMUU2OUE4MjhBMUFFNEEyQUIwNCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4NkJCQjNDQTRFODQxMUU2OUE4MjhBMUFFNEEyQUIwNCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PsgTJLcAAALJSURBVHja3JnLS1VBHMfvQ7g9dBXRRrwEFRciAhMi1JRW1aIHVEIYEkW0iVpUhOD/ICK6cFMgSbUpC6VFkQa9NtpjkauriRY9Noa3pHT8/mIODMM5Or85o87pC5/NPf5mvmc8M7+Z36SFEKkY2gj2gUawF2wHW8A6+fwv+A6KYAQMg+dg2rbDtKXhGnAaHJIms4zYz9J4HxgAf1g9k2EGteAhWBBuNApaQNrUg6nRTaAbzIuV0RCocWW4DoyJlVcJXI5ruFk2tJqi/2TWxvA5sXbqA2Ucw01i7dVjargazAo/dE33p6/DlAheg50pP0SJpwG8CH7IaH/Q5pFZUhnoArkwwwVwJeWfdoMLYYZvqG+yTGo9CerAoIWBT+A4qAdPDWOugwo1NVcxJtpFZRLkwH3GJCqCghJfxVjnz1JMMMKnwAbGRAg0B5rAA4O4CblZ+qj8tkBjZthvSzDCtFIMM0ZpQhslk5Eej4jpZ/T7G+ygwG1ghrk+jjNMFy1eMPJzpOAzlou6iWmXZkm91EBHjEwUZXoQTDk2SxqhRh7HTJ9hpstB3rFZ0ldq6J2DnB9m2rXZfxOPlrX1DrJRXiaBXSHPaMHvB0cd9JPLpBImMvzLQTuUFA6A9yHPfoIjhsllOc1l5N4grtmDWgYrl5+JTUZcSjNkeMyxWdpA3ZN72IJj01OJTByJS82J2/wQVxmB5y1HK8x0JWMf/kzdD98FJcY5S51gdwyTQl6eUAraspo27PeWXgy8afim0+CELAwOWHyH9EkdkyWwJ4Yxk6BCP+bTm48anutWW5dAp34IpbW03UOzb0FPVEHbx0LKfvAyqpAyKw97JU8Mt6pml6rAJ6oY6Eu5NfvfF7QTeWWQyEsZr6694lwsNoPD8mKRo29gCNwGj7gXi7aGA1EBcY+8vq0GW8FmJb3Pgx9gEnwAr8Ab8MW2w0UBBgAVyyyaohV7ewAAAABJRU5ErkJggg==) no-repeat 50% 50%}.iziModal .iziModal-button-fullscreen{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTMyIDc5LjE1OTI4NCwgMjAxNi8wNC8xOS0xMzoxMzo0MCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUuNSAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RTBBOUI4RUM0RTg0MTFFNjk0NTY4NUNFRkZFNEFEQzIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RTBBOUI4RUQ0RTg0MTFFNjk0NTY4NUNFRkZFNEFEQzIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpFMEE5QjhFQTRFODQxMUU2OTQ1Njg1Q0VGRkU0QURDMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpFMEE5QjhFQjRFODQxMUU2OTQ1Njg1Q0VGRkU0QURDMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PrQO6gAAAANmSURBVHjazJlbSBRRGMd3x92i0ForRRMiKiUoX4ouiFlJkRVBDxW9GJERwUasvdRT9FD00osRQtAFqegGBUHRBY0uaCVKEkSRpVR0tSwrQtp1+p/4Bk7D7M45M/Ot/uGHu+Psmf+c+eY753wnbJpmyIfGgvmgiv6WgkKQBwzwE3wBr0AnuAta6ZgnhT0aFuY2ghoyGdH4bS+4Dc6CZjCkdWVhWIPF4JoZnB6CDToeVE8sBidNPt0E5UEZrgG9Jr8GwHa/huMgaWZXDSDsxfBuc/jUBAwdw3Fz+NWoang5SJkjQwm7P3seLqQEX2LLfgfBdZcMORMcBqNDwekPqASP0uXhpjR3Ok0x/fUw9HIHGGVdw5DuRtzJpgxDsJui2qOWmuaAOuuLbHivz4YLwLgQj/aAXNmwuItlHhtbA7pAG5jEZHgKWCcbrhUTIY+NPQVjqFFObbYMi/hc6aOhl2AJ9TKnFoIyYXgemKEzJQXVVkyR3oFVzKZFuqw2qHdyFPKhrHPgMoWC3fRjRtNVVg+7SR5IiqmXxUt60cG0CK/vTIZniZVCmcKJF0C3ZNjKBqvJ9Hrwm46tsN1EkCoRQ/M3fBjvs6GrYAvdwHEfGcd1qBaGkwoxrKI+xjz83yJ0iLFHApd46X4xX+M+WECh4lepCNUIcpnMijrEWtAvTRHrbOd8FZNG8uA2Nf0hpmwtjBPwpQ5T0GPS/+tBAZhIq+b3Lu09EyHRwRgO+0C+7dhWcII+PwCf6Sk/Aa9d2vtn+A7nyASugJiD6YSDQcOlvVbxiCaAN8xrs3sgprBiac/QhlhnzjUo6JuZM0UlDS5FPtoQIdNlPYJTWUihFaDex+9Pg6T1KHJAJ2NI7ASllA28hEQ/KJIXoSlwgKlnh+jFe+GjLtwIPtjfyktUt+UaUZWqvw7H3oJD1peI7eQdoF1xWa+zQikHH13OmwqmOxxP0EiZtgK/DRwNuIcHwSeXc2K01WAPhbhKBb5hBNTVbskVH7fqpZGhbJUNtYF83fqwQSXPbOsGjb6etwx2gcEsmT3iFAZeNmUqaMeHSz2qu0k6W15Rqsx3B2i0D+xXGAHTFrRVlEeFuVoqH+ku6VNUbDkPzlAtg30nVK66i8rRIjAbTKaSQVQyN0DD6nOqcLZQld9TLfmvAAMAeMcvp3eCFqQAAAAASUVORK5CYII=) no-repeat 50% 50%}.iziModal.isFullscreen .iziModal-button-fullscreen{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTMyIDc5LjE1OTI4NCwgMjAxNi8wNC8xOS0xMzoxMzo0MCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUuNSAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MkFFRTU5NDA0RTg1MTFFNjk0NEZFQzBGMkVBMDYyRDkiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MkFFRTU5NDE0RTg1MTFFNjk0NEZFQzBGMkVBMDYyRDkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoyQUVFNTkzRTRFODUxMUU2OTQ0RkVDMEYyRUEwNjJEOSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoyQUVFNTkzRjRFODUxMUU2OTQ0RkVDMEYyRUEwNjJEOSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PuDFfX8AAANASURBVHjazJlZSBVRGMfHcWlB0xZM68GKukQLYaGkmEUR2EsvRfQS+BSJPUQE+lTR8hqIZY8hFS0ERVCRoW3gpUApghYpszLTVnCB3O70/+K7MAwzc78Z58z4hx8XzpzvzJ+Zc+d85ztphmFoU9BsUAoq+XcFyAc5QAfD4BfoBp3gCWjnNl9K82mYzO0FVWwyw0NsD3gIroBWkPB0ZzLsgc3grhGcnoE9XjxIOxaCC4Y6tYC1QRmuAj2Geg2CA1M1XAsmjHDVANL8GK4zolMz0L0YrjWiV5PU8HYw6TBIf8imD6UynA96HYKPg3mgMUTDY6DUzXCzQ+AxSz+r6QEQZz4HbLoDZNkZrnAIoOlRZjN1Gk3XS0zty/gTFaRq7Ay3uAR8BcU2ps/z9QJTWw74HrDhTyDbbHg9SKQI+sb9rKa3mV8ZmAt+KJjP1TS+zinFPkqEUqQdBeAOKLa0UwIzpqlXtcYpIKWIO4RBZPoRKNfC10YQI8MlYLkwaAB8ABsiMDwDbKU8dgtIFwRMgJ3guRadKpNPWBMa7tOi1WoyHJPuTsC4oN+IQsOLM3gPJlEWqOE/neMGBqwDeYoMz6G8c0I4h6eFyHBC8A2eVoaH8JutaPwuUA/+uvSht1sHKgTjTWZwjUCVYdrK3xT0iwkND+lc5FClUQ9fINHCRYY7FBrWPSz5Er2lAR9H9P+hpfYGl64OCmPadQ7ojcDwOJetysBMQX/6mrWS4d+cIoYtMnAEnBT2fwVeJufYxZBMFoKFlrajQtOX/uczvEtIB50Kdgn1lt3JGdANltjsXE64jPMnuQ1LPuFJcFrBE11gzQXAUnAPFNk86esO4zSBfmu5lVa9toCf8DC4Ba6C22DEdO01KDLdP5fLr1Z94X2ibV1ilWVQ1XrDpvPAU4c+u1KVqvaHXI7q43ltp3PSYmDDNCgGPrCUD1wN6y5lqzAUN89baX1Y55Jn2LrPRUffRwaHwWhIZs/aTQM/hzLlDp+coPRReprk5cgrkyvz7wM0+hOcAvOlPvwcLNIp526ux1H5aJbHeFpVX4Br4LLXWoffk9CkVnLlaBNYAxaBXJBpMjfIy+o7EAdtfIyb8HPDfwIMAM1WPs8F9tcxAAAAAElFTkSuQmCC) no-repeat 50% 50%}.iziModal .iziModal-button-close:hover{transform:rotate(180deg)}.iziModal .iziModal-button:hover{opacity:.8}.iziModal .iziModal-header.iziModal-noSubtitle{height:auto;padding:10px 15px 12px}.iziModal .iziModal-header.iziModal-noSubtitle .iziModal-header-icon{font-size:23px;padding-right:13px}.iziModal .iziModal-header.iziModal-noSubtitle .iziModal-header-title{font-size:15px;margin:3px 0 0;font-weight:400}.iziModal .iziModal-header.iziModal-noSubtitle .iziModal-header-buttons{right:6px;margin:-16px 0 0}.iziModal .iziModal-header.iziModal-noSubtitle .iziModal-button{height:30px;width:30px}.iziModal-rtl{direction:rtl}.iziModal-rtl .iziModal-header{padding:14px 18px 15px 40px}.iziModal-rtl .iziModal-header-icon{float:right;padding:0 0 0 15px}.iziModal-rtl .iziModal-header-buttons{right:initial;left:10px}.iziModal-rtl .iziModal-button{float:left}.iziModal-rtl .iziModal-header-subtitle,.iziModal-rtl .iziModal-header-title{text-align:right;font-family:Tahoma,'Lato',Arial;font-weight:500}.iziModal-rtl .iziModal-header.iziModal-noSubtitle{padding:10px 15px 12px 40px}.iziModal-rtl .iziModal-header.iziModal-noSubtitle .iziModal-header-icon{padding:0 0 0 13px}.iziModal.iziModal-light .iziModal-header-icon{color:rgba(0,0,0,.5)}.iziModal.iziModal-light .iziModal-header-title{color:#000}.iziModal.iziModal-light .iziModal-header-subtitle{color:rgba(0,0,0,.6)}.iziModal.iziModal-light .iziModal-button-close{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA4JpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTM4IDc5LjE1OTgyNCwgMjAxNi8wOS8xNC0wMTowOTowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDoyQTU1RUZDNzRFODQxMUU2ODAxOEUwQzg0QjBDQjI3OSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo1NEM4MTU1MEI4QUExMUU2QjNGOEVBMjg4OTRBRTg2NyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo0RTNFNENDMkI4QUExMUU2QjNGOEVBMjg4OTRBRTg2NyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxNyAoTWFjaW50b3NoKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjZjYzMwMmE1LWFlMjEtNDI3ZS1hMmE4LTJlYjhlMmZlY2E3NSIgc3RSZWY6ZG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOjdmYmU3NGE3LTAxMDUtMTE3YS1hYmM3LWEzNWNkOWU1Yzc4NyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Po24QssAAANtSURBVHja3JlJaBRBFIa7ZxyTSXADHUkikuAawZNLEOOGGrwJQYko8R4RBQ+OICoqghJQUVwPYjzFY0QUBQU1kogoKO6CG0pcIwbiNibj/8JraNvu6Xo9NTOtP3xzSKe6/65+Ve9VlWlkp2IwGUwFE0E5GA4G8/U+0APegWfgHrgPuq0bpNNp0QPNgEYngHlgGpuMCNp2s+kr4BYM/8ql4WqwHEzP4mXteg7awOW0YlerPnQIaARLNBl1ikLlBDw/1WF4ClgHKozc6idogekz2RheANbaBlE+dB4chfF+qeHF3LOF0FWwF6b7nBe8RvecApolzQVr3C64GR4H1huFV51pmvV+hikRbABFRji0GqarMxluAGON8CgKmmA65mZ4DFhqhE9VPP//ZXgZiCmm1t1gI6XWAAY+gF0gCe4qtqlHL8fthkeBWsXGreA6eMgPviEw+x5sBZ3gAdjPCcNPI8Fsu+FawUCzz40psEfRNJndBl7b/pZmVLTQMkzJo0bQSys43iWm3cxS+DUJOmoSwqKCRmEZWKkYv6RSMBPc5lqXRGm0A1Q6XiaT2aSwo8jrK/qZwZlFIlXTusxa6iXDddTdARpnMj2ek9AWjWYH7h/lubcs4A28THdyAdOl0ezAmKNBNyLLiT0Btjti9zuHg06zpJKIprohwXNypcu1OIdGjYbnxCLGPyYy/EPDfejzbwYvXK59AzuFGdFLKTL8WYNZ59RVzGESJCNm0teI40E6zNIA2wSaA2REP32iaW0omKXRbJKTUVyYEVV0J8oxvEiQmiUZrFSz6XNkuJe3nBKCelaSbjOZrhLsd1BInYxweSeJq9YA6dYtuZCBI4JZ6jGW/W+sebhd0DAaMIO5mTYFW1+X6GeQ7TO3W0WyQj3cw0ulBg4nSUbcAY7zPVYp7ip95FXOH29Hb35AOPjypWMIh7PORSjFZVsIzdKW7AWvfYnTVNWHyCytHw+jd1Nehqks3KepvtChUzD7yGvE2/cduqxldQF1EWZb/PbWLF3jAVgo0WrlkN+c6hSd+rzlaSuaR7O0oX0wyIa2pVAdGaj0HCUVOqIq4dVwrg5lmmG2w+8f/9tjL6foYHE+Gy8Xtv3CPUpf7WauDxadKuIwoeNbOmoYDYbZ0ns/1wxUC7ykigs8sS/LpEe3vwUYALiKDDDSgEiSAAAAAElFTkSuQmCC) no-repeat 50% 50%}.iziModal.iziModal-light .iziModal-button-fullscreen{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA4JpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTM4IDc5LjE1OTgyNCwgMjAxNi8wOS8xNC0wMTowOTowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpEQTg1NTA2NTRFODQxMUU2OTQ0N0VERjY2Q0M5ODYwRCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo0RTNFNENCQkI4QUExMUU2QjNGOEVBMjg4OTRBRTg2NyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo0RTNFNENCQUI4QUExMUU2QjNGOEVBMjg4OTRBRTg2NyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxNyAoTWFjaW50b3NoKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjFlNTQwYzczLTVhZmEtNDJlYi04YzJlLWMwMzFlYmFiYmIyNiIgc3RSZWY6ZG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOmVkYmRiMzM1LTAxMDUtMTE3YS1hYmM3LWEzNWNkOWU1Yzc4NyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PvIicdUAAAOvSURBVHjaxJlZbA1hFMe/qaItUUsspakg1laJ7UUisQuRvvTFA15sQSRCLBFrQryhHqxNHxEPtaQ8CCUkIrVVRbVBJdZYSrXVonr9/3pGxnTunZk78/X+k1+aO+1899/vnnvO+c4YKpi6ghEgW34OBD1BKjBAM6gH78Fz8BhUyrW/ikQivt7QiNMozU0DE8RkJx/3fgCPwA1QHvHp2K/hHJAPJqpwVA2K4flW2IZ7gyVgptKjh6AQxl+GYZi7uRr0U3rVBIpg+nIQwwvACpCkOk4XwYlosR3LMGN1qUqMroGDTqaNGDu7SiVWl+D3iP2i00c9HqxUidd8wzDy3HY4HRwCfWzXz4L7Lm+QKfHeOUTTLWAzdro6muH1YIbDjculWrmpUEM2YYXcCNMt9pAYE8WsWYLdlAxaNYTGMDDHKYYXBVy4B0jTFM/5iOcUc1fM/2JcnItNAYtBNzGtQ33BVHDV3OHpARqhV6CLLKpTs8yQYHxOCrDQO7AV1Gg2PBJhMYiGh4MMnx1eLkixXKsFuzSbZrrMpeGxHnqFFtvrTWCbhILd9AuNpnPMHXaTtZD0kl1mRdwSxXSjJsNZfONjcmqIJR5p3lp6Y+sXrAzsBz/lNXvmtZYMFKbqafi0pKQgKpOSPhmsC5BxXEs1Fz4fUr/7TWMe/q9bC2s3tJs1Df/Q/B5PwAZwJYS1WpPlo0zRZJZziL2gQU7I1GyHL7QSD26taVOytI26DpinxKypApvpk+C6dHlMnXskbUbT1yTpN3WJHWB327UCS3hUoc+tA/VyxP/ost5rGq7QWZnAdoe0eZgnYweDbgmgkoafgk8aTfNgsMNmmqfhC+Czj3V4T3mSBH255kxB0ztd4tNNDJkas2CUdkAKHQ3yAtxfijj/bdb7Cumyhmoyexzcs6Qwv2qUbPKvJDOtnNFklrF3R5qneA2XYHe/2A+ht1Xb3FZXRY1XTAjFTgtxJ45qKtWDpZK1g6dhIQuvBzjcy8FgQ6y8Nw+sCdnwL1Dn8jdMe6m2a+3ma9ESNUdOC1VixSH3bnPiYyraswnO0fqDIQkyW8WmCWab7b+I9TCF3+x0j2e+MPUA7LPGrVfD1F3VNsrPVR0zhS8BB5x21muzYa1Sy1Tb4y4d4qOwIi9Pk/wcj1gV50p5zQjJKAsJH8KcY4vpdYrjV0w9HMxxHjfKNpfwdMyRNuAmyy2M1vq5OegBNFMmR9lSHDizSLPMJGjuO2BZfSOtLKvpMylUvh/d/hFgAOH4+ibxGTZuAAAAAElFTkSuQmCC) no-repeat 50% 50%}.iziModal.iziModal-light.isFullscreen .iziModal-button-fullscreen{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3BpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTM4IDc5LjE1OTgyNCwgMjAxNi8wOS8xNC0wMTowOTowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDoyRUUxMkYxODRFODUxMUU2Qjc3RDk0MUUzMzJDRjBEOCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo0RTNFNENCRkI4QUExMUU2QjNGOEVBMjg4OTRBRTg2NyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo0RTNFNENCRUI4QUExMUU2QjNGOEVBMjg4OTRBRTg2NyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxNyAoTWFjaW50b3NoKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjgzM2MwOWZiLWJjOTEtNGVlZS05MDM1LTRkMmU2ZmE1ZjBmMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoyRUUxMkYxODRFODUxMUU2Qjc3RDk0MUUzMzJDRjBEOCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv1Q9Z8AAAOXSURBVHjaxJlLbA1RGMfPjIs+EvoIRYt4FVUl2EkkRTxKUqQbG0SEho2FjUQ8YtEICbEgTdFYeK1KaGvVeoUltyStt0UlNE17aWhV2+v/9X5XJpMzc8/0zpn5kl+aO3Nm7r/fnPu9xhDp2URQDJbw3xkgB2QCAwyAPvANfARvQDsfG7V4PO7pC40xCiVxa8AKFjnOw7VdoA08BtG4R8VeBZeCKrBS+GPvQAM0P/NbcB7YBdYJPfYKXIXwL34IJm8eBFOFXusH9RDdnI7gLWA/MEVwdh/UOe1tN8G0V3eLcKwFXJCJNl08G5ZYsrWgWnZCJng5OOBwo1iAoisMw6hMJXgyOOywVW7xj+9BgKL3QHSxm+C9IF9y4U2GMlStRPQP8Jbp9lFwhJwE0RHrgaSV8N6xG238l7Zjtfx3K58/Bd7zsWngIqdnP2we2ACa7B7e6RL6joK5EtHNfL7b5u1Bn7dGFbycYRVM/8WyFJnuJK+z2iVwzFrMcF1h+Cx4ClhtFVyu8CW54ITE01EwFMAPcH1SMJWIqxQvItE1YHEIsXkhtkUhCV4ApiteFOPadn4IgseDMooSSxVrhWFwmkvCsKw06WGhKLhHhGuzSHChh9pZ5cc1oFFwfoTTsWrWqQCvXdZQEpkDsjUJziSv3Qu43k3LTA1BXqvRY/4DMjTd/yu4niJVm9wslCjcb4QE/9Qo+Al44baAmgpKCIqC+01OBLrsr8/de8zkiYwuUxWSq7iuM8JhantIqfYItkOepKBysnbycIfPXYKqURL6DhaBCQrrKcZHTa5loyEIJgHXwG3F9TQV+pxMGK0BiaTHn2OLEjcURbdi7XBSMO3jTxoEjtg+7wDnhG3spSD6F3hk7Tjoxnc0CJ5k+5wFCrhplYl2mmI24nyvvWumAE9z2zIfBW8WifnxIHc2yb6xiHtEoms0/hlGtpAPHCkgNDjFyZngPN88COvkPpEe+XGHbFcD7z53C+ybwKEAo0UPZ8QCybkmiL3sNvkheygSI08RYOSQiaUhd52sUpIZLWwJsYqkkdcZeHfIS66nc9XcZQRpNBY7C7F9Yy1OtonErDgSgNhGcEXmWa/VFA1O9onE6y4dRqGtXuVtkpf2iDy8EVR6GLykMnrsNFC867QF0hH8v3MVicFcuYdKy56uqQx4SukWQj3NOtJtQIt4ckSvbmdziMqy7HcS9xv0cn/Xwdn0A1drnl/d/hNgAGQa6Lgarp6BAAAAAElFTkSuQmCC) no-repeat 50% 50%}.iziModal .iziModal-loader{background:#fff url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDQiIGhlaWdodD0iNDQiIHZpZXdCb3g9IjAgMCA0NCA0NCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBzdHJva2U9IiM5OTkiPiAgICA8ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0cm9rZS13aWR0aD0iMiI+ICAgICAgICA8Y2lyY2xlIGN4PSIyMiIgY3k9IjIyIiByPSIxIj4gICAgICAgICAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJyIiAgICAgICAgICAgICAgICBiZWdpbj0iMHMiIGR1cj0iMS40cyIgICAgICAgICAgICAgICAgdmFsdWVzPSIxOyAyMCIgICAgICAgICAgICAgICAgY2FsY01vZGU9InNwbGluZSIgICAgICAgICAgICAgICAga2V5VGltZXM9IjA7IDEiICAgICAgICAgICAgICAgIGtleVNwbGluZXM9IjAuMTY1LCAwLjg0LCAwLjQ0LCAxIiAgICAgICAgICAgICAgICByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgLz4gICAgICAgICAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJzdHJva2Utb3BhY2l0eSIgICAgICAgICAgICAgICAgYmVnaW49IjBzIiBkdXI9IjEuNHMiICAgICAgICAgICAgICAgIHZhbHVlcz0iMTsgMCIgICAgICAgICAgICAgICAgY2FsY01vZGU9InNwbGluZSIgICAgICAgICAgICAgICAga2V5VGltZXM9IjA7IDEiICAgICAgICAgICAgICAgIGtleVNwbGluZXM9IjAuMywgMC42MSwgMC4zNTUsIDEiICAgICAgICAgICAgICAgIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiAvPiAgICAgICAgPC9jaXJjbGU+ICAgICAgICA8Y2lyY2xlIGN4PSIyMiIgY3k9IjIyIiByPSIxIj4gICAgICAgICAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJyIiAgICAgICAgICAgICAgICBiZWdpbj0iLTAuOXMiIGR1cj0iMS40cyIgICAgICAgICAgICAgICAgdmFsdWVzPSIxOyAyMCIgICAgICAgICAgICAgICAgY2FsY01vZGU9InNwbGluZSIgICAgICAgICAgICAgICAga2V5VGltZXM9IjA7IDEiICAgICAgICAgICAgICAgIGtleVNwbGluZXM9IjAuMTY1LCAwLjg0LCAwLjQ0LCAxIiAgICAgICAgICAgICAgICByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgLz4gICAgICAgICAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJzdHJva2Utb3BhY2l0eSIgICAgICAgICAgICAgICAgYmVnaW49Ii0wLjlzIiBkdXI9IjEuNHMiICAgICAgICAgICAgICAgIHZhbHVlcz0iMTsgMCIgICAgICAgICAgICAgICAgY2FsY01vZGU9InNwbGluZSIgICAgICAgICAgICAgICAga2V5VGltZXM9IjA7IDEiICAgICAgICAgICAgICAgIGtleVNwbGluZXM9IjAuMywgMC42MSwgMC4zNTUsIDEiICAgICAgICAgICAgICAgIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiAvPiAgICAgICAgPC9jaXJjbGU+ICAgIDwvZz48L3N2Zz4=) no-repeat 50% 50%;position:absolute;left:0;right:0;top:0;bottom:0;z-index:9}.iziModal .iziModal-content-loader{background:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDQiIGhlaWdodD0iNDQiIHZpZXdCb3g9IjAgMCA0NCA0NCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBzdHJva2U9IiM5OTkiPiAgICA8ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0cm9rZS13aWR0aD0iMiI+ICAgICAgICA8Y2lyY2xlIGN4PSIyMiIgY3k9IjIyIiByPSIxIj4gICAgICAgICAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJyIiAgICAgICAgICAgICAgICBiZWdpbj0iMHMiIGR1cj0iMS40cyIgICAgICAgICAgICAgICAgdmFsdWVzPSIxOyAyMCIgICAgICAgICAgICAgICAgY2FsY01vZGU9InNwbGluZSIgICAgICAgICAgICAgICAga2V5VGltZXM9IjA7IDEiICAgICAgICAgICAgICAgIGtleVNwbGluZXM9IjAuMTY1LCAwLjg0LCAwLjQ0LCAxIiAgICAgICAgICAgICAgICByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgLz4gICAgICAgICAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJzdHJva2Utb3BhY2l0eSIgICAgICAgICAgICAgICAgYmVnaW49IjBzIiBkdXI9IjEuNHMiICAgICAgICAgICAgICAgIHZhbHVlcz0iMTsgMCIgICAgICAgICAgICAgICAgY2FsY01vZGU9InNwbGluZSIgICAgICAgICAgICAgICAga2V5VGltZXM9IjA7IDEiICAgICAgICAgICAgICAgIGtleVNwbGluZXM9IjAuMywgMC42MSwgMC4zNTUsIDEiICAgICAgICAgICAgICAgIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiAvPiAgICAgICAgPC9jaXJjbGU+ICAgICAgICA8Y2lyY2xlIGN4PSIyMiIgY3k9IjIyIiByPSIxIj4gICAgICAgICAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJyIiAgICAgICAgICAgICAgICBiZWdpbj0iLTAuOXMiIGR1cj0iMS40cyIgICAgICAgICAgICAgICAgdmFsdWVzPSIxOyAyMCIgICAgICAgICAgICAgICAgY2FsY01vZGU9InNwbGluZSIgICAgICAgICAgICAgICAga2V5VGltZXM9IjA7IDEiICAgICAgICAgICAgICAgIGtleVNwbGluZXM9IjAuMTY1LCAwLjg0LCAwLjQ0LCAxIiAgICAgICAgICAgICAgICByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgLz4gICAgICAgICAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJzdHJva2Utb3BhY2l0eSIgICAgICAgICAgICAgICAgYmVnaW49Ii0wLjlzIiBkdXI9IjEuNHMiICAgICAgICAgICAgICAgIHZhbHVlcz0iMTsgMCIgICAgICAgICAgICAgICAgY2FsY01vZGU9InNwbGluZSIgICAgICAgICAgICAgICAga2V5VGltZXM9IjA7IDEiICAgICAgICAgICAgICAgIGtleVNwbGluZXM9IjAuMywgMC42MSwgMC4zNTUsIDEiICAgICAgICAgICAgICAgIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiAvPiAgICAgICAgPC9jaXJjbGU+ICAgIDwvZz48L3N2Zz4=) no-repeat 50% 50%}.iziModal .iziModal-content:after,.iziModal .iziModal-content:before{content:'';display:table}.iziModal .iziModal-content:after{clear:both}.iziModal .iziModal-content{zoom:1;width:100%;-webkit-overflow-scrolling:touch}.iziModal .iziModal-wrap{width:100%;position:relative;-webkit-overflow-scrolling:touch;overflow-scrolling:touch}.iziModal .iziModal-iframe{border:0;margin:0 0 -6px;width:100%;transition:height .3s ease}.iziModal-overlay{display:block;position:fixed;top:0;left:0;height:100%;width:100%}.iziModal-navigate{position:fixed;left:0;right:0;top:0;bottom:0;pointer-events:none}.iziModal-navigate-caption{position:absolute;left:10px;top:10px;color:#fff;line-height:16px;font-size:9px;font-family:'Lato',Arial;letter-spacing:.1em;text-indent:0;text-align:center;width:70px;padding:5px 0;text-transform:uppercase;display:none}.iziModal-navigate-caption::after,.iziModal-navigate-caption::before{position:absolute;top:2px;width:20px;height:20px;text-align:center;line-height:14px;font-size:12px;content:'';background-size:100%!important}.iziModal-navigate-caption:before{left:0;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAoCAYAAACFFRgXAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA4ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTMyIDc5LjE1OTI4NCwgMjAxNi8wNC8xOS0xMzoxMzo0MCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDoyNmFjNjAyMy04OWU0LWE0NDAtYmMxMy1kOTA5MTQ3MmYzYjAiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NDREQ0YwRjA1MzQzMTFFNkE5NUNDRDkyQzEwMzM5RTMiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NDREQ0YwRUY1MzQzMTFFNkE5NUNDRDkyQzEwMzM5RTMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUuNSAoV2luZG93cykiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpmNmM0Nzk3Ni1mNzE3LTk5NDAtYTgyYS1mNTdjNmNiYmU0NWMiIHN0UmVmOmRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDowZGVmYTEyZC01MzM0LTExZTYtYWRkYi04Y2NmYjI5ZTAxNjYiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7oo0ptAAACWklEQVR42uyZTWsTYRSFZybxo4kWk5g2NC5qTAU3Kq30A9udi1oXolV/hWuhv6R/Q6utioi4LbbVFHemamlRU0OCEk0wZjwXzwtDoBDopHMHcuFJMplZnLm5ue+589qu61qeOApyYAjEgG0FEyLqN/gKiqBuTtgewWlwCZw056xgwwirgU3wxSv4NJgCUV5YBRXQDEhsBJwCSSauBVZFdJRlIJk9Av7wbj577jDIOENtRmPVwcsw6KfAAvikRKzEDlhnhuU/lRPBWaa9wsxqC6ndPX7OiOA4D8qW3vjO9z7H0w3+KhZstNmOFbLoCQ6DYGmL+bAInmGfLFC4asFXwRJIgB+goVmw+I7HXO+/gevGnGgUPEGxktkSmAMbWmt4HDwBKS6XN1jDKrvEFYoVK7oLroE3h93Woh1eNwqWafJ/gQV65vM+ail34mc6EZwBK2CAx8fAIjjeBYMzDT4cVHCEXtRbRvEu/Nr9HCIOnGGp15vgEec9KYn74B0nAT/CZnv86FcNvwK3wENwAjwAs2Bbs5d4CW5zir0AXvv8p+tKH34B5lkW4h2egRHtbu05uMMHHWfB0zC4NRF5l09kzvE4rd2tyUJyjy4tz7akZqXbL8QETbJ/FsMgWOJtb6brCQ5YsBsC8Uab63DVkkgqFpzie93h8OhScFah2LTHi5ccWroaLd5l6//+hpYQoWP05LKqFs2WQYbTsNxAi+5fxpWmdfh7HS7XhwSzG+H3a2JnvZsyktmLbdOFhpDMvrf4sN1u2/aK0cwMcmYLcturweceW+CnOfFPgAEA8uWFFylBJYoAAAAASUVORK5CYII=) no-repeat 50% 50%}.iziModal-navigate-caption:after{right:0;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAoCAYAAACFFRgXAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAEFkb2JlIEltYWdlUmVhZHlxyWU8AAADhmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxMzIgNzkuMTU5Mjg0LCAyMDE2LzA0LzE5LTEzOjEzOjQwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOjI2YWM2MDIzLTg5ZTQtYTQ0MC1iYzEzLWQ5MDkxNDcyZjNiMCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo0NERDRjBGMDUzNDMxMUU2QTk1Q0NEOTJDMTAzMzlFMyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo0NERDRjBFRjUzNDMxMUU2QTk1Q0NEOTJDMTAzMzlFMyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxNS41IChXaW5kb3dzKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOmY2YzQ3OTc2LWY3MTctOTk0MC1hODJhLWY1N2M2Y2JiZTQ1YyIgc3RSZWY6ZG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOjBkZWZhMTJkLTUzMzQtMTFlNi1hZGRiLThjY2ZiMjllMDE2NiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PuijSm0AAAKbSURBVFhH7ZnJj0xRGEerzFoIMTaCZmOIedhaiJj55yz8DaYdNhIJEUMQbCTG3rQ02hDSiEY553XdTpHS3nv96taV9ElO6lVt6peb7933fffVG41GrYW5uBaX4EysYzcw1Fd8hc/wM2a0Bl6Nm3BW9i0dDPsQX/olBF6FO72AH/gG3+N3jL3KBpqGC3ERTsGfeAsHDTyHi71oCXzBe/gaU2A5bscZOIxXTb8OLQNX9i6mElYsg/voqruwfQb2BhODWgqpMYDv0NLsNXC4yd42P1PEwNJj4HBTWdipErLVDfxfMRm408QMvBu3jV6WJ1Zg9/rbeBOP+UNZYgX+iE/Rp+lpPIKliBXYB9IhtPNy3z/T/F6YmDXsChvyBc7Gs3gACxEzsDzBg9iPPXgO92NuYgeWx2h3+AhtaM7jPsyF7aV37XR8gNZYO/pwKY51+xPkG27Fk2joT3gCr2A7NuJ6HMkTeAPadlp3VeMChF7G0P6X3dmfjAXOUxIj6LZkv1ylNuStDZejkL+PS96ScFzRqnDAtI5PoTefvbg7iNNOOwqVRCfYghdxBbpHH8Y7+DcKlUTV7MLLaNghPIrjhf2N2IF34AVcjE44hrXHyE3MwE6/loEzpEcIlqKjeyFiBe7FS+he/gENewMLEyuwXdo8dGWP43UsRazA9g7uDNbwNX8oS8watlsz+ISIGbgSJgN3GgOHlnFq8zNFQraGgT1iFc9iUyU0XsMGHhy9zh6XbvCp4ZuBBWglDBj4OdqLeu0+uRJTwMZ+Dbp/e21P3m97yWe2snsw1LTHmz5C/9lQdwhfGbiq89GwvrrwUT4UAouhN6MzloTRpVuEYI5O9urZYXtrYPGQw2OlZegM163QhrJMfWVgyTq0Qq32C/N7uPz9OknWAAAAAElFTkSuQmCC) no-repeat 50% 50%}.iziModal-navigate>button{position:fixed;bottom:0;top:0;border:0;height:100%;width:84px;background-size:100%!important;cursor:pointer;padding:0;opacity:.2;transition:opacity .3s ease;pointer-events:all;margin:0;outline:0}.iziModal-navigate>button:hover{opacity:1}.iziModal-navigate-prev{left:50%;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALwAAAC8CAYAAADCScSrAAAACXBIWXMAAAsTAAALEwEAmpwYAAA5sGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxMzIgNzkuMTU5Mjg0LCAyMDE2LzA0LzE5LTEzOjEzOjQwICAgICAgICAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgICAgICAgICAgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiCiAgICAgICAgICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICAgICAgICAgICB4bWxuczpwaG90b3Nob3A9Imh0dHA6Ly9ucy5hZG9iZS5jb20vcGhvdG9zaG9wLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIgogICAgICAgICAgICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyI+CiAgICAgICAgIDx4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ+eG1wLmRpZDo2NDkyYzcxMy05ZDM0LTZlNGQtYmUwNi1hMDMyY2Q4NDVjNGU8L3htcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOkRvY3VtZW50SUQ+eG1wLmRpZDo1QjIzMUMxODU3RjcxMUU2ODUzRkRBRjE5RDhDQjZBRDwveG1wTU06RG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOkluc3RhbmNlSUQ+eG1wLmlpZDpjZmMwNzVmNC1kODA3LWI0NDMtYWIwYS02YWVhZjRjMDgxZWE8L3htcE1NOkluc3RhbmNlSUQ+CiAgICAgICAgIDx4bXBNTTpEZXJpdmVkRnJvbSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgIDxzdFJlZjppbnN0YW5jZUlEPnhtcC5paWQ6NjQ5MmM3MTMtOWQzNC02ZTRkLWJlMDYtYTAzMmNkODQ1YzRlPC9zdFJlZjppbnN0YW5jZUlEPgogICAgICAgICAgICA8c3RSZWY6ZG9jdW1lbnRJRD54bXAuZGlkOjY0OTJjNzEzLTlkMzQtNmU0ZC1iZTA2LWEwMzJjZDg0NWM0ZTwvc3RSZWY6ZG9jdW1lbnRJRD4KICAgICAgICAgPC94bXBNTTpEZXJpdmVkRnJvbT4KICAgICAgICAgPHhtcE1NOkhpc3Rvcnk+CiAgICAgICAgICAgIDxyZGY6U2VxPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOmNmYzA3NWY0LWQ4MDctYjQ0My1hYjBhLTZhZWFmNGMwODFlYTwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAxNi0wOC0wMVQxMTo1ODowNC0wMzowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIENDIDIwMTUuNSAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpjaGFuZ2VkPi88L3N0RXZ0OmNoYW5nZWQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICA8L3JkZjpTZXE+CiAgICAgICAgIDwveG1wTU06SGlzdG9yeT4KICAgICAgICAgPHhtcDpDcmVhdG9yVG9vbD5BZG9iZSBQaG90b3Nob3AgQ0MgMjAxNS41IChXaW5kb3dzKTwveG1wOkNyZWF0b3JUb29sPgogICAgICAgICA8eG1wOkNyZWF0ZURhdGU+MjAxNi0wOC0wMVQwOTo0MDo1Ni0wMzowMDwveG1wOkNyZWF0ZURhdGU+CiAgICAgICAgIDx4bXA6TW9kaWZ5RGF0ZT4yMDE2LTA4LTAxVDExOjU4OjA0LTAzOjAwPC94bXA6TW9kaWZ5RGF0ZT4KICAgICAgICAgPHhtcDpNZXRhZGF0YURhdGU+MjAxNi0wOC0wMVQxMTo1ODowNC0wMzowMDwveG1wOk1ldGFkYXRhRGF0ZT4KICAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9wbmc8L2RjOmZvcm1hdD4KICAgICAgICAgPHBob3Rvc2hvcDpDb2xvck1vZGU+MzwvcGhvdG9zaG9wOkNvbG9yTW9kZT4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgICAgPHRpZmY6WFJlc29sdXRpb24+NzIwMDAwLzEwMDAwPC90aWZmOlhSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpZUmVzb2x1dGlvbj43MjAwMDAvMTAwMDA8L3RpZmY6WVJlc29sdXRpb24+CiAgICAgICAgIDx0aWZmOlJlc29sdXRpb25Vbml0PjI8L3RpZmY6UmVzb2x1dGlvblVuaXQ+CiAgICAgICAgIDxleGlmOkNvbG9yU3BhY2U+NjU1MzU8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjE4ODwvZXhpZjpQaXhlbFhEaW1lbnNpb24+CiAgICAgICAgIDxleGlmOlBpeGVsWURpbWVuc2lvbj4xODg8L2V4aWY6UGl4ZWxZRGltZW5zaW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/PvAvv7QAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAAmdJREFUeNrs1LsJQkEQhtH/mtmBgQ8QA7tQK1e7MBBBMbADwzUZEyuQveeDCXbD4TBDay3SWJpYgYCXgJeAl4CXgJeAl4CXgJeAl4CXgJeAF/AS8BLwEvAS8BLwEvAS8BLwEvAS8BLwAl4CXgJeAl4CXv/WJskpyQJ4jQH7Mcmu0C+BV+/Y5/VeF/oV8Ood+7dpDfDqHvsrySHJBXjBDrxgB16wAy/YgRfswAt24AU78IIdeMEOPOywAw+7gIcdeMEOvGAHXrADL9iBF+zAC3bgBTvwsMMOPOwCHnYBD7uAhx14wQ68YAdesAMv2IEX7MDDDjvwsAt42AU87AIedgEPu4CHXcDDDrxgB16wAw877MDDDjvwsAt42AU87AIedgEPu4CHXcDDLuBhB16wAw877MDDLuBhF/CwC3jYBTzsAh52AQ+7gIddwEtjB3+tS/78+Z/V5d9iATz0Ah56AQ+9gIdewEMv4KEX8NALeOgFPPQCHnoBDz3wgh54QQ889NADDz30wEMv4KEX8NALeOgFPPQCHnoBD72Ahx54QQ+8oAde0AMv6IEX9MBDDz3w0EMPPPQCHnoBD72Ah17AQw+8FUAPvKAHXtADL+iBF/TAC3rgBT3wgh546KEHHnrogYdewEMv4KEHXtADL+iBF/TAC3rgBT3wgh54QQ+8oAde0AMv6IGHHnrgoU/yrgFe3aO/JdknuQOv3tGfC/tjjEsYWmsoyIWXgJeAl4CXgJeAl4CXgJeAl4CXgJeAF/AS8BLwEvAS8BLwEvAS8BLwEvAS8BLwAl4CXgJeAl4CXvqnPgAAAP//AwCEcoCBRabYzAAAAABJRU5ErkJggg==) no-repeat 50% 50%}.iziModal-navigate-next{right:50%;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALwAAAC8CAYAAADCScSrAAAACXBIWXMAAB3SAAAd0gEUasEwAAA7pGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxMzIgNzkuMTU5Mjg0LCAyMDE2LzA0LzE5LTEzOjEzOjQwICAgICAgICAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgICAgICAgICAgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIgogICAgICAgICAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgICAgICAgICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIj4KICAgICAgICAgPHhtcDpDcmVhdG9yVG9vbD5BZG9iZSBQaG90b3Nob3AgQ0MgMjAxNS41IChXaW5kb3dzKTwveG1wOkNyZWF0b3JUb29sPgogICAgICAgICA8eG1wOkNyZWF0ZURhdGU+MjAxNi0wOC0wMVQwOTo0MDoxNC0wMzowMDwveG1wOkNyZWF0ZURhdGU+CiAgICAgICAgIDx4bXA6TW9kaWZ5RGF0ZT4yMDE2LTA4LTAxVDExOjU4OjEyLTAzOjAwPC94bXA6TW9kaWZ5RGF0ZT4KICAgICAgICAgPHhtcDpNZXRhZGF0YURhdGU+MjAxNi0wOC0wMVQxMTo1ODoxMi0wMzowMDwveG1wOk1ldGFkYXRhRGF0ZT4KICAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9wbmc8L2RjOmZvcm1hdD4KICAgICAgICAgPHBob3Rvc2hvcDpDb2xvck1vZGU+MzwvcGhvdG9zaG9wOkNvbG9yTW9kZT4KICAgICAgICAgPHhtcE1NOkluc3RhbmNlSUQ+eG1wLmlpZDphZjljN2Q2MC00MTg2LWE3NGQtYTBiMS1mMGU5ODUwYzg2ZGY8L3htcE1NOkluc3RhbmNlSUQ+CiAgICAgICAgIDx4bXBNTTpEb2N1bWVudElEPnhtcC5kaWQ6NjQ5MmM3MTMtOWQzNC02ZTRkLWJlMDYtYTAzMmNkODQ1YzRlPC94bXBNTTpEb2N1bWVudElEPgogICAgICAgICA8eG1wTU06T3JpZ2luYWxEb2N1bWVudElEPnhtcC5kaWQ6NjQ5MmM3MTMtOWQzNC02ZTRkLWJlMDYtYTAzMmNkODQ1YzRlPC94bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpIaXN0b3J5PgogICAgICAgICAgICA8cmRmOlNlcT4KICAgICAgICAgICAgICAgPHJkZjpsaSByZGY6cGFyc2VUeXBlPSJSZXNvdXJjZSI+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDphY3Rpb24+Y3JlYXRlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjY0OTJjNzEzLTlkMzQtNmU0ZC1iZTA2LWEwMzJjZDg0NWM0ZTwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAxNi0wOC0wMVQwOTo0MDoxNC0wMzowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIENDIDIwMTUuNSAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjAxNjJjMmE3LWZmMjYtYzE0ZC05Yjg4LTc2MGM2NzAxYjYzNzwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAxNi0wOC0wMVQxMTo1MTowNy0wMzowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIENDIDIwMTUuNSAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpjaGFuZ2VkPi88L3N0RXZ0OmNoYW5nZWQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOmFmOWM3ZDYwLTQxODYtYTc0ZC1hMGIxLWYwZTk4NTBjODZkZjwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAxNi0wOC0wMVQxMTo1ODoxMi0wMzowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIENDIDIwMTUuNSAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDpjaGFuZ2VkPi88L3N0RXZ0OmNoYW5nZWQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICA8L3JkZjpTZXE+CiAgICAgICAgIDwveG1wTU06SGlzdG9yeT4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgICAgPHRpZmY6WFJlc29sdXRpb24+MTkzOTAzNi8xMDAwMDwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+MTkzOTAzNi8xMDAwMDwvdGlmZjpZUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6UmVzb2x1dGlvblVuaXQ+MjwvdGlmZjpSZXNvbHV0aW9uVW5pdD4KICAgICAgICAgPGV4aWY6Q29sb3JTcGFjZT42NTUzNTwvZXhpZjpDb2xvclNwYWNlPgogICAgICAgICA8ZXhpZjpQaXhlbFhEaW1lbnNpb24+MTg4PC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjE4ODwvZXhpZjpQaXhlbFlEaW1lbnNpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIAo8P3hwYWNrZXQgZW5kPSJ3Ij8+nbt1mgAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAACQklEQVR42uzSsQ3CQAAEQTdiOyGg/wrciJ0QUMYSECEKAP3PSdvAaZZqkWbJCQJeAl4CXgJeAl4CXgJeAl4CXgJeAl4CXsBLwEvAS8BLwEvAS8BLwEvAS8BLwEvAC3gJeAl4CXgJ+D9vrY7qBgLwo7dVZ+89oAd+5Pbq6nPQAz9s9+rZ96AHHnoBD72Ah17AQy/goRfw0At46AU89AIeegEPvYCHHnhBD7ygBx566IGHHnrgoRfw0At46AU89AIeegEPvYCHXsBDL+ChB17QAy/ogRf0wAt64KGHHnjooQceegEPvYCHXsBDL+ChF/DQAy/ogRf0wAt64AU98IIeeEEPvKAHXtADDz30wEPvI+ChF/DQAy/ogRf0wAt64AU98IIeeEEPvKAHXtADL+iBF/TAC3rgoZ8ePRDAAy/YgRfswAt24AU78IIdeMEOvGAHXrADL9iBhx124GEX8LADL9iBF+zAC3bgBTvwgh14wQ68YAcedtiBh13Awy7gYRfwsAMv2IEX7MALduAFO/CCHXjYYQcedgEPu4CHXcDDLuBhF/CwA+8E2IEX7MALduAFO/Cwww487AIedgEPu4CHXcDDLuBhF/CwC3jYgRfswMMOO/CwC3jYBTzsAh52AQ+7gIddwMMu4GEX8LBravB7dcEO/Ext1Qk78DO1VgfswEvAS8BLwEvAS8BLwEvAS8BLwEvAS8ALeAl4CXgJeAl4CXgJeAl4CXgJeAl4CXgBLwEvAS8BLwEvAS/9shcAAAD//wMAtAygvJrkwJUAAAAASUVORK5CYII=) no-repeat 50% 50%}.iziModal.isAttachedTop .iziModal-header{border-top-left-radius:0;border-top-right-radius:0}.iziModal.isAttachedTop{margin-top:0!important;margin-bottom:auto!important;border-top-left-radius:0!important;border-top-right-radius:0!important}.iziModal.isAttachedBottom{margin-top:auto!important;margin-bottom:0!important;border-bottom-left-radius:0!important;border-bottom-right-radius:0!important}.iziModal.isFullscreen{max-width:100%!important;margin:0!important;height:100%!important}.iziModal.isAttached,.iziModal.isFullscreen{border-radius:0!important}.iziModal.hasScroll .iziModal-wrap{overflow-y:auto;overflow-x:hidden}html.iziModal-isAttached,html.iziModal-isOverflow{overflow:hidden}html.iziModal-isAttached body,html.iziModal-isOverflow body{overflow-y:scroll;position:relative}.iziModal ::-webkit-scrollbar{overflow:visible;height:7px;width:7px}.iziModal ::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,.2);background-clip:padding-box;border:solid transparent;border-width:0;min-height:28px;padding:100px 0 0;box-shadow:inset 1px 1px 0 rgba(0,0,0,.1),inset 0 -1px 0 rgba(0,0,0,.07)}.iziModal ::-webkit-scrollbar-thumb:active{background-color:rgba(0,0,0,.4)}.iziModal ::-webkit-scrollbar-button{height:0;width:0}.iziModal ::-webkit-scrollbar-track{background-clip:padding-box;border:solid transparent;border-width:0 0 0 2px}.iziModal.transitionIn .iziModal-header{-webkit-animation:iziM-slideDown .7s cubic-bezier(.7,0,.3,1);-moz-animation:iziM-slideDown .7s cubic-bezier(.7,0,.3,1);animation:iziM-slideDown .7s cubic-bezier(.7,0,.3,1)}.iziModal.transitionIn .iziModal-header .iziModal-header-icon{-webkit-animation:iziM-revealIn 1s cubic-bezier(.16,.81,.32,1) both;-moz-animation:iziM-revealIn 1s cubic-bezier(.16,.81,.32,1) both;animation:iziM-revealIn 1s cubic-bezier(.16,.81,.32,1) both}.iziModal.transitionIn .iziModal-header .iziModal-header-subtitle,.iziModal.transitionIn .iziModal-header .iziModal-header-title{-webkit-animation:iziM-slideIn 1s cubic-bezier(.16,.81,.32,1) both;-moz-animation:iziM-slideIn 1s cubic-bezier(.16,.81,.32,1) both;animation:iziM-slideIn 1s cubic-bezier(.16,.81,.32,1) both}.iziModal.transitionIn .iziModal-header .iziModal-button{-webkit-animation:iziM-revealIn 1.2s cubic-bezier(.7,0,.3,1);-moz-animation:iziM-revealIn 1.2s cubic-bezier(.7,0,.3,1);animation:iziM-revealIn 1.2s cubic-bezier(.7,0,.3,1)}.iziModal.transitionIn .iziModal-iframe,.iziModal.transitionIn .iziModal-wrap{-webkit-animation:iziM-fadeIn 1.3s;-moz-animation:iziM-fadeIn 1.3s;animation:iziM-fadeIn 1.3s}.iziModal.transitionIn .iziModal-header{-webkit-animation-delay:0s;-moz-animation:0s;animation-delay:0s}.iziModal.transitionIn .iziModal-header .iziModal-header-icon,.iziModal.transitionIn .iziModal-header .iziModal-header-title{-webkit-animation-delay:.4s;-moz-animation:.4s;animation-delay:.4s}.iziModal.transitionIn .iziModal-header .iziModal-header-subtitle{-webkit-animation-delay:.5s;-moz-animation:.5s;animation-delay:.5s}.iziModal.transitionOut .iziModal-header,.iziModal.transitionOut .iziModal-header *{transition:none!important}.iziModal .fadeOut,.iziModal-navigate.fadeOut,.iziModal-overlay.fadeOut,.iziModal.fadeOut{-webkit-animation:iziM-fadeOut .5s;-moz-animation:iziM-fadeOut .5s;animation:iziM-fadeOut .5s;animation-fill-mode:forwards}.iziModal .fadeIn,.iziModal-navigate.fadeIn,.iziModal-overlay.fadeIn,.iziModal.fadeIn{-webkit-animation:iziM-fadeIn .5s;-moz-animation:iziM-fadeIn .5s;animation:iziM-fadeIn .5s}.iziModal-overlay.comingIn,.iziModal.comingIn{-webkit-animation:iziM-comingIn .5s ease;-moz-animation:iziM-comingIn .5s ease;animation:iziM-comingIn .5s ease}.iziModal-overlay.comingOut,.iziModal.comingOut{-webkit-animation:iziM-comingOut .5s cubic-bezier(.16,.81,.32,1);-moz-animation:iziM-comingOut .5s cubic-bezier(.16,.81,.32,1);animation:iziM-comingOut .5s cubic-bezier(.16,.81,.32,1);animation-fill-mode:forwards}.iziModal-overlay.bounceInDown,.iziModal.bounceInDown{-webkit-animation:iziM-bounceInDown .7s ease;animation:iziM-bounceInDown .7s ease}.iziModal-overlay.bounceOutDown,.iziModal.bounceOutDown{-webkit-animation:iziM-bounceOutDown .7s ease;animation:iziM-bounceOutDown .7s ease}.iziModal-overlay.bounceInUp,.iziModal.bounceInUp{-webkit-animation:iziM-bounceInUp .7s ease;animation:iziM-bounceInUp .7s ease}.iziModal-overlay.bounceOutUp,.iziModal.bounceOutUp{-webkit-animation:iziM-bounceOutUp .7s ease;animation:iziM-bounceOutUp .7s ease}.iziModal-overlay.fadeInDown,.iziModal.fadeInDown{-webkit-animation:iziM-fadeInDown .7s cubic-bezier(.16,.81,.32,1);animation:iziM-fadeInDown .7s cubic-bezier(.16,.81,.32,1)}.iziModal-overlay.fadeOutDown,.iziModal.fadeOutDown{-webkit-animation:iziM-fadeOutDown .5s ease;animation:iziM-fadeOutDown .5s ease}.iziModal-overlay.fadeInUp,.iziModal.fadeInUp{-webkit-animation:iziM-fadeInUp .7s cubic-bezier(.16,.81,.32,1);animation:iziM-fadeInUp .7s cubic-bezier(.16,.81,.32,1)}.iziModal-overlay.fadeOutUp,.iziModal.fadeOutUp{-webkit-animation:iziM-fadeOutUp .5s ease;animation:iziM-fadeOutUp .5s ease}.iziModal-overlay.fadeInLeft,.iziModal.fadeInLeft{-webkit-animation:iziM-fadeInLeft .7s cubic-bezier(.16,.81,.32,1);animation:iziM-fadeInLeft .7s cubic-bezier(.16,.81,.32,1)}.iziModal-overlay.fadeOutLeft,.iziModal.fadeOutLeft{-webkit-animation:iziM-fadeOutLeft .5s ease;animation:iziM-fadeOutLeft .5s ease}.iziModal-overlay.fadeInRight,.iziModal.fadeInRight{-webkit-animation:iziM-fadeInRight .7s cubic-bezier(.16,.81,.32,1);animation:iziM-fadeInRight .7s cubic-bezier(.16,.81,.32,1)}.iziModal-overlay.fadeOutRight,.iziModal.fadeOutRight{-webkit-animation:iziM-fadeOutRight .5s ease;animation:iziM-fadeOutRight .5s ease}.iziModal-overlay.flipInX,.iziModal.flipInX{-webkit-animation:iziM-flipInX .7s ease;animation:iziM-flipInX .7s ease}.iziModal-overlay.flipOutX,.iziModal.flipOutX{-webkit-animation:iziM-flipOutX .7s ease;animation:iziM-flipOutX .7s ease}@-webkit-keyframes iziM-comingIn{0%{opacity:0;transform:scale(.9) translateY(-20px) perspective(600px) rotateX(10deg)}to{opacity:1;transform:scale(1) translateY(0) perspective(600px) rotateX(0)}}@-moz-keyframes iziM-comingIn{0%{opacity:0;transform:scale(.9) translateY(-20px) perspective(600px) rotateX(10deg)}to{opacity:1;transform:scale(1) translateY(0) perspective(600px) rotateX(0)}}@keyframes iziM-comingIn{0%{opacity:0;transform:scale(.9) translateY(-20px) perspective(600px) rotateX(10deg)}to{opacity:1;transform:scale(1) translateY(0) perspective(600px) rotateX(0)}}@-webkit-keyframes iziM-comingOut{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.9)}}@-moz-keyframes iziM-comingOut{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.9)}}@keyframes iziM-comingOut{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.9)}}@-webkit-keyframes iziM-fadeOut{0%{opacity:1}to{opacity:0}}@-moz-keyframes iziM-fadeOut{0%{opacity:1}to{opacity:0}}@keyframes iziM-fadeOut{0%{opacity:1}to{opacity:0}}@-webkit-keyframes iziM-fadeIn{0%{opacity:0}to{opacity:1}}@-moz-keyframes iziM-fadeIn{0%{opacity:0}to{opacity:1}}@keyframes iziM-fadeIn{0%{opacity:0}to{opacity:1}}@-webkit-keyframes iziM-slideIn{0%{opacity:0;-webkit-transform:translateX(50px)}to{opacity:1;-webkit-transform:translateX(0)}}@-moz-keyframes iziM-slideIn{0%{opacity:0;-moz-transform:translateX(50px)}to{opacity:1;-moz-transform:translateX(0)}}@keyframes iziM-slideIn{0%{opacity:0;transform:translateX(50px)}to{opacity:1;transform:translateX(0)}}@-webkit-keyframes iziM-slideDown{0%{opacity:0;-webkit-transform:scale(1,0) translateY(-40px);-webkit-transform-origin:center top}}@-moz-keyframes iziM-slideDown{0%{opacity:0;-moz-transform:scale(1,0) translateY(-40px);-moz-transform-origin:center top}}@keyframes iziM-slideDown{0%{opacity:0;transform:scale(1,0) translateY(-40px);transform-origin:center top}}@-webkit-keyframes iziM-revealIn{0%{opacity:0;-webkit-transform:scale3d(.3,.3,1)}}@-moz-keyframes iziM-revealIn{0%{opacity:0;-moz-transform:scale3d(.3,.3,1)}}@keyframes iziM-revealIn{0%{opacity:0;transform:scale3d(.3,.3,1)}}@-webkit-keyframes iziM-bounceInDown{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,-1000px,0);transform:translate3d(0,-1000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,25px,0);transform:translate3d(0,25px,0)}75%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}90%{-webkit-transform:translate3d(0,5px,0);transform:translate3d(0,5px,0)}to{-webkit-transform:none;transform:none}}@keyframes iziM-bounceInDown{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,-1000px,0);transform:translate3d(0,-1000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,25px,0);transform:translate3d(0,25px,0)}75%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}90%{-webkit-transform:translate3d(0,5px,0);transform:translate3d(0,5px,0)}to{-webkit-transform:none;transform:none}}@-webkit-keyframes iziM-bounceOutDown{20%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}to{opacity:0;-webkit-transform:translate3d(0,1000px,0);transform:translate3d(0,1000px,0)}}@keyframes iziM-bounceOutDown{20%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}to{opacity:0;-webkit-transform:translate3d(0,1000px,0);transform:translate3d(0,1000px,0)}}@-webkit-keyframes iziM-bounceInUp{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,1000px,0);transform:translate3d(0,1000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}75%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}90%{-webkit-transform:translate3d(0,-5px,0);transform:translate3d(0,-5px,0)}to{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}@keyframes iziM-bounceInUp{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;-webkit-transform:translate3d(0,1000px,0);transform:translate3d(0,1000px,0)}60%{opacity:1;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}75%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}90%{-webkit-transform:translate3d(0,-5px,0);transform:translate3d(0,-5px,0)}to{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}@-webkit-keyframes iziM-bounceOutUp{20%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,20px,0);transform:translate3d(0,20px,0)}to{opacity:0;-webkit-transform:translate3d(0,-2000px,0);transform:translate3d(0,-2000px,0)}}@keyframes iziM-bounceOutUp{20%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}40%,45%{opacity:1;-webkit-transform:translate3d(0,20px,0);transform:translate3d(0,20px,0)}to{opacity:0;-webkit-transform:translate3d(0,-1000px,0);transform:translate3d(0,-1000px,0)}}@-webkit-keyframes iziM-fadeInDown{0%{opacity:0;-webkit-transform:translate3d(0,-100px,0);transform:translate3d(0,-100px,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes iziM-fadeInDown{0%{opacity:0;-webkit-transform:translate3d(0,-100px,0);transform:translate3d(0,-100px,0)}to{opacity:1;-webkit-transform:none;transform:none}}@-webkit-keyframes iziM-fadeOutDown{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,100px,0);transform:translate3d(0,100px,0)}}@keyframes iziM-fadeOutDown{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,100px,0);transform:translate3d(0,100px,0)}}@-webkit-keyframes iziM-fadeInUp{0%{opacity:0;-webkit-transform:translate3d(0,100px,0);transform:translate3d(0,100px,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes iziM-fadeInUp{0%{opacity:0;-webkit-transform:translate3d(0,100px,0);transform:translate3d(0,100px,0)}to{opacity:1;-webkit-transform:none;transform:none}}@-webkit-keyframes iziM-fadeOutUp{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,-100px,0);transform:translate3d(0,-100px,0)}}@keyframes iziM-fadeOutUp{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(0,-100px,0);transform:translate3d(0,-100px,0)}}@-webkit-keyframes iziM-fadeInLeft{0%{opacity:0;-webkit-transform:translate3d(-200px,0,0);transform:translate3d(-200px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes iziM-fadeInLeft{0%{opacity:0;-webkit-transform:translate3d(-200px,0,0);transform:translate3d(-200px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}@-webkit-keyframes iziM-fadeOutLeft{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(-200px,0,0);transform:translate3d(-200px,0,0)}}@keyframes iziM-fadeOutLeft{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(-200px,0,0);transform:translate3d(-200px,0,0)}}@-webkit-keyframes iziM-fadeInRight{0%{opacity:0;-webkit-transform:translate3d(200px,0,0);transform:translate3d(200px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}@keyframes iziM-fadeInRight{0%{opacity:0;-webkit-transform:translate3d(200px,0,0);transform:translate3d(200px,0,0)}to{opacity:1;-webkit-transform:none;transform:none}}@-webkit-keyframes iziM-fadeOutRight{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(200px,0,0);transform:translate3d(200px,0,0)}}@keyframes iziM-fadeOutRight{0%{opacity:1}to{opacity:0;-webkit-transform:translate3d(200px,0,0);transform:translate3d(200px,0,0)}}@-webkit-keyframes iziM-flipInX{0%{-webkit-transform:perspective(400px) rotateX(60deg);opacity:0}40%{-webkit-transform:perspective(400px) rotateX(-10deg)}70%{-webkit-transform:perspective(400px) rotateX(10deg)}to{-webkit-transform:perspective(400px) rotateX(0deg);opacity:1}}@keyframes iziM-flipInX{0%{transform:perspective(400px) rotateX(60deg);opacity:0}40%{transform:perspective(400px) rotateX(-10deg)}70%{transform:perspective(400px) rotateX(10deg)}to{transform:perspective(400px) rotateX(0deg);opacity:1}}@-webkit-keyframes iziM-flipOutX{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotate3d(1,0,0,-20deg);transform:perspective(400px) rotate3d(1,0,0,-20deg);opacity:1}to{-webkit-transform:perspective(400px) rotate3d(1,0,0,40deg);transform:perspective(400px) rotate3d(1,0,0,40deg);opacity:0}}@keyframes iziM-flipOutX{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotate3d(1,0,0,-20deg);transform:perspective(400px) rotate3d(1,0,0,-20deg);opacity:1}to{-webkit-transform:perspective(400px) rotate3d(1,0,0,40deg);transform:perspective(400px) rotate3d(1,0,0,40deg);opacity:0}}assets/img/lscwp_font-icon_32px.svg000064400000004632152075713270013341 0ustar00 assets/img/lscwp_gray-yellow_font-icon_22px.svg000064400000003170152075713270015667 0ustar00 assets/img/quic-cloud-icon-16x16.svg000064400000003764152075713270013144 0ustar00 assets/img/lscwp_blue_font-icon_22px.svg000064400000003166152075713270014350 0ustar00 assets/img/quic-cloud-logo-light_stack_300px.png000064400000007133152075713270015574 0ustar00PNG  IHDR,,N~GPLTE>PY>PY>PY>PY>PY>PY>PY>PY>PY>PY>PY>PY>PY>PY>PY\ks\ks\ks\ks\ks\ks\ks\ks\ks\ks\ks\ks\ks\ks\kspppppppppppppppfffffffffffffff>PY\kspf^ۧ_=tRNS 0@P`pp`P@0  0@P`pp`P@0 , IDATxk[8(2"P@xU mmK$4I0{eҡ7o`0!>6-Y 'Ge1Ft=&`y scj ёaSp2 ך qUq8#9 ={z\6\,r,49N73AGn{сLAMIXO&/6ra"wuan\\" U7\1l=.Tmtٽ쮟U~0]U;T,Z03j{"2slM)u-tQ̎ǖX8tImǖxT*3EmYA^u z揭Yj!jDG m"| yP%.3V9Ѱq>D sE X[6 GÄɖI-Fg^&,;Ĕf۹c"%0z !S9lYvHjj5C"XUh*2ٱ%V Aߎ#bKw4+`9UpA~,"l  G;)%G.sLDKB+blSKlHS6V S~l͕ -AV%=ޛ,}zRm2=W f}+^>y"uJ7SK#we=ov<>.Y2~?Xʍ>6OV7W,FuM^S%+ig=o< n`bi)Af"ñm mdųIw%PMz֟M60\V&|OqlIim,mJiRqV>(fR]MY.sǦD[2b|cE2] ~m-eZ є:1- C7j6BsgMyp_o<>l`ʲ8L͌ؒ;o`t>i5-ݖ5 k<㴥BqW,˿-26m_n^WMɈ-ԳSG 濇gⰄ}^}[=,#mNf|ؚyXaOg xwJU嫒bQ,GƙGQ Nnz&xl45w=~̟֖5bl#/-=#p?.A_eeo`|$Ԃ(1)Bb1[:9Vmوb- vakN"Ŗ ֻ[wwD٦~Xƶ8 5lJ-x#$#SzJ-U]4_q][^w/Ʀ/6a[F}>M-}> ]*ϗ7+67y47#clʭߞ)uȘ1̏"ҌKLuEa04)eOZQMv=ɁyZ+9_d{N;o-Pw GпzC3\ [ L?JrDZçV-4(R=*gS=A(#tsVN Y@OGzss^-!NU5Wie).5d6W4eUg>E<(!NJ@$($8PAV}/gIHV5qFh9"Wi9~\ĶdE_D_V53vO /䫈Xdٲޖ[qdCdfQ[*gnVQ]VZѹ\_CZ1Gj(]g)A ܓbK+,kpޣ'Һؑ1u f].s=WU^Tuu5u!0; Q&gAmɪ T-tWZ^XN푽N_v|o>Yez|GVX 7ZLV誋1e%]n#YYQ3=M˺/wdǿe]k{~>-ɓ w8d9 ^mAt Yu6UVJPCVcRsZJUt$f58@ h- ƍ!3H|\gWdu >EIH9++ߔ6"t_o  ɲ,YWVx8B/!] j)6,@,0FزGOuQ+,me]K+ bpMtҞJ=b (gˢw#Q$d!er*fq;=TFqYTYmqYd*#?eEQ#,tm.Rj%GVG4Z 6YЛٷF{CB㦈dV/RYfYiQ&w+Nk 4S^)o~ſԣ,42&3SV2lw6;xBf#Zuծ!B$sQqWfe˝(L[& ISY-a$ BX.pw㙣~z[@Xb椇gMPzs㠧^5ndSRHu8"YRfWLqb?SdnTA-ÂC<~Q94'̝腸7,{VhrXԈed}X>i?Э7\ύ|F M$Ykԃ9ކvd15]VKVbSn-F+KoEofO\ԏrw|ЏwꁎF"""""""""""""""""""""""""""""""""""""zΘcIENDB`assets/img/lscwp_grayscale_font-icon_22px.svg000064400000003357152075713270015375 0ustar00 assets/img/icons/purge-object.svg000064400000014213152075713270013056 0ustar00 assets/img/icons/revision.svg000064400000005522152075713270012331 0ustar00 assets/img/icons/trackback-pingback.svg000064400000005032152075713270014170 0ustar00 assets/img/icons/auto_draft.svg000064400000013031152075713270012615 0ustar00 assets/img/icons/img_optm.svg000064400000013002152075713270012276 0ustar00 assets/img/icons/purge-all.svg000064400000011415152075713270012361 0ustar00 assets/img/icons/cross_icon.svg000064400000006650152075713270012637 0ustar00 assets/img/icons/empty-cache.svg000064400000014224152075713270012671 0ustar00 assets/img/icons/purge-500.svg000064400000010630152075713270012113 0ustar00 assets/img/icons/trash_post.svg000064400000011716152075713270012663 0ustar00 assets/img/icons/purge-front.svg000064400000005562152075713270012747 0ustar00 assets/img/icons/all.svg000064400000015710152075713270011243 0ustar00 assets/img/icons/db.svg000064400000004146152075713270011061 0ustar00 assets/img/icons/optimize_tables.svg000064400000005505152075713270013666 0ustar00 assets/img/icons/all_transients.svg000064400000006441152075713270013516 0ustar00 assets/img/icons/purge-403.svg000064400000011104152075713270012112 0ustar00 assets/img/icons/expired_transient.svg000064400000007250152075713270014222 0ustar00 assets/img/icons/img_optm_disabled.svg000064400000013002152075713270014125 0ustar00 assets/img/icons/success_icon.svg000064400000007225152075713270013155 0ustar00 assets/img/icons/img_webp_disabled.svg000064400000007011152075713270014106 0ustar00 assets/img/icons/purge-404.svg000064400000007735152075713270012132 0ustar00 assets/img/icons/purge-cssjs.svg000064400000011654152075713270012743 0ustar00 assets/img/icons/img_webp.svg000064400000007011152075713270012257 0ustar00 assets/img/icons/purge-pages.svg000064400000007664152075713270012723 0ustar00 assets/img/icons/spam_comment.svg000064400000006656152075713270013166 0ustar00 assets/img/icons/trash_comment.svg000064400000014532152075713270013337 0ustar00 assets/img/icons/purge-opcache.svg000064400000010564152075713270013217 0ustar00 assets/img/lscwp_gray_font-icon_22px.svg000064400000003166152075713270014363 0ustar00 assets/img/lscwp-logo_90x90.png000064400000003224152075713270012301 0ustar00PNG  IHDRZZv0PLTEGpLuռа˝z}jknsvyKdrs3hikc5MefiSy_J1DsAԤ_adج;r{ss/:GQ7im,X'\]_bJ#Do[]`uItRNS`(MlIDATx! EQ,_wryjmEFf@1"!2I[defӁCL)I>anwg/ipI)E d ٛח 2YIi=ˆ8Nm-dDʉ,F;PfRtN!8Qk6mbDL|<9{;8@>bBHʒi (2"m GȘ]=mloK[6:벌T+eەbJrLr\Ƞ\%XbI E&!(-?w {Tor>.O'Op!5EElG/#m<:]L!B6AW6`s\|͠wEvN 9ʷifT 2D;Jڦe;3^AֶCtUYv7hӁֿ>Co$Վ^@t4pHӬ-useY܋"|?Xtf2lt{FB>@%,tx9C+YWufŏn[2ֻH>'ՄO:jky^; 9.Q, wRLSNvu4drK##ahk:kS' ɚX8?}ymYOr܎yrO?:EVIENDB`assets/img/iconlscwp.svg000064400000002065152075713270011360 0ustar00 LiteSpeed Technologies assets/img/slack-logo.png000064400000002271152075713270011376 0ustar00PNG  IHDRKKPLTEGpL8;9::9:;OOOOPOO詟8OԪ8v6ftM ],kzOU{29:VTTTTRU/4e)9GU.E-P{TSGsw]=U,UYgJpu@\NypEhmGDeHtRNS'@[a8P[ 9'Gap "HIDATx՗8EVAa~4ʶǾՕ^eYxp n_R$~LtƐNC HV%3!F\\WP{tJӯTX,imE\P-~ݕGWNjO__W+@Ñh\m7EqP?<<-aḂHĩݥ<7'ѵ++H?R,#<'S<.a$)V8WؾtFŎ#BWDBxeP4?8ځiaZAA&hR$b"h!d?d?T<XBn}Z.1i/T`Hhs *!cdDb["%ۧH*%k"EH B_nbC܉_ZkE"?u9랂Q$<ݤM YT #yu}5_2ۻkFkmDbB&hHG}DY]'@B5rgt7s6`ۧHk>E"`HhߧG>\9o?x_On~R?lrGg>$E2&ܦZ|^5DxoS(H~Oq"4 *hqdS}_?oBS:'o".x[X&ϨWP=ȧnZY\x`F%95si̗M+^)H؃= `;z ^kΨLmx|ѥbhc2.M** {-IENDB`assets/img/quic-cloud-logo.svg000064400000027160152075713270012365 0ustar00 ]> assets/img/Litespeed.icon.svg000064400000005167152075713270012232 0ustar00 assets/js/component.cdn.js000064400000014530152075713270011601 0ustar00/** * CDN module * @author Hai Zheng */ class CDNMapping extends React.Component { constructor(props) { super(props); this.state = { list: props.list, }; this.onChange = this.onChange.bind(this); this.delRow = this.delRow.bind(this); this.addNew = this.addNew.bind(this); } onChange(e, index) { const target = e.currentTarget; const value = target.dataset.hasOwnProperty('value') ? Boolean(target.dataset.value * 1) : target.value; const list = this.state.list; list[index][target.dataset.type] = value; this.setState({ list: list, }); } delRow(index) { const data = this.state.list; data.splice(index, 1); this.setState({ list: data }); } addNew() { const list = this.state.list; list.push({ url: '' }); this.setState({ list: list }); } render() { return ( {this.state.list.map((item, i) => ( ))}

); } } // { url: '', inc_img: true, inc_css: false, inc_js: false, filetype: [ '.aac', '.eot', ... ] } class CDNMappingBlock extends React.Component { constructor(props) { super(props); this.onChange = this.onChange.bind(this); this.delRow = this.delRow.bind(this); } onChange(e) { this.props.onChange(e, this.props.index); } delRow() { this.props.delRow(this.props.index); } render() { const name_prefix = litespeed_data['ids']['cdn_mapping']; const item = this.props.item; const filetype = item.filetype ? (Array.isArray(item.filetype) ? item.filetype.join('\n') : item.filetype) : ''; return (
{litespeed_data['lang']['cdn_mapping_inc_img']}
{litespeed_data['lang']['cdn_mapping_inc_css']}
{litespeed_data['lang']['cdn_mapping_inc_js']}
'; $this->_check_overwritten( $id ); } /** * Calculate textarea rows. * * @since 7.4 * * @param string $val Text area value. * @return int Number of rows to use. */ public function get_textarea_rows( $val ) { $rows = 5; $lines = substr_count( (string) $val, "\n" ) + 2; if ( $lines > $rows ) { $rows = $lines; } if ( $rows > 40 ) { $rows = 40; } return $rows; } /** * Build a text input field. * * @since 1.1.0 * * @param string $id Setting ID. * @param string|null $cls CSS class. * @param string|null $val Value. * @param string $type Input type. * @param bool $disabled Whether disabled. * @return void */ public function build_input( $id, $cls = null, $val = null, $type = 'text', $disabled = false ) { if ( null === $val ) { $val = $this->conf( $id, true ); // Mask passwords. if ( $this->_conf_pswd( $id ) && $val ) { $val = str_repeat( '*', strlen( $val ) ); } } $label_id = preg_replace( '/\W/', '', $id ); if ( 'text' === $type ) { $cls = "regular-text $cls"; } if ( $disabled ) { echo " "; } else { $this->enroll( $id ); echo " "; } $this->_check_overwritten( $id ); } /** * Build a checkbox HTML snippet. * * @since 1.1.0 * * @param string $id Setting ID. * @param string $title Checkbox label (HTML allowed). * @param bool|null $checked Whether checked. * @param int|string $value Checkbox value. * @return void */ public function build_checkbox( $id, $title, $checked = null, $value = 1 ) { if ( null === $checked && $this->conf( $id, true ) ) { $checked = true; } $label_id = preg_replace( '/\W/', '', $id ); if ( 1 !== $value ) { $label_id .= '_' . $value; } $this->enroll( $id ); echo "
'; $this->_check_overwritten( $id ); } /** * Build a toggle checkbox snippet. * * @since 1.7 * * @param string $id Setting ID. * @param bool|null $checked Whether enabled. * @param string|null $title_on Label when on. * @param string|null $title_off Label when off. * @return void */ public function build_toggle( $id, $checked = null, $title_on = null, $title_off = null ) { if ( null === $checked && $this->conf( $id, true ) ) { $checked = true; } if ( null === $title_on ) { $title_on = __( 'ON', 'litespeed-cache' ); $title_off = __( 'OFF', 'litespeed-cache' ); } $cls = $checked ? 'primary' : 'default litespeed-toggleoff'; echo "
"; } /** * Build a switch (radio) field. * * @since 1.1.0 * @since 1.7 Removed $disable param. * * @param string $id Setting ID. * @param array|false $title_list Labels for options (OFF/ON). * @return void */ public function build_switch( $id, $title_list = false ) { $this->enroll( $id ); echo '
'; if ( ! $title_list ) { $title_list = [ __( 'OFF', 'litespeed-cache' ), __( 'ON', 'litespeed-cache' ) ]; } foreach ( $title_list as $k => $v ) { $this->_build_radio( $id, $k, $v ); } echo '
'; $this->_check_overwritten( $id ); } /** * Build a radio input and echo it. * * @since 1.1.0 * @access private * * @param string $id Setting ID. * @param int|string $val Value for the radio. * @param string $txt Label HTML. * @return void */ private function _build_radio( $id, $val, $txt ) { $id_attr = 'input_radio_' . preg_replace( '/\W/', '', $id ) . '_' . $val; $default = isset( self::$_default_options[ $id ] ) ? self::$_default_options[ $id ] : self::$_default_site_options[ $id ]; $is_checked = ! is_string( $default ) ? ( (int) $this->conf( $id, true ) === (int) $val ) : ( $this->conf( $id, true ) === $val ); echo " '; } /** * Show overwritten info if value comes from const/primary/filter/server. * * @since 3.0 * @since 7.4 Show value from filters. Added type parameter. * * @param string $id Setting ID. * @return void */ protected function _check_overwritten( $id ) { $const_val = $this->const_overwritten( $id ); $primary_val = $this->primary_overwritten( $id ); $deprecated_filter_val = $this->deprecated_filter_overwritten( $id ); $filter_val = $this->filter_overwritten( $id ); $server_val = $this->server_overwritten( $id ); if ( null === $const_val && null === $primary_val && null === $deprecated_filter_val && null === $filter_val && null === $server_val ) { return; } // Get value to display. $val = null !== $const_val ? $const_val : $primary_val; // If we have deprecated_filter_val will set as new val. if ( null !== $deprecated_filter_val ) { $val = $deprecated_filter_val; } // If we have filter_val will set as new val. if ( null !== $filter_val ) { $val = $filter_val; } // If we have server_val will set as new val. if ( null !== $server_val ) { $val = $server_val; } // Get type (used for display purpose). $type = ( isset( self::$settings_filters[ $id ] ) && isset( self::$settings_filters[ $id ]['type'] ) ) ? self::$settings_filters[ $id ]['type'] : 'textarea'; if ( ( null !== $const_val || null !== $primary_val || null !== $filter_val ) && null === $deprecated_filter_val ) { $type = 'setting'; } // Get default setting: if settings exist, use default setting, otherwise use filter/server value. $default = ''; if ( isset( self::$_default_options[ $id ] ) || isset( self::$_default_site_options[ $id ] ) ) { $default = isset( self::$_default_options[ $id ] ) ? self::$_default_options[ $id ] : self::$_default_site_options[ $id ]; } if ( null !== $deprecated_filter_val || null !== $server_val ) { $default = null !== $deprecated_filter_val ? $deprecated_filter_val : $server_val; } // Set value to display, will be a string. if ( is_bool( $default ) ) { $val = $val ? __( 'ON', 'litespeed-cache' ) : __( 'OFF', 'litespeed-cache' ); } else { if ( is_array( $val ) ) { $val = implode( "\n", $val ); } $val = esc_textarea( $val ); } // Show warning for all types except textarea. if ( 'textarea' !== $type ) { echo '
⚠️ '; if ( null !== $server_val ) { // Show $_SERVER value. printf( esc_html__( 'This value is overwritten by the %s variable.', 'litespeed-cache' ), '$_SERVER' ); $val = '$_SERVER["' . $server_val[0] . '"] = ' . $server_val[1]; } elseif ( null !== $deprecated_filter_val ) { // Show filter value. echo esc_html__( 'This value is overwritten by the filter.', 'litespeed-cache' ); } elseif ( null !== $const_val ) { // Show CONSTANT value. printf( esc_html__( 'This value is overwritten by the PHP constant %s.', 'litespeed-cache' ), '' . esc_html( Base::conf_const( $id ) ) . '' ); } elseif ( is_multisite() ) { // Show multisite overwrite. if ( get_current_blog_id() !== BLOG_ID_CURRENT_SITE && $this->conf( self::NETWORK_O_USE_PRIMARY ) ) { echo esc_html__( 'This value is overwritten by the primary site setting.', 'litespeed-cache' ); } else { echo esc_html__( 'This value is overwritten by the Network setting.', 'litespeed-cache' ); } } elseif ( null !== $filter_val ) { // Show filter value. echo esc_html__( 'This value is overwritten by the filter.', 'litespeed-cache' ); } echo ' ' . sprintf( esc_html__( 'Currently set to %s', 'litespeed-cache' ), '' . esc_html( $val ) . '' ) . '
'; } elseif ( 'textarea' === $type && null !== $deprecated_filter_val ) { // Show warning for textarea. // Textarea sizes. $cols = 30; $rows = $this->get_textarea_rows( $val ); $rows_current_val = $this->get_textarea_rows( implode( "\n", $this->conf( $id, true ) ) ); // If filter rows is bigger than textarea size, equalize them. if ( $rows > $rows_current_val ) { $rows = $rows_current_val; } ?>
:
'; } /** * Display default value for a setting. * * @since 1.1.1 * * @param string $id Setting ID. * @return void */ public function recommended( $id ) { if ( ! $this->default_settings ) { $this->default_settings = $this->load_default_vals(); } $val = $this->default_settings[ $id ]; if ( ! $val ) { return; } if ( ! is_array( $val ) ) { printf( '%s: %s', esc_html__( 'Default value', 'litespeed-cache' ), esc_html( $val ) ); return; } $rows = 5; $cols = 30; // Flexible rows/cols. $lines = count( $val ) + 1; $rows = min( max( $lines, $rows ), 40 ); foreach ( $val as $v ) { $cols = max( strlen( $v ), $cols ); } $cols = min( $cols, 150 ); $val = implode( "\n", $val ); printf( '
%s:
', esc_html__( 'Default value', 'litespeed-cache' ), (int) $rows, (int) $cols, esc_textarea( $val ) ); } /** * Validate rewrite rules regex syntax. * * @since 3.0 * * @param string $id Setting ID. * @return void */ protected function _validate_syntax( $id ) { $val = $this->conf( $id, true ); if ( ! $val ) { return; } if ( ! is_array( $val ) ) { $val = [ $val ]; } foreach ( $val as $v ) { if ( ! Utility::syntax_checker( $v ) ) { echo '
❌ ' . esc_html__( 'Invalid rewrite rule', 'litespeed-cache' ) . ': ' . wp_kses_post( $v ) . ''; } } } /** * Validate if the .htaccess path is valid. * * @since 3.0 * * @param string $id Setting ID. * @return void */ protected function _validate_htaccess_path( $id ) { $val = $this->conf( $id, true ); if ( ! $val ) { return; } if ( '/.htaccess' !== substr( $val, -10 ) ) { echo '
❌ ' . sprintf( esc_html__( 'Path must end with %s', 'litespeed-cache' ), '/.htaccess' ) . ''; } } /** * Check TTL ranges and show tips. * * @since 3.0 * * @param string $id Setting ID. * @param int|bool $min Minimum value (or false). * @param int|bool $max Maximum value (or false). * @param bool $allow_zero Whether zero is allowed. * @return void */ protected function _validate_ttl( $id, $min = false, $max = false, $allow_zero = false ) { $val = $this->conf( $id, true ); $tip = []; if ( $min && $val < $min && ( ! $allow_zero || 0 !== $val ) ) { $tip[] = esc_html__( 'Minimum value', 'litespeed-cache' ) . ': ' . $min . '.'; } if ( $max && $val > $max ) { $tip[] = esc_html__( 'Maximum value', 'litespeed-cache' ) . ': ' . $max . '.'; } echo '
'; if ( $tip ) { echo ' ❌ ' . wp_kses_post( implode( ' ', $tip ) ) . ''; } $range = ''; if ( $allow_zero ) { $range .= esc_html__( 'Zero, or', 'litespeed-cache' ) . ' '; } if ( $min && $max ) { $range .= $min . ' - ' . $max; } elseif ( $min ) { $range .= esc_html__( 'Larger than', 'litespeed-cache' ) . ' ' . $min; } elseif ( $max ) { $range .= esc_html__( 'Smaller than', 'litespeed-cache' ) . ' ' . $max; } echo esc_html__( 'Value range', 'litespeed-cache' ) . ': ' . esc_html( $range ) . ''; } /** * Validate IPs in a list. * * @since 3.0 * * @param string $id Setting ID. * @return void */ protected function _validate_ip( $id ) { $val = $this->conf( $id, true ); if ( ! $val ) { return; } if ( ! is_array( $val ) ) { $val = [ $val ]; } $tip = []; foreach ( $val as $v ) { if ( ! $v ) { continue; } if ( ! \WP_Http::is_ip_address( $v ) ) { $tip[] = esc_html__( 'Invalid IP', 'litespeed-cache' ) . ': ' . esc_html( $v ) . '.'; } } if ( $tip ) { echo '
❌ ' . wp_kses_post( implode( ' ', $tip ) ) . ''; } } /** * Display API environment variable support. * * @since 1.8.3 * @access protected * * @param string ...$args Server variable names. * @return void */ protected function _api_env_var( ...$args ) { echo ' ' . esc_html__( 'API', 'litespeed-cache' ) . ': ' . sprintf( /* translators: %s: list of server variables in tags */ esc_html__( 'Server variable(s) %s available to override this setting.', 'litespeed-cache' ), '' . implode( ', ', array_map( 'esc_html', $args ) ) . '' ) . ''; Doc::learn_more( 'https://docs.litespeedtech.com/lscache/lscwp/admin/#limiting-the-crawler' ); } /** * Display URI setting example. * * @since 2.6.1 * @access protected * @return void */ protected function _uri_usage_example() { echo esc_html__( 'The URLs will be compared to the REQUEST_URI server variable.', 'litespeed-cache' ); /* translators: 1: example URL, 2: pattern example */ echo ' ' . sprintf( esc_html__( 'For example, for %1$s, %2$s can be used here.', 'litespeed-cache' ), '/mypath/mypage?aa=bb', 'mypage?aa=' ); echo '
'; /* translators: %s: caret symbol */ printf( esc_html__( 'To match the beginning, add %s to the beginning of the item.', 'litespeed-cache' ), '^' ); /* translators: %s: dollar symbol */ echo ' ' . sprintf( esc_html__( 'To do an exact match, add %s to the end of the URL.', 'litespeed-cache' ), '$' ); echo ' ' . esc_html__( 'One per line.', 'litespeed-cache' ); echo ''; } /** * Return pluralized strings. * * @since 2.0 * * @param int $num Number. * @param string $kind Kind of item (group|image). * @return string */ public static function print_plural( $num, $kind = 'group' ) { if ( $num > 1 ) { switch ( $kind ) { case 'group': return sprintf( esc_html__( '%s groups', 'litespeed-cache' ), $num ); case 'image': return sprintf( esc_html__( '%s images', 'litespeed-cache' ), $num ); default: return $num; } } switch ( $kind ) { case 'group': return sprintf( esc_html__( '%s group', 'litespeed-cache' ), $num ); case 'image': return sprintf( esc_html__( '%s image', 'litespeed-cache' ), $num ); default: return $num; } } /** * Return guidance HTML. * * @since 2.0 * * @param string $title Title HTML. * @param array $steps Steps list (HTML allowed). * @param int|string $current_step Current step number or 'done'. * @return string HTML for guidance widget. */ public static function guidance( $title, $steps, $current_step ) { if ( 'done' === $current_step ) { $current_step = count( $steps ) + 1; } $percentage = ' (' . floor( ( ( $current_step - 1 ) * 100 ) / count( $steps ) ) . '%)'; $html = '

' . $title . $percentage . '

    '; foreach ( $steps as $k => $v ) { $step = $k + 1; if ( $current_step > $step ) { $html .= '
  1. '; } else { $html .= '
  2. '; } $html .= $v . '
  3. '; } $html .= '
'; return $html; } /** * Check whether has QC hide banner cookie. * * @since 7.1 * * @return bool */ public static function has_qc_hide_banner() { return isset( $_COOKIE[ self::COOKIE_QC_HIDE_BANNER ] ) && ( time() - (int) $_COOKIE[ self::COOKIE_QC_HIDE_BANNER ] ) < 86400 * 90; } /** * Set QC hide banner cookie. * * @since 7.1 * @return void */ public static function set_qc_hide_banner() { $expire = time() + 86400 * 365; self::debug( 'Set qc hide banner cookie' ); setcookie( self::COOKIE_QC_HIDE_BANNER, time(), $expire, COOKIEPATH, COOKIE_DOMAIN ); } /** * Handle all request actions from main cls. * * @since 7.1 * @return void */ public function handler() { $type = Router::verify_type(); switch ( $type ) { case self::TYPE_QC_HIDE_BANNER: self::set_qc_hide_banner(); break; default: break; } Admin::redirect(); } } src/activation.cls.php000064400000042473152075713270011010 0ustar00 */ namespace LiteSpeed; defined( 'WPINC' ) || exit(); /** * Class Activation * * Handles plugin activation, deactivation, and related file management. * * @since 1.1.0 */ class Activation extends Base { const TYPE_UPGRADE = 'upgrade'; const TYPE_INSTALL_3RD = 'install_3rd'; const TYPE_INSTALL_ZIP = 'install_zip'; const TYPE_DISMISS_RECOMMENDED = 'dismiss_recommended'; const NETWORK_TRANSIENT_COUNT = 'lscwp_network_count'; /** * Data file path for configuration. * * @since 4.1 * @var string */ private static $data_file; /** * Construct * * Initializes the data file path. * * @since 4.1 */ public function __construct() { self::$data_file = LSCWP_CONTENT_DIR . '/' . self::CONF_FILE; } /** * The activation hook callback. * * Handles plugin activation tasks, including file creation and multisite setup. * * @since 1.0.0 * @access public */ public static function register_activation() { $count = 0; ! defined( 'LSCWP_LOG_TAG' ) && define( 'LSCWP_LOG_TAG', 'Activate_' . get_current_blog_id() ); /* Network file handler */ if ( is_multisite() ) { $count = self::get_network_count(); if ( false !== $count ) { $count = (int) $count + 1; set_site_transient( self::NETWORK_TRANSIENT_COUNT, $count, DAY_IN_SECONDS ); } if ( ! is_network_admin() ) { if ( 1 === $count ) { // Only itself is activated, set .htaccess with only CacheLookUp try { Htaccess::cls()->insert_ls_wrapper(); } catch ( \Exception $ex ) { Admin_Display::error( $ex->getMessage() ); } } } } self::cls()->update_files(); if ( defined( 'LSCWP_REF' ) && 'whm' === LSCWP_REF ) { GUI::update_option( GUI::WHM_MSG, GUI::WHM_MSG_VAL ); } } /** * Uninstall plugin * * Removes all LiteSpeed Cache settings and data. * * @since 1.1.0 * @since 7.3 Updated to remove all settings. * @access public */ public static function uninstall_litespeed_cache() { Task::destroy(); if ( is_multisite() ) { // Save main site id $current_blog = get_current_blog_id(); // get all sites $sub_sites = get_sites(); // clear foreach site foreach ( $sub_sites as $sub_site ) { $sub_blog_id = (int) $sub_site->blog_id; if ( $sub_blog_id !== $current_blog ) { // Switch to blog switch_to_blog( $sub_blog_id ); // Delete site options self::delete_settings(); // Delete site tables Data::cls()->tables_del(); } } // Return to main site switch_to_blog( $current_blog ); } // Delete current blog/site // Delete options self::delete_settings(); // Delete site tables Data::cls()->tables_del(); if ( file_exists( LITESPEED_STATIC_DIR ) ) { File::rrmdir( LITESPEED_STATIC_DIR ); } Cloud::version_check( 'uninstall' ); } /** * Remove all litespeed settings. * * Deletes all LiteSpeed Cache options from the database. * * @since 7.3 * @access private */ private static function delete_settings() { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->query($wpdb->prepare("DELETE FROM `$wpdb->options` WHERE option_name LIKE %s", 'litespeed.%')); } /** * Get the blog ids for the network. Accepts function arguments. * * @since 1.0.12 * @access public * @param array $args Arguments for get_sites(). * @return array The array of blog ids. */ public static function get_network_ids( $args = [] ) { $args['fields'] = 'ids'; $blogs = get_sites( $args ); return $blogs; } /** * Gets the count of active litespeed cache plugins on multisite. * * @since 1.0.12 * @access private * @return int|false Number of active LSCWP or false if none. */ private static function get_network_count() { $count = get_site_transient( self::NETWORK_TRANSIENT_COUNT ); if ( false !== $count ) { return (int) $count; } // need to update $default = []; $count = 0; $sites = self::get_network_ids( [ 'deleted' => 0 ] ); if ( empty( $sites ) ) { return false; } foreach ( $sites as $site ) { $bid = is_object( $site ) && property_exists( $site, 'blog_id' ) ? $site->blog_id : $site; $plugins = get_blog_option( $bid, 'active_plugins', $default ); if ( ! empty( $plugins ) && in_array( LSCWP_BASENAME, $plugins, true ) ) { ++$count; } } /** * In case this is called outside the admin page * * @see https://codex.wordpress.org/Function_Reference/is_plugin_active_for_network * @since 2.0 */ if ( ! function_exists( 'is_plugin_active_for_network' ) ) { require_once ABSPATH . '/wp-admin/includes/plugin.php'; } if ( is_plugin_active_for_network( LSCWP_BASENAME ) ) { ++$count; } return $count; } /** * Is this deactivate call the last active installation on the multisite network? * * @since 1.0.12 * @access private */ private static function is_deactivate_last() { $count = self::get_network_count(); if ( false === $count ) { return false; } if ( 1 !== $count ) { // Not deactivating the last one. --$count; set_site_transient( self::NETWORK_TRANSIENT_COUNT, $count, DAY_IN_SECONDS ); return false; } delete_site_transient( self::NETWORK_TRANSIENT_COUNT ); return true; } /** * The deactivation hook callback. * * Initializes all clean up functionalities. * * @since 1.0.0 * @access public */ public static function register_deactivation() { Task::destroy(); ! defined( 'LSCWP_LOG_TAG' ) && define( 'LSCWP_LOG_TAG', 'Deactivate_' . get_current_blog_id() ); Purge::purge_all(); if ( is_multisite() ) { if ( ! self::is_deactivate_last() ) { if ( is_network_admin() ) { // Still other activated subsite left, set .htaccess with only CacheLookUp try { Htaccess::cls()->insert_ls_wrapper(); } catch ( \Exception $ex ) { Admin_Display::error($ex->getMessage()); } } return; } } /* 1) wp-config.php; */ try { self::cls()->manage_wp_cache_const( false ); } catch ( \Exception $ex ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.PHP.DevelopmentFunctions.error_log_error_log error_log( 'In wp-config.php: WP_CACHE could not be set to false during deactivation!' ); Admin_Display::error( $ex->getMessage() ); } /* 2) adv-cache.php; Dropped in v3.0.4 */ /* 3) object-cache.php; */ Object_Cache::cls()->del_file(); /* 4) .htaccess; */ try { Htaccess::cls()->clear_rules(); } catch ( \Exception $ex ) { Admin_Display::error( $ex->getMessage() ); } /* 5) .litespeed_conf.dat; */ self::del_conf_data_file(); /* 6) delete option lscwp_whm_install */ // delete in case it's not deleted prior to deactivation. GUI::dismiss_whm(); } /** * Manage related files based on plugin latest conf * * Handle files: * 1) wp-config.php; * 2) adv-cache.php; * 3) object-cache.php; * 4) .htaccess; * 5) .litespeed_conf.dat; * * @since 3.0 * @access public */ public function update_files() { Debug2::debug( '🗂️ [Activation] update_files' ); // Update cache setting `_CACHE` $this->cls( 'Conf' )->define_cache(); // Site options applied already $options = $this->get_options(); /* 1) wp-config.php; */ try { $this->manage_wp_cache_const( $options[ self::_CACHE ] ); } catch ( \Exception $ex ) { // Add msg to admin page or CLI Admin_Display::error( wp_kses_post( $ex->getMessage() ) ); } /* 2) adv-cache.php; Dropped in v3.0.4 */ /* 3) object-cache.php; */ if ( $options[ self::O_OBJECT ] && ( ! $options[ self::O_DEBUG_DISABLE_ALL ] || is_multisite() ) ) { $this->cls( 'Object_Cache' )->update_file( $options ); } else { $this->cls( 'Object_Cache' )->del_file(); // Note: because it doesn't reconnect, which caused setting page OC option changes delayed, thus may meet Connect Test Failed issue (Next refresh will correct it). Not a big deal, will keep as is. } /* 4) .htaccess; */ try { $this->cls( 'Htaccess' )->update( $options ); } catch ( \Exception $ex ) { Admin_Display::error( wp_kses_post( $ex->getMessage() ) ); } /* 5) .litespeed_conf.dat; */ if ( ( $options[ self::O_GUEST ] || $options[ self::O_OBJECT ] ) && ( ! $options[ self::O_DEBUG_DISABLE_ALL ] || is_multisite() ) ) { $this->update_conf_data_file( $options ); } } /** * Delete data conf file * * Removes the .litespeed_conf.dat file. * * @since 4.1 * @access private */ private static function del_conf_data_file() { global $wp_filesystem; if ( ! $wp_filesystem ) { require_once ABSPATH . 'wp-admin/includes/file.php'; WP_Filesystem(); } if ( $wp_filesystem->exists( self::$data_file ) ) { $wp_filesystem->delete( self::$data_file ); } } /** * Update data conf file for guest mode & object cache * * Updates the .litespeed_conf.dat file with relevant settings. * * @since 4.1 * @access private * @param array $options Plugin options. */ private function update_conf_data_file( $options ) { $ids = []; if ( $options[ self::O_OBJECT ] ) { $this_ids = [ self::O_DEBUG, self::O_OBJECT_KIND, self::O_OBJECT_HOST, self::O_OBJECT_PORT, self::O_OBJECT_LIFE, self::O_OBJECT_USER, self::O_OBJECT_PSWD, self::O_OBJECT_DB_ID, self::O_OBJECT_PERSISTENT, self::O_OBJECT_ADMIN, self::O_OBJECT_GLOBAL_GROUPS, self::O_OBJECT_NON_PERSISTENT_GROUPS, ]; $ids = array_merge( $ids, $this_ids ); } if ( $options[ self::O_GUEST ] ) { $this_ids = [ self::HASH, self::O_CACHE_LOGIN_COOKIE, self::O_DEBUG_IPS, self::O_UTIL_NO_HTTPS_VARY, ]; $ids = array_merge( $ids, $this_ids ); } $data = []; foreach ( $ids as $v ) { $data[ $v ] = $options[ $v ]; } $data = wp_json_encode( $data ); $old_data = File::read( self::$data_file ); if ( $old_data !== $data ) { defined( 'LSCWP_LOG' ) && Debug2::debug( '[Activation] Updating .litespeed_conf.dat' ); File::save( self::$data_file, $data ); } } /** * Update the WP_CACHE variable in the wp-config.php file. * * If enabling, check if the variable is defined, and if not, define it. * Vice versa for disabling. * * @since 1.0.0 * @since 3.0 Refactored * @param bool $enable Whether to enable WP_CACHE. * @throws \Exception If wp-config.php cannot be modified. * @return bool True if updated, false if no change needed. */ public function manage_wp_cache_const( $enable ) { if ( $enable ) { if ( defined( 'WP_CACHE' ) && WP_CACHE ) { return false; } } elseif ( ! defined( 'WP_CACHE' ) || ( defined( 'WP_CACHE' ) && ! WP_CACHE ) ) { return false; } if ( apply_filters( 'litespeed_wpconfig_readonly', false ) ) { throw new \Exception( 'wp-config file is forbidden to modify due to API hook: litespeed_wpconfig_readonly' ); } /** * Follow WP's logic to locate wp-config file * * @see wp-load.php */ $conf_file = ABSPATH . 'wp-config.php'; if ( ! file_exists( $conf_file ) ) { $conf_file = dirname( ABSPATH ) . '/wp-config.php'; } $content = File::read( $conf_file ); if ( ! $content ) { throw new \Exception( 'wp-config file content is empty: ' . wp_kses_post( $conf_file ) ); } // Remove the line `define('WP_CACHE', true/false);` first if ( defined( 'WP_CACHE' ) ) { $content = preg_replace( '/define\(\s*([\'"])WP_CACHE\1\s*,\s*\w+\s*\)\s*;/sU', '', $content ); } // Insert const if ( $enable ) { $content = preg_replace( '/^<\?php/', "conf( Base::O_AUTO_UPGRADE ) ) { return; } add_filter( 'auto_update_plugin', [ $this, 'auto_update_hook' ], 10, 2 ); } /** * Auto upgrade hook * * Determines whether to auto-update the plugin. * * @since 3.0 * @access public * @param bool $update Whether to update. * @param object $item Plugin data. * @return bool Whether to update. */ public function auto_update_hook( $update, $item ) { if ( ! empty( $item->slug ) && 'litespeed-cache' === $item->slug ) { $auto_v = Cloud::version_check( 'auto_update_plugin' ); if ( ! empty( $auto_v['latest'] ) && ! empty( $item->new_version ) && $auto_v['latest'] === $item->new_version ) { return true; } } return $update; // Else, use the normal API response to decide whether to update or not } /** * Upgrade LSCWP * * Upgrades the LiteSpeed Cache plugin. * * @since 2.9 * @access public */ public function upgrade() { $plugin = Core::PLUGIN_FILE; /** * Load upgrade cls * * @see wp-admin/update.php */ include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; include_once ABSPATH . 'wp-admin/includes/file.php'; include_once ABSPATH . 'wp-admin/includes/misc.php'; try { ob_start(); $skin = new \WP_Ajax_Upgrader_Skin(); $upgrader = new \Plugin_Upgrader( $skin ); $result = $upgrader->upgrade( $plugin ); if ( ! is_plugin_active( $plugin ) ) { // todo: upgrade should reactivate the plugin again by WP. Need to check why disabled after upgraded. activate_plugin( $plugin, '', is_multisite() ); } ob_end_clean(); } catch ( \Exception $e ) { Admin_Display::error( __( 'Failed to upgrade.', 'litespeed-cache' ) ); return; } if ( is_wp_error( $result ) ) { Admin_Display::error( __( 'Failed to upgrade.', 'litespeed-cache' ) ); return; } Admin_Display::success( __( 'Upgraded successfully.', 'litespeed-cache' ) ); } /** * Detect if the plugin is active or not * * @since 1.0 * @access public * @param string $plugin Plugin slug. * @return bool True if active, false otherwise. */ public function dash_notifier_is_plugin_active( $plugin ) { include_once ABSPATH . 'wp-admin/includes/plugin.php'; $plugin_path = $plugin . '/' . $plugin . '.php'; return is_plugin_active( $plugin_path ); } /** * Detect if the plugin is installed or not * * @since 1.0 * @access public * @param string $plugin Plugin slug. * @return bool True if installed, false otherwise. */ public function dash_notifier_is_plugin_installed( $plugin ) { include_once ABSPATH . 'wp-admin/includes/plugin.php'; $plugin_path = $plugin . '/' . $plugin . '.php'; $valid = validate_plugin( $plugin_path ); return ! is_wp_error( $valid ); } /** * Grab a plugin info from WordPress * * @since 1.0 * @access public * @param string $slug Plugin slug. * @return object|false Plugin info or false on failure. */ public function dash_notifier_get_plugin_info( $slug ) { include_once ABSPATH . 'wp-admin/includes/plugin-install.php'; $result = plugins_api( 'plugin_information', [ 'slug' => $slug ] ); if ( is_wp_error( $result ) ) { return false; } return $result; } /** * Install the 3rd party plugin * * Installs and activates a third-party plugin. * * @since 1.0 * @access public */ public function dash_notifier_install_3rd() { ! defined( 'SILENCE_INSTALL' ) && define( 'SILENCE_INSTALL', true ); // phpcs:ignore $slug = ! empty( $_GET['plugin'] ) ? wp_unslash( sanitize_text_field( $_GET['plugin'] ) ) : false; // Check if plugin is installed already if ( ! $slug || $this->dash_notifier_is_plugin_active( $slug ) ) { return; } /** * Load upgrade cls * * @see wp-admin/update.php */ include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; include_once ABSPATH . 'wp-admin/includes/file.php'; include_once ABSPATH . 'wp-admin/includes/misc.php'; $plugin_path = $slug . '/' . $slug . '.php'; if ( ! $this->dash_notifier_is_plugin_installed( $slug ) ) { $plugin_info = $this->dash_notifier_get_plugin_info( $slug ); if ( ! $plugin_info ) { return; } // Try to install plugin try { ob_start(); $skin = new \Automatic_Upgrader_Skin(); $upgrader = new \Plugin_Upgrader( $skin ); $result = $upgrader->install( $plugin_info->download_link ); ob_end_clean(); } catch ( \Exception $e ) { return; } } if ( ! is_plugin_active( $plugin_path ) ) { activate_plugin( $plugin_path ); } } /** * Handle all request actions from main cls * * Processes various activation-related actions. * * @since 2.9 * @access public */ public function handler() { $type = Router::verify_type(); switch ( $type ) { case self::TYPE_UPGRADE: $this->upgrade(); break; case self::TYPE_INSTALL_3RD: $this->dash_notifier_install_3rd(); break; case self::TYPE_DISMISS_RECOMMENDED: Cloud::reload_summary(); Cloud::save_summary( [ 'news.new' => 0 ] ); break; case self::TYPE_INSTALL_ZIP: Cloud::reload_summary(); $summary = Cloud::get_summary(); if ( ! empty( $summary['news.zip'] ) ) { Cloud::save_summary( [ 'news.new' => 0 ] ); $this->cls( 'Debug2' )->beta_test( $summary['zip'] ); } break; default: break; } Admin::redirect(); } } src/admin-settings.cls.php000064400000026167152075713270011577 0ustar00 $raw_data Raw data from request/CLI. * @return void */ public function save( $raw_data ) { self::debug( 'saving' ); if ( empty( $raw_data[ self::ENROLL ] ) ) { wp_die( esc_html__( 'No fields', 'litespeed-cache' ) ); } $raw_data = Admin::cleanup_text( $raw_data ); // Convert data to config format. $the_matrix = []; foreach ( array_unique( $raw_data[ self::ENROLL ] ) as $id ) { $child = false; // Drop array format. if ( false !== strpos( $id, '[' ) ) { if ( 0 === strpos( $id, self::O_CDN_MAPPING ) || 0 === strpos( $id, self::O_CRAWLER_COOKIES ) ) { // CDN child | Cookie Crawler settings. $child = substr( $id, strpos( $id, '[' ) + 1, strpos( $id, ']' ) - strpos( $id, '[' ) - 1 ); // Drop ending []; Compatible with xx[0] way from CLI. $id = substr( $id, 0, strpos( $id, '[' ) ); } else { // Drop ending []. $id = substr( $id, 0, strpos( $id, '[' ) ); } } if ( ! array_key_exists( $id, self::$_default_options ) ) { continue; } // Validate $child. if ( self::O_CDN_MAPPING === $id ) { if ( ! in_array( $child, [ self::CDN_MAPPING_URL, self::CDN_MAPPING_INC_IMG, self::CDN_MAPPING_INC_CSS, self::CDN_MAPPING_INC_JS, self::CDN_MAPPING_FILETYPE ], true ) ) { continue; } } if ( self::O_CRAWLER_COOKIES === $id ) { if ( ! in_array( $child, [ self::CRWL_COOKIE_NAME, self::CRWL_COOKIE_VALS ], true ) ) { continue; } } // Pull value from request. if ( $child ) { // []=xxx or [0]=xxx $data = ! empty( $raw_data[ $id ][ $child ] ) ? $raw_data[ $id ][ $child ] : $this->type_casting(false, $id); } else { $data = ! empty( $raw_data[ $id ] ) ? $raw_data[ $id ] : $this->type_casting(false, $id); } // Sanitize/normalize complex fields. if ( self::O_CDN_MAPPING === $id || self::O_CRAWLER_COOKIES === $id ) { // Use existing queued data if available (only when $child != false). $data2 = array_key_exists( $id, $the_matrix ) ? $the_matrix[ $id ] : ( defined( 'WP_CLI' ) && WP_CLI ? $this->conf( $id ) : [] ); } switch ( $id ) { // Don't allow Editor/admin to be used in crawler role simulator. case self::O_CRAWLER_ROLES: $data = Utility::sanitize_lines( $data ); if ( $data ) { foreach ( $data as $k => $v ) { if ( user_can( $v, 'edit_posts' ) ) { /* translators: %s: user id in tags */ $msg = sprintf( esc_html__( 'The user with id %s has editor access, which is not allowed for the role simulator.', 'litespeed-cache' ), '' . esc_html( $v ) . '' ); Admin_Display::error( $msg ); unset( $data[ $k ] ); } } } break; case self::O_CDN_MAPPING: /** * CDN setting * * Raw data format: * cdn-mapping[url][] = 'xxx' * cdn-mapping[url][2] = 'xxx2' * cdn-mapping[inc_js][] = 1 * * Final format: * cdn-mapping[0][url] = 'xxx' * cdn-mapping[2][url] = 'xxx2' */ if ( $data ) { foreach ( $data as $k => $v ) { if ( self::CDN_MAPPING_FILETYPE === $child ) { $v = Utility::sanitize_lines( $v ); } if ( self::CDN_MAPPING_URL === $child ) { // If not a valid URL, turn off CDN. if ( 0 !== strpos( $v, 'https://' ) ) { self::debug( '❌ CDN mapping set to OFF due to invalid URL' ); $the_matrix[ self::O_CDN ] = false; } $v = trailingslashit( $v ); } if ( in_array( $child, [ self::CDN_MAPPING_INC_IMG, self::CDN_MAPPING_INC_CSS, self::CDN_MAPPING_INC_JS ], true ) ) { // Because these can't be auto detected in `config->update()`, need to format here. $v = 'false' === $v ? 0 : (bool) $v; } if ( empty( $data2[ $k ] ) ) { $data2[ $k ] = []; } $data2[ $k ][ $child ] = $v; } } $data = $data2; break; case self::O_CRAWLER_COOKIES: /** * Cookie Crawler setting * Raw Format: * crawler-cookies[name][] = xxx * crawler-cookies[name][2] = xxx2 * crawler-cookies[vals][] = xxx * * Final format: * crawler-cookie[0][name] = 'xxx' * crawler-cookie[0][vals] = 'xxx' * crawler-cookie[2][name] = 'xxx2' * * Empty line for `vals` uses literal `_null`. */ if ( $data ) { foreach ( $data as $k => $v ) { if ( self::CRWL_COOKIE_VALS === $child ) { $v = Utility::sanitize_lines( $v ); } if ( empty( $data2[ $k ] ) ) { $data2[ $k ] = []; } $data2[ $k ][ $child ] = $v; } } $data = $data2; break; // Cache exclude category. case self::O_CACHE_EXC_CAT: $data2 = []; $data = Utility::sanitize_lines( $data ); foreach ( $data as $v ) { $cat_id = get_cat_ID( $v ); if ( ! $cat_id ) { continue; } $data2[] = $cat_id; } $data = $data2; break; // Cache exclude tag. case self::O_CACHE_EXC_TAG: $data2 = []; $data = Utility::sanitize_lines( $data ); foreach ( $data as $v ) { $term = get_term_by( 'name', $v, 'post_tag' ); if ( ! $term ) { // Could surface an admin error here if desired. continue; } $data2[] = $term->term_id; } $data = $data2; break; case self::O_IMG_OPTM_SIZES_SKIPPED: // Skip image sizes $image_sizes = Utility::prepare_image_sizes_array(); $saved_sizes = isset( $raw_data[$id] ) ? $raw_data[$id] : []; $data = array_diff( $image_sizes, $saved_sizes ); break; default: break; } $the_matrix[ $id ] = $data; } // Special handler for CDN/Crawler 2d list to drop empty rows. foreach ( $the_matrix as $id => $data ) { /** * Format: * cdn-mapping[0][url] = 'xxx' * cdn-mapping[2][url] = 'xxx2' * crawler-cookie[0][name] = 'xxx' * crawler-cookie[0][vals] = 'xxx' * crawler-cookie[2][name] = 'xxx2' */ if ( self::O_CDN_MAPPING === $id || self::O_CRAWLER_COOKIES === $id ) { // Drop row if all children are empty. foreach ( $data as $k => $v ) { foreach ( $v as $v2 ) { if ( $v2 ) { continue 2; } } // All empty. unset( $the_matrix[ $id ][ $k ] ); } } // Don't allow repeated cookie names. if ( self::O_CRAWLER_COOKIES === $id ) { $existed = []; foreach ( $the_matrix[ $id ] as $k => $v ) { if ( empty( $v[ self::CRWL_COOKIE_NAME ] ) || in_array( $v[ self::CRWL_COOKIE_NAME ], $existed, true ) ) { // Filter repeated or empty name. unset( $the_matrix[ $id ][ $k ] ); continue; } $existed[] = $v[ self::CRWL_COOKIE_NAME ]; } } // tmp fix the 3rd part woo update hook issue when enabling vary cookie. if ( 'wc_cart_vary' === $id ) { if ( $data ) { add_filter( 'litespeed_vary_cookies', function ( $arr ) { $arr[] = 'woocommerce_cart_hash'; return array_unique( $arr ); } ); } else { add_filter( 'litespeed_vary_cookies', function ( $arr ) { $key = array_search( 'woocommerce_cart_hash', $arr, true ); if ( false !== $key ) { unset( $arr[ $key ] ); } return array_unique( $arr ); } ); } } } // id validation will be inside. $this->cls( 'Conf' )->update_confs( $the_matrix ); $msg = __( 'Options saved.', 'litespeed-cache' ); Admin_Display::success( $msg ); } /** * Parses any changes made by the network admin on the network settings. * * @since 3.0 * * @param array $raw_data Raw data from request/CLI. * @return void */ public function network_save( $raw_data ) { self::debug( 'network saving' ); if ( empty( $raw_data[ self::ENROLL ] ) ) { wp_die( esc_html__( 'No fields', 'litespeed-cache' ) ); } $raw_data = Admin::cleanup_text( $raw_data ); foreach ( array_unique( $raw_data[ self::ENROLL ] ) as $id ) { // Append current field to setting save. if ( ! array_key_exists( $id, self::$_default_site_options ) ) { continue; } $data = ! empty( $raw_data[ $id ] ) ? $raw_data[ $id ] : false; // id validation will be inside. $this->cls( 'Conf' )->network_update( $id, $data ); } // Update related files. Activation::cls()->update_files(); $msg = __( 'Options saved.', 'litespeed-cache' ); Admin_Display::success( $msg ); } /** * Hooked to the wp_redirect filter when saving widgets fails validation. * * @since 1.1.3 * * @param string $location The redirect location. * @return string Updated location string. */ public static function widget_save_err( $location ) { return str_replace( '?message=0', '?error=0', $location ); } /** * Validate the LiteSpeed Cache settings on widget save. * * @since 1.1.3 * * @param array $instance The new settings. * @param array $new_instance The raw submitted settings. * @param array $old_instance The original settings. * @param \WP_Widget $widget The widget instance. * @return array|false Updated settings on success, false on error. */ public static function validate_widget_save( $instance, $new_instance, $old_instance, $widget ) { if ( empty( $new_instance ) ) { return $instance; } if ( ! isset( $new_instance[ ESI::WIDGET_O_ESIENABLE ], $new_instance[ ESI::WIDGET_O_TTL ] ) ) { return $instance; } $esi = (int) $new_instance[ ESI::WIDGET_O_ESIENABLE ] % 3; $ttl = (int) $new_instance[ ESI::WIDGET_O_TTL ]; if ( 0 !== $ttl && $ttl < 30 ) { add_filter( 'wp_redirect', __CLASS__ . '::widget_save_err' ); return false; // Invalid ttl. } if ( empty( $instance[ Conf::OPTION_NAME ] ) ) { // @todo to be removed. $instance[ Conf::OPTION_NAME ] = []; } $instance[ Conf::OPTION_NAME ][ ESI::WIDGET_O_ESIENABLE ] = $esi; $instance[ Conf::OPTION_NAME ][ ESI::WIDGET_O_TTL ] = $ttl; $current = ! empty( $old_instance[ Conf::OPTION_NAME ] ) ? $old_instance[ Conf::OPTION_NAME ] : false; // Avoid unsanitized superglobal usage. $referrer = isset( $_SERVER['HTTP_REFERER'] ) ? esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : ''; // Only purge when not in the Customizer. if ( false === strpos( $referrer, '/wp-admin/customize.php' ) ) { if ( ! $current || $esi !== (int) $current[ ESI::WIDGET_O_ESIENABLE ] ) { Purge::purge_all( 'Widget ESI_enable changed' ); } elseif ( 0 !== $ttl && $ttl !== (int) $current[ ESI::WIDGET_O_TTL ] ) { Purge::add( Tag::TYPE_WIDGET . $widget->id ); } Purge::purge_all( 'Widget saved' ); } return $instance; } } src/img-optm-manage.trait.php000064400000075542152075713300012165 0ustar00__data->tb_exist( 'img_optm' ) ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(1) FROM `$this->_table_img_optm` WHERE post_id = %d", $post_id ) ); if ( $count > 0 ) { return true; } } // Check img_optming table if ( $this->__data->tb_exist( 'img_optming' ) ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(1) FROM `$this->_table_img_optming` WHERE post_id = %d", $post_id ) ); if ( $count > 0 ) { return true; } } // Check if optimized files exist (.webp, .avif) if ( null === $metadata ) { $metadata = wp_get_attachment_metadata( $post_id ); } if ( ! empty( $metadata['file'] ) ) { $short_file_path = $metadata['file']; if ( $this->__media->info( $short_file_path . '.webp', $post_id ) ) { return true; } if ( $this->__media->info( $short_file_path . '.avif', $post_id ) ) { return true; } } return false; } /** * Clean up all unfinished queue locally and to Cloud server * * @since 2.1.2 * @access public */ public function clean() { global $wpdb; // Reset img_optm table's queue if ( $this->__data->tb_exist( 'img_optming' ) ) { // Get min post id to mark $q = "SELECT MIN(post_id) FROM `$this->_table_img_optming`"; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $min_pid = $wpdb->get_var( $q ) - 1; if ( $this->_summary['next_post_id'] > $min_pid ) { $this->_summary['next_post_id'] = $min_pid; self::save_summary(); } $q = "DELETE FROM `$this->_table_img_optming`"; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $wpdb->query( $q ); } $msg = __( 'Cleaned up unfinished data successfully.', 'litespeed-cache' ); Admin_Display::success( $msg ); } /** * Reset image counter * * @since 7.0 * @access private */ private function _reset_counter() { self::debug( 'reset image optm counter' ); $this->_summary['next_post_id'] = 0; self::save_summary(); $this->clean(); $msg = __( 'Reset image optimization counter successfully.', 'litespeed-cache' ); Admin_Display::success( $msg ); } /** * Destroy all optimized images * * @since 3.0 * @access private */ private function _destroy() { global $wpdb; self::debug( 'executing DESTROY process' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended $offset = ! empty( $_GET['litespeed_i'] ) ? absint( wp_unslash( $_GET['litespeed_i'] ) ) : 0; /** * Limit images each time before redirection to fix Out of memory issue. #665465 * * @since 2.9.8 */ // Start deleting files $limit = apply_filters( 'litespeed_imgoptm_destroy_max_rows', 500 ); $img_q = "SELECT b.post_id, b.meta_value FROM `$wpdb->posts` a LEFT JOIN `$wpdb->postmeta` b ON b.post_id = a.ID WHERE b.meta_key = '_wp_attachment_metadata' AND a.post_type = 'attachment' AND a.post_status = 'inherit' AND a.post_mime_type IN ('image/jpeg', 'image/png', 'image/gif') ORDER BY a.ID LIMIT %d,%d "; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $q = $wpdb->prepare( $img_q, [ $offset * $limit, $limit ] ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $list = $wpdb->get_results( $q ); $i = 0; foreach ( $list as $v ) { if ( ! $v->post_id ) { continue; } $meta_value = $this->_parse_wp_meta_value( $v ); if ( ! $meta_value ) { continue; } ++$i; $this->tmp_pid = $v->post_id; $this->tmp_path = pathinfo( $meta_value['file'], PATHINFO_DIRNAME ) . '/'; $this->_destroy_optm_file( $meta_value, true ); if ( ! empty( $meta_value['sizes'] ) ) { array_map( [ $this, '_destroy_optm_file' ], $meta_value['sizes'] ); } } self::debug( 'batch switched images total: ' . $i ); ++$offset; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $to_be_continued = $wpdb->get_row( $wpdb->prepare( $img_q, [ $offset * $limit, 1 ] ) ); if ( $to_be_continued ) { // Check if post_id is beyond next_post_id self::debug( '[next_post_id] ' . $this->_summary['next_post_id'] . ' [cursor post id] ' . $to_be_continued->post_id ); if ( $to_be_continued->post_id <= $this->_summary['next_post_id'] ) { self::debug( 'redirecting to next' ); return Router::self_redirect( Router::ACTION_IMG_OPTM, self::TYPE_DESTROY ); } self::debug( '🎊 Finished destroying' ); } // Delete postmeta info $q = "DELETE FROM `$wpdb->postmeta` WHERE meta_key = %s"; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $wpdb->query( $wpdb->prepare( $q, self::DB_SIZE ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $wpdb->query( $wpdb->prepare( $q, self::DB_SET ) ); // Delete img_optm table $this->__data->tb_del( 'img_optm' ); $this->__data->tb_del( 'img_optming' ); // Clear options table summary info self::delete_option( '_summary' ); self::delete_option( self::DB_NEED_PULL ); $msg = __( 'Destroy all optimization data successfully.', 'litespeed-cache' ); Admin_Display::success( $msg ); } /** * Destroy optm file * * @since 3.0 * @access private * @param array $meta_value The meta value array containing file info. * @param bool $is_ori_file Whether this is the original file. */ private function _destroy_optm_file( $meta_value, $is_ori_file = false ) { $short_file_path = $meta_value['file']; if ( ! $is_ori_file ) { $short_file_path = $this->tmp_path . $short_file_path; } self::debug( 'deleting ' . $short_file_path ); // del webp $this->__media->info( $short_file_path . '.webp', $this->tmp_pid ) && $this->__media->del( $short_file_path . '.webp', $this->tmp_pid ); $this->__media->info( $short_file_path . '.optm.webp', $this->tmp_pid ) && $this->__media->del( $short_file_path . '.optm.webp', $this->tmp_pid ); // del avif $this->__media->info( $short_file_path . '.avif', $this->tmp_pid ) && $this->__media->del( $short_file_path . '.avif', $this->tmp_pid ); $this->__media->info( $short_file_path . '.optm.avif', $this->tmp_pid ) && $this->__media->del( $short_file_path . '.optm.avif', $this->tmp_pid ); $extension = pathinfo( $short_file_path, PATHINFO_EXTENSION ); $local_filename = substr( $short_file_path, 0, -strlen( $extension ) - 1 ); $bk_file = $local_filename . '.bk.' . $extension; $bk_optm_file = $local_filename . '.bk.optm.' . $extension; // del optimized ori if ( $this->__media->info( $bk_file, $this->tmp_pid ) ) { self::debug( 'deleting optim ori' ); $this->__media->del( $short_file_path, $this->tmp_pid ); $this->__media->rename( $bk_file, $short_file_path, $this->tmp_pid ); } $this->__media->info( $bk_optm_file, $this->tmp_pid ) && $this->__media->del( $bk_optm_file, $this->tmp_pid ); } /** * Rescan to find new generated images * * @since 1.6.7 * @access private */ private function _rescan() { // phpcs:ignore Squiz.PHP.NonExecutableCode exit( 'tobedone' ); // phpcs:disable Squiz.PHP.NonExecutableCode global $wpdb; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $offset = ! empty( $_GET['litespeed_i'] ) ? absint( wp_unslash( $_GET['litespeed_i'] ) ) : 0; $limit = 500; self::debug( 'rescan images' ); // Get images $q = "SELECT b.post_id, b.meta_value FROM `$wpdb->posts` a, `$wpdb->postmeta` b WHERE a.post_type = 'attachment' AND a.post_status = 'inherit' AND a.post_mime_type IN ('image/jpeg', 'image/png', 'image/gif') AND a.ID = b.post_id AND b.meta_key = '_wp_attachment_metadata' ORDER BY a.ID LIMIT %d, %d "; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $list = $wpdb->get_results( $wpdb->prepare( $q, $offset * $limit, $limit + 1 ) ); // last one is the seed for next batch if ( ! $list ) { $msg = __( 'Rescanned successfully.', 'litespeed-cache' ); Admin_Display::success( $msg ); self::debug( 'rescan bypass: no gathered image found' ); return; } if ( count( $list ) === $limit + 1 ) { $to_be_continued = true; array_pop( $list ); // last one is the seed for next round, discard here. } else { $to_be_continued = false; } // Prepare post_ids to inquery gathered images $pid_set = []; $scanned_list = []; foreach ( $list as $v ) { $meta_value = $this->_parse_wp_meta_value( $v ); if ( ! $meta_value ) { continue; } $scanned_list[] = [ 'pid' => $v->post_id, 'meta' => $meta_value, ]; $pid_set[] = $v->post_id; } // Build gathered images $q = "SELECT src, post_id FROM `$this->_table_img_optm` WHERE post_id IN (" . implode( ',', array_fill( 0, count( $pid_set ), '%d' ) ) . ')'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $list = $wpdb->get_results( $wpdb->prepare( $q, $pid_set ) ); foreach ( $list as $v ) { $this->_existed_src_list[] = $v->post_id . '.' . $v->src; } // Find new images foreach ( $scanned_list as $v ) { $meta_value = $v['meta']; // Parse all child src and put them into $this->_img_in_queue, missing ones to $this->_img_in_queue_missed $this->tmp_pid = $v['pid']; $this->tmp_path = pathinfo( $meta_value['file'], PATHINFO_DIRNAME ) . '/'; $this->_append_img_queue( $meta_value, true ); if ( ! empty( $meta_value['sizes'] ) ) { foreach ( $meta_value['sizes'] as $img_size_name => $img_size ) { $this->_append_img_queue( $img_size, false, $img_size_name ); } } } self::debug( 'rescanned [img] ' . count( $this->_img_in_queue ) ); $count = count( $this->_img_in_queue ); if ( $count > 0 ) { // Save to DB $this->_save_raw(); } if ( $to_be_continued ) { return Router::self_redirect( Router::ACTION_IMG_OPTM, self::TYPE_RESCAN ); } $msg = $count ? sprintf( __( 'Rescanned %d images successfully.', 'litespeed-cache' ), $count ) : __( 'Rescanned successfully.', 'litespeed-cache' ); Admin_Display::success( $msg ); // phpcs:enable Squiz.PHP.NonExecutableCode } /** * Calculate bkup original images storage * * @since 2.2.6 * @access private */ private function _calc_bkup() { global $wpdb; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $offset = ! empty( $_GET['litespeed_i'] ) ? absint( wp_unslash( $_GET['litespeed_i'] ) ) : 0; $limit = 500; if ( ! $offset ) { $this->_summary['bk_summary'] = [ 'date' => time(), 'count' => 0, 'sum' => 0, ]; } $img_q = "SELECT b.post_id, b.meta_value FROM `$wpdb->posts` a LEFT JOIN `$wpdb->postmeta` b ON b.post_id = a.ID WHERE b.meta_key = '_wp_attachment_metadata' AND a.post_type = 'attachment' AND a.post_status = 'inherit' AND a.post_mime_type IN ('image/jpeg', 'image/png', 'image/gif') ORDER BY a.ID LIMIT %d,%d "; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $q = $wpdb->prepare( $img_q, [ $offset * $limit, $limit ] ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $list = $wpdb->get_results( $q ); foreach ( $list as $v ) { if ( ! $v->post_id ) { continue; } $meta_value = $this->_parse_wp_meta_value( $v ); if ( ! $meta_value ) { continue; } $this->tmp_pid = $v->post_id; $this->tmp_path = pathinfo( $meta_value['file'], PATHINFO_DIRNAME ) . '/'; $this->_get_bk_size( $meta_value, true ); if ( ! empty( $meta_value['sizes'] ) ) { array_map( [ $this, '_get_bk_size' ], $meta_value['sizes'] ); } } $this->_summary['bk_summary']['date'] = time(); self::save_summary(); self::debug( '_calc_bkup total: ' . $this->_summary['bk_summary']['count'] . ' [size] ' . $this->_summary['bk_summary']['sum'] ); ++$offset; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $to_be_continued = $wpdb->get_row( $wpdb->prepare( $img_q, [ $offset * $limit, 1 ] ) ); if ( $to_be_continued ) { return Router::self_redirect( Router::ACTION_IMG_OPTM, self::TYPE_CALC_BKUP ); } $msg = __( 'Calculated backups successfully.', 'litespeed-cache' ); Admin_Display::success( $msg ); } /** * Calculate single size * * @since 2.2.6 * @access private * @param array $meta_value The meta value array containing file info. * @param bool $is_ori_file Whether this is the original file. */ private function _get_bk_size( $meta_value, $is_ori_file = false ) { $short_file_path = $meta_value['file']; if ( ! $is_ori_file ) { $short_file_path = $this->tmp_path . $short_file_path; } $extension = pathinfo( $short_file_path, PATHINFO_EXTENSION ); $local_filename = substr( $short_file_path, 0, -strlen( $extension ) - 1 ); $bk_file = $local_filename . '.bk.' . $extension; $img_info = $this->__media->info( $bk_file, $this->tmp_pid ); if ( ! $img_info ) { return; } ++$this->_summary['bk_summary']['count']; $this->_summary['bk_summary']['sum'] += $img_info['size']; } /** * Delete bkup original images storage * * @since 2.5 * @access public */ public function rm_bkup() { global $wpdb; if ( ! $this->__data->tb_exist( 'img_optming' ) ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended $offset = ! empty( $_GET['litespeed_i'] ) ? absint( wp_unslash( $_GET['litespeed_i'] ) ) : 0; $limit = 500; if ( empty( $this->_summary['rmbk_summary'] ) ) { $this->_summary['rmbk_summary'] = [ 'date' => time(), 'count' => 0, 'sum' => 0, ]; } $img_q = "SELECT b.post_id, b.meta_value FROM `$wpdb->posts` a LEFT JOIN `$wpdb->postmeta` b ON b.post_id = a.ID WHERE b.meta_key = '_wp_attachment_metadata' AND a.post_type = 'attachment' AND a.post_status = 'inherit' AND a.post_mime_type IN ('image/jpeg', 'image/png', 'image/gif') ORDER BY a.ID LIMIT %d,%d "; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $q = $wpdb->prepare( $img_q, [ $offset * $limit, $limit ] ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $list = $wpdb->get_results( $q ); foreach ( $list as $v ) { if ( ! $v->post_id ) { continue; } $meta_value = $this->_parse_wp_meta_value( $v ); if ( ! $meta_value ) { continue; } $this->tmp_pid = $v->post_id; $this->tmp_path = pathinfo( $meta_value['file'], PATHINFO_DIRNAME ) . '/'; $this->_del_bk_file( $meta_value, true ); if ( ! empty( $meta_value['sizes'] ) ) { array_map( [ $this, '_del_bk_file' ], $meta_value['sizes'] ); } } $this->_summary['rmbk_summary']['date'] = time(); self::save_summary(); self::debug( 'rm_bkup total: ' . $this->_summary['rmbk_summary']['count'] . ' [size] ' . $this->_summary['rmbk_summary']['sum'] ); ++$offset; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $to_be_continued = $wpdb->get_row( $wpdb->prepare( $img_q, [ $offset * $limit, 1 ] ) ); if ( $to_be_continued ) { return Router::self_redirect( Router::ACTION_IMG_OPTM, self::TYPE_RM_BKUP ); } $msg = __( 'Removed backups successfully.', 'litespeed-cache' ); Admin_Display::success( $msg ); } /** * Delete single file * * @since 2.5 * @access private * @param array $meta_value The meta value array containing file info. * @param bool $is_ori_file Whether this is the original file. */ private function _del_bk_file( $meta_value, $is_ori_file = false ) { $short_file_path = $meta_value['file']; if ( ! $is_ori_file ) { $short_file_path = $this->tmp_path . $short_file_path; } $extension = pathinfo( $short_file_path, PATHINFO_EXTENSION ); $local_filename = substr( $short_file_path, 0, -strlen( $extension ) - 1 ); $bk_file = $local_filename . '.bk.' . $extension; $img_info = $this->__media->info( $bk_file, $this->tmp_pid ); if ( ! $img_info ) { return; } ++$this->_summary['rmbk_summary']['count']; $this->_summary['rmbk_summary']['sum'] += $img_info['size']; $this->__media->del( $bk_file, $this->tmp_pid ); } /** * Count images * * @since 1.6 * @access public * @return array Image count data. */ public function img_count() { global $wpdb; $q = "SELECT count(*) FROM `$wpdb->posts` a LEFT JOIN `$wpdb->postmeta` b ON b.post_id = a.ID WHERE b.meta_key = '_wp_attachment_metadata' AND a.post_type = 'attachment' AND a.post_status = 'inherit' AND a.post_mime_type IN ('image/jpeg', 'image/png', 'image/gif') "; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $groups_all = $wpdb->get_var( $q ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $groups_new = $wpdb->get_var( $q . ' AND ID>' . (int) $this->_summary['next_post_id'] . ' ORDER BY ID' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $groups_done = $wpdb->get_var( $q . ' AND ID<=' . (int) $this->_summary['next_post_id'] . ' ORDER BY ID' ); $q = "SELECT b.post_id FROM `$wpdb->posts` a LEFT JOIN `$wpdb->postmeta` b ON b.post_id = a.ID WHERE b.meta_key = '_wp_attachment_metadata' AND a.post_type = 'attachment' AND a.post_status = 'inherit' AND a.post_mime_type IN ('image/jpeg', 'image/png', 'image/gif') ORDER BY a.ID DESC LIMIT 1 "; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $max_id = $wpdb->get_var( $q ); $count_list = [ 'max_id' => $max_id, 'groups_all' => $groups_all, 'groups_new' => $groups_new, 'groups_done' => $groups_done, ]; // images count from work table if ( $this->__data->tb_exist( 'img_optming' ) ) { $q = "SELECT COUNT(DISTINCT post_id),COUNT(*) FROM `$this->_table_img_optming` WHERE optm_status = %d"; $groups_to_check = [ self::STATUS_RAW, self::STATUS_REQUESTED, self::STATUS_NOTIFIED, self::STATUS_ERR_FETCH ]; foreach ( $groups_to_check as $v ) { $count_list[ 'img.' . $v ] = 0; $count_list[ 'group.' . $v ] = 0; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared list( $count_list[ 'group.' . $v ], $count_list[ 'img.' . $v ] ) = $wpdb->get_row( $wpdb->prepare( $q, $v ), ARRAY_N ); } } return $count_list; } /** * Check if fetch cron is running * * @since 1.6.2 * @access public * @param bool $bool_res Whether to return boolean result. * @return bool|array Boolean result or array with last run time and status. */ public function cron_running( $bool_res = true ) { $last_run = ! empty( $this->_summary['last_pull'] ) ? $this->_summary['last_pull'] : 0; $is_running = $last_run && time() - $last_run < 120; if ( $bool_res ) { return $is_running; } return [ $last_run, $is_running ]; } /** * Update fetch cron timestamp tag * * @since 1.6.2 * @access private * @param bool $done Whether the cron job is done. */ private function _update_cron_running( $done = false ) { $this->_summary['last_pull'] = time(); if ( $done ) { // Only update cron tag when its from the active running cron if ( $this->_cron_ran ) { // Rollback for next running $this->_summary['last_pull'] -= 120; } else { return; } } self::save_summary(); $this->_cron_ran = true; } /** * Batch switch images to ori/optm version * * @since 1.6.2 * @access public * @param string $type The switch type (batch_switch_ori or batch_switch_optm). */ public function batch_switch( $type ) { if ( defined( 'LITESPEED_CLI' ) || wp_doing_cron() ) { $offset = 0; while ( 'done' !== $offset ) { Admin_Display::info( "Starting switch to $type [offset] $offset" ); $offset = $this->_batch_switch( $type, $offset ); } } else { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $offset = ! empty( $_GET['litespeed_i'] ) ? absint( wp_unslash( $_GET['litespeed_i'] ) ) : 0; $new_offset = $this->_batch_switch( $type, $offset ); if ( 'done' !== $new_offset ) { return Router::self_redirect( Router::ACTION_IMG_OPTM, $type ); } } $msg = __( 'Switched images successfully.', 'litespeed-cache' ); Admin_Display::success( $msg ); } /** * Switch images per offset * * @since 1.6.2 * @access private * @param string $type The switch type. * @param int $offset The current offset. * @return int|string Next offset or 'done'. */ private function _batch_switch( $type, $offset ) { global $wpdb; $limit = 500; $this->tmp_type = $type; $img_q = "SELECT b.post_id, b.meta_value FROM `$wpdb->posts` a LEFT JOIN `$wpdb->postmeta` b ON b.post_id = a.ID WHERE b.meta_key = '_wp_attachment_metadata' AND a.post_type = 'attachment' AND a.post_status = 'inherit' AND a.post_mime_type IN ('image/jpeg', 'image/png', 'image/gif') ORDER BY a.ID LIMIT %d,%d "; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $q = $wpdb->prepare( $img_q, [ $offset * $limit, $limit ] ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $list = $wpdb->get_results( $q ); $i = 0; foreach ( $list as $v ) { if ( ! $v->post_id ) { continue; } $meta_value = $this->_parse_wp_meta_value( $v ); if ( ! $meta_value ) { continue; } ++$i; $this->tmp_pid = $v->post_id; $this->tmp_path = pathinfo( $meta_value['file'], PATHINFO_DIRNAME ) . '/'; $this->_switch_bk_file( $meta_value, true ); if ( ! empty( $meta_value['sizes'] ) ) { array_map( [ $this, '_switch_bk_file' ], $meta_value['sizes'] ); } } self::debug( 'batch switched images total: ' . $i . ' [type] ' . $type ); ++$offset; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $to_be_continued = $wpdb->get_row( $wpdb->prepare( $img_q, [ $offset * $limit, 1 ] ) ); if ( $to_be_continued ) { return $offset; } return 'done'; } /** * Switch backup file between original and optimized * * @since 1.6.2 * @access private * @param array $meta_value The meta value array containing file info. * @param bool $is_ori_file Whether this is the original file. */ private function _switch_bk_file( $meta_value, $is_ori_file = false ) { $short_file_path = $meta_value['file']; if ( ! $is_ori_file ) { $short_file_path = $this->tmp_path . $short_file_path; } $extension = pathinfo( $short_file_path, PATHINFO_EXTENSION ); $local_filename = substr( $short_file_path, 0, -strlen( $extension ) - 1 ); $bk_file = $local_filename . '.bk.' . $extension; $bk_optm_file = $local_filename . '.bk.optm.' . $extension; // self::debug('_switch_bk_file ' . $bk_file . ' [type] ' . $this->tmp_type); // switch to ori if ( self::TYPE_BATCH_SWITCH_ORI === $this->tmp_type || 'orig' === $this->tmp_type ) { // self::debug('switch to orig ' . $bk_file); if ( ! $this->__media->info( $bk_file, $this->tmp_pid ) ) { return; } $this->__media->rename( $local_filename . '.' . $extension, $bk_optm_file, $this->tmp_pid ); $this->__media->rename( $bk_file, $local_filename . '.' . $extension, $this->tmp_pid ); } elseif ( self::TYPE_BATCH_SWITCH_OPTM === $this->tmp_type || 'optm' === $this->tmp_type ) { // switch to optm // self::debug('switch to optm ' . $bk_file); if ( ! $this->__media->info( $bk_optm_file, $this->tmp_pid ) ) { return; } $this->__media->rename( $local_filename . '.' . $extension, $bk_file, $this->tmp_pid ); $this->__media->rename( $bk_optm_file, $local_filename . '.' . $extension, $this->tmp_pid ); } } /** * Switch image between original one and optimized one * * @since 1.6.2 * @access private * @param string $type The switch type (webpXXX, avifXXX, or origXXX where XXX is the post ID). */ private function _switch_optm_file( $type ) { Admin_Display::success( __( 'Switched to optimized file successfully.', 'litespeed-cache' ) ); return; // phpcs:disable Squiz.PHP.NonExecutableCode global $wpdb; $pid = substr( $type, 4 ); $switch_type = substr( $type, 0, 4 ); $q = "SELECT src,post_id FROM `$this->_table_img_optm` WHERE post_id = %d AND optm_status = %d"; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $list = $wpdb->get_results( $wpdb->prepare( $q, [ $pid, self::STATUS_PULLED ] ) ); $msg = 'Unknown Msg'; foreach ( $list as $v ) { // to switch webp file if ( 'webp' === $switch_type ) { if ( $this->__media->info( $v->src . '.webp', $v->post_id ) ) { $this->__media->rename( $v->src . '.webp', $v->src . '.optm.webp', $v->post_id ); self::debug( 'Disabled WebP: ' . $v->src ); $msg = __( 'Disabled WebP file successfully.', 'litespeed-cache' ); } elseif ( $this->__media->info( $v->src . '.optm.webp', $v->post_id ) ) { $this->__media->rename( $v->src . '.optm.webp', $v->src . '.webp', $v->post_id ); self::debug( 'Enable WebP: ' . $v->src ); $msg = __( 'Enabled WebP file successfully.', 'litespeed-cache' ); } } elseif ( 'avif' === $switch_type ) { // to switch avif file if ( $this->__media->info( $v->src . '.avif', $v->post_id ) ) { $this->__media->rename( $v->src . '.avif', $v->src . '.optm.avif', $v->post_id ); self::debug( 'Disabled AVIF: ' . $v->src ); $msg = __( 'Disabled AVIF file successfully.', 'litespeed-cache' ); } elseif ( $this->__media->info( $v->src . '.optm.avif', $v->post_id ) ) { $this->__media->rename( $v->src . '.optm.avif', $v->src . '.avif', $v->post_id ); self::debug( 'Enable AVIF: ' . $v->src ); $msg = __( 'Enabled AVIF file successfully.', 'litespeed-cache' ); } } else { // to switch original file $extension = pathinfo( $v->src, PATHINFO_EXTENSION ); $local_filename = substr( $v->src, 0, -strlen( $extension ) - 1 ); $bk_file = $local_filename . '.bk.' . $extension; $bk_optm_file = $local_filename . '.bk.optm.' . $extension; // revert ori back if ( $this->__media->info( $bk_file, $v->post_id ) ) { $this->__media->rename( $v->src, $bk_optm_file, $v->post_id ); $this->__media->rename( $bk_file, $v->src, $v->post_id ); self::debug( 'Restore original img: ' . $bk_file ); $msg = __( 'Restored original file successfully.', 'litespeed-cache' ); } elseif ( $this->__media->info( $bk_optm_file, $v->post_id ) ) { $this->__media->rename( $v->src, $bk_file, $v->post_id ); $this->__media->rename( $bk_optm_file, $v->src, $v->post_id ); self::debug( 'Switch to optm img: ' . $v->src ); $msg = __( 'Switched to optimized file successfully.', 'litespeed-cache' ); } } } Admin_Display::success( $msg ); // phpcs:enable Squiz.PHP.NonExecutableCode } /** * Delete one optm data and recover original file * * @since 2.4.2 * @access public * @param int $post_id The post ID to reset. * @param bool $silent Whether to suppress success message. Default false. */ public function reset_row( $post_id, $silent = false ) { global $wpdb; if ( ! $post_id ) { return; } self::debug( '_reset_row [pid] ' . $post_id ); // TODO: Load image sub files $img_q = "SELECT b.post_id, b.meta_value FROM `$wpdb->postmeta` b WHERE b.post_id =%d AND b.meta_key = '_wp_attachment_metadata'"; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $q = $wpdb->prepare( $img_q, [ $post_id ] ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $v = $wpdb->get_row( $q ); $meta_value = $this->_parse_wp_meta_value( $v ); if ( $meta_value ) { $this->tmp_pid = $v->post_id; $this->tmp_path = pathinfo( $meta_value['file'], PATHINFO_DIRNAME ) . '/'; $this->_destroy_optm_file( $meta_value, true ); if ( ! empty( $meta_value['sizes'] ) ) { array_map( [ $this, '_destroy_optm_file' ], $meta_value['sizes'] ); } } delete_post_meta( $post_id, self::DB_SIZE ); delete_post_meta( $post_id, self::DB_SET ); // Delete records from img_optm and img_optming tables if ( $this->__data->tb_exist( 'img_optm' ) ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $wpdb->query( $wpdb->prepare( "DELETE FROM `$this->_table_img_optm` WHERE post_id = %d", $post_id ) ); } if ( $this->__data->tb_exist( 'img_optming' ) ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $wpdb->query( $wpdb->prepare( "DELETE FROM `$this->_table_img_optming` WHERE post_id = %d", $post_id ) ); } if ( ! $silent ) { $msg = __( 'Reset the optimized data successfully.', 'litespeed-cache' ); Admin_Display::success( $msg ); } } /** * Show an image's optm status * * @since 1.6.5 * @access public * @return array Response data with image info. */ public function check_img() { global $wpdb; // phpcs:ignore WordPress.Security.NonceVerification.Missing $pid = isset( $_POST['data'] ) ? absint( wp_unslash( $_POST['data'] ) ) : 0; self::debug( 'Check image [ID] ' . $pid ); $data = []; $data['img_count'] = $this->img_count(); $data['optm_summary'] = self::get_summary(); $data['_wp_attached_file'] = get_post_meta( $pid, '_wp_attached_file', true ); $data['_wp_attachment_metadata'] = get_post_meta( $pid, '_wp_attachment_metadata', true ); // Get img_optm data $q = "SELECT * FROM `$this->_table_img_optm` WHERE post_id = %d"; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $list = $wpdb->get_results( $wpdb->prepare( $q, $pid ) ); $img_data = []; if ( $list ) { foreach ( $list as $v ) { $img_data[] = [ 'id' => $v->id, 'optm_status' => $v->optm_status, 'src' => $v->src, 'srcpath_md5' => $v->srcpath_md5, 'src_md5' => $v->src_md5, 'server_info' => $v->server_info, ]; } } $data['img_data'] = $img_data; return [ '_res' => 'ok', 'data' => $data, ]; } } src/cloud-request.trait.php000064400000047275152075713310012005 0ustar00_get( $service, $data ); } /** * Get data from QUIC cloud server (private) * * @since 3.0 * @access private * * @param string $service Service. * @param array|bool $data Data array or false to omit. * @return mixed */ private function _get( $service, $data = false ) { $service_tag = $service; if ( ! empty( $data['action'] ) ) { $service_tag .= '-' . $data['action']; } $maybe_cloud = $this->_maybe_cloud( $service_tag ); if ( ! $maybe_cloud || 'svc_hot' === $maybe_cloud ) { return $maybe_cloud; } $server = $this->detect_cloud( $service ); if ( ! $server ) { return; } $url = $server . '/' . $service; $param = [ 'site_url' => site_url(), 'main_domain'=> ! empty( $this->_summary['main_domain'] ) ? $this->_summary['main_domain'] : '', 'ver' => Core::VER, ]; if ( $data ) { $param['data'] = $data; } $url .= '?' . http_build_query( $param ); self::debug( 'getting from : ' . $url ); self::save_summary( [ 'curr_request.' . $service_tag => time() ] ); File::save( $this->_qc_time_file( $service_tag, 'curr' ), time(), true ); $response = wp_safe_remote_get( $url, [ 'timeout' => 15, 'headers' => [ 'Accept' => 'application/json' ], ] ); return $this->_parse_response( $response, $service, $service_tag, $server ); } /** * Check if is able to do cloud request or not * * @since 3.0 * @access private * * @param string $service_tag Service tag. * @return bool|string */ private function _maybe_cloud( $service_tag ) { $site_url = site_url(); if ( ! wp_http_validate_url( $site_url ) ) { self::debug( 'wp_http_validate_url failed: ' . $site_url ); return false; } // Deny if is IP if ( preg_match( '#^(([1-9]?\d|1\d\d|25[0-5]|2[0-4]\d)\.){3}([1-9]?\d|1\d\d|25[0-5]|2[0-4]\d)$#', Utility::parse_url_safe( $site_url, PHP_URL_HOST ) ) ) { self::debug( 'IP home url is not allowed for cloud service.' ); $msg = __( 'In order to use QC services, need a real domain name, cannot use an IP.', 'litespeed-cache' ); Admin_Display::error( $msg ); return false; } // If in valid err_domains, bypass request if ( $this->_is_err_domain( $site_url ) ) { self::debug( 'home url is in err_domains, bypass request: ' . $site_url ); return false; } // we don't want the `img_optm-taken` to fail at any given time if ( self::IMGOPTM_TAKEN === $service_tag ) { return true; } if ( self::SVC_D_SYNC_CONF === $service_tag && ! $this->activated() ) { self::debug( 'Skip sync conf as QC not activated yet.' ); return false; } // Check TTL if ( ! empty( $this->_summary[ 'ttl.' . $service_tag ] ) ) { $ttl = (int) $this->_summary[ 'ttl.' . $service_tag ] - time(); if ( $ttl > 0 ) { self::debug( '❌ TTL limit. [srv] ' . $service_tag . ' [TTL cool down] ' . $ttl . ' seconds' ); return 'svc_hot'; } } $expiration_req = self::EXPIRATION_REQ; // Limit frequent unfinished request to 5min $timestamp_tag = 'curr'; if ( self::SVC_IMG_OPTM . '-' . Img_Optm::TYPE_NEW_REQ === $service_tag ) { $timestamp_tag = 'last'; } // For all other requests, if is under debug mode, will always allow if ( ! $this->conf( self::O_DEBUG ) ) { if ( ! empty( $this->_summary[ $timestamp_tag . '_request.' . $service_tag ] ) ) { $expired = (int) $this->_summary[ $timestamp_tag . '_request.' . $service_tag ] + $expiration_req - time(); if ( $expired > 0 ) { self::debug( '❌ try [' . $service_tag . '] after ' . $expired . ' seconds' ); if ( self::API_VER !== $service_tag ) { $msg = __( 'Cloud Error', 'litespeed-cache' ) . ': ' . sprintf( __( 'Please try after %1$s for service %2$s.', 'litespeed-cache' ), Utility::readable_time( $expired, 0, true ), '' . $service_tag . '' ); Admin_Display::error( [ 'cloud_trylater' => $msg ] ); } return false; } } else { // May fail to store to db if db is oc cached/dead/locked/readonly. Need to store to file to prevent from duplicate calls $file_path = $this->_qc_time_file( $service_tag, $timestamp_tag ); if ( file_exists( $file_path ) ) { $last_request = File::read( $file_path ); $expired = (int) $last_request + $expiration_req * 10 - time(); if ( $expired > 0 ) { self::debug( '❌ try [' . $service_tag . '] after ' . $expired . ' seconds' ); return false; } } // For ver check, additional check to prevent frequent calls as old DB ver may be cached if ( self::API_VER === $service_tag ) { $file_path = $this->_qc_time_file( $service_tag ); if ( file_exists( $file_path ) ) { $last_request = File::read( $file_path ); $expired = (int) $last_request + $expiration_req * 10 - time(); if ( $expired > 0 ) { self::debug( '❌❌ Unusual req! try [' . $service_tag . '] after ' . $expired . ' seconds' ); return false; } } } } } if ( in_array( $service_tag, self::$_pub_svc_set, true ) ) { return true; } if ( ! $this->activated() && self::SVC_D_ACTIVATE !== $service_tag ) { Admin_Display::error( Error::msg( 'qc_setup_required' ) ); return false; } return true; } /** * Get QC req ts file path * * @since 7.5 * * @param string $service_tag Service tag. * @param string $type Type: 'last' or 'curr'. * @return string */ private function _qc_time_file( $service_tag, $type = 'last' ) { if ( 'curr' !== $type ) { $type = 'last'; } $legacy_file = LITESPEED_STATIC_DIR . '/qc_' . $type . '_request' . md5( $service_tag ); if ( file_exists( $legacy_file ) ) { wp_delete_file( $legacy_file ); } $service_tag = preg_replace( '/[^a-zA-Z0-9]/', '', $service_tag ); return LITESPEED_STATIC_DIR . '/qc.' . $type . '.' . $service_tag; } /** * Check if a service tag ttl is valid or not * * @since 7.1 * * @param string $service_tag Service tag. * @return int|false Seconds remaining or false if not hot. */ public function service_hot( $service_tag ) { if ( empty( $this->_summary[ 'ttl.' . $service_tag ] ) ) { return false; } $ttl = (int) $this->_summary[ 'ttl.' . $service_tag ] - time(); if ( $ttl <= 0 ) { return false; } return $ttl; } /** * Post data to QUIC.cloud server * * @since 3.0 * @access public * * @param string $service Service name/route. * @param array|bool $data Payload data or false to omit. * @param int|false $time_out Timeout seconds or false for default. * @return mixed Response payload or false on failure. */ public static function post( $service, $data = false, $time_out = false ) { $instance = self::cls(); return $instance->_post( $service, $data, $time_out ); } /** * Post data to cloud server * * @since 3.0 * @access private * * @param string $service Service name/route. * @param array|bool $data Payload data or false to omit. * @param int|false $time_out Timeout seconds or false for default. * @return mixed Response payload or false on failure. */ private function _post( $service, $data = false, $time_out = false ) { $service_tag = $service; if ( ! empty( $data['action'] ) ) { $service_tag .= '-' . $data['action']; } $maybe_cloud = $this->_maybe_cloud( $service_tag ); if ( ! $maybe_cloud || 'svc_hot' === $maybe_cloud ) { self::debug( 'Maybe cloud failed: ' . wp_json_encode( $maybe_cloud ) ); return $maybe_cloud; } $server = $this->detect_cloud( $service ); if ( ! $server ) { return; } $url = $server . '/' . $this->_maybe_queue( $service ); self::debug( 'posting to : ' . $url ); if ( $data ) { $data['service_type'] = $service; // For queue distribution usage } // Encrypt service as signature // $signature_ts = time(); // $sign_data = [ // 'service_tag' => $service_tag, // 'ts' => $signature_ts, // ]; // $data['signature_b64'] = $this->_sign_b64(implode('', $sign_data)); // $data['signature_ts'] = $signature_ts; self::debug( 'data', $data ); $param = [ 'site_url' => site_url(), // Need to use site_url() as WPML case may change home_url() for diff langs (no need to treat as alias for multi langs) 'main_domain' => ! empty( $this->_summary['main_domain'] ) ? $this->_summary['main_domain'] : '', 'wp_pk_b64' => ! empty( $this->_summary['pk_b64'] ) ? $this->_summary['pk_b64'] : '', 'ver' => Core::VER, 'data' => $data, ]; self::save_summary( [ 'curr_request.' . $service_tag => time() ] ); File::save( $this->_qc_time_file( $service_tag, 'curr' ), time(), true ); $response = wp_safe_remote_post( $url, [ 'body' => $param, 'timeout' => $time_out ? $time_out : 30, 'headers' => [ 'Accept' => 'application/json', 'Expect' => '', ], ] ); return $this->_parse_response( $response, $service, $service_tag, $server ); } /** * Parse response JSON * Mark the request successful if the response status is ok * * @since 3.0 * * @param array|mixed $response WP HTTP API response. * @param string $service Service name. * @param string $service_tag Service tag including action. * @param string $server Server URL. * @return array|false Parsed JSON array or false on failure. */ private function _parse_response( $response, $service, $service_tag, $server ) { // If show the error or not if failed $visible_err = self::API_VER !== $service && self::API_NEWS !== $service && self::SVC_D_DASH !== $service; if ( is_wp_error( $response ) ) { $error_message = $response->get_error_message(); self::debug( 'failed to request: ' . $error_message ); if ( $visible_err ) { $msg = esc_html__( 'Failed to request via WordPress', 'litespeed-cache' ) . ': ' . esc_html( $error_message ) . ' [server] ' . esc_html( $server ) . ' [service] ' . esc_html( $service ); Admin_Display::error( $msg ); // Tmp disabled this node from reusing in 1 day if ( empty( $this->_summary['disabled_node'] ) ) { $this->_summary['disabled_node'] = []; } $this->_summary['disabled_node'][ $server ] = time(); self::save_summary(); // Force redetect node self::debug( 'Node error, redetecting node [svc] ' . $service ); $this->detect_cloud( $service, true ); } return false; } $json = \json_decode( $response['body'], true ); if ( ! is_array( $json ) ) { self::debugErr( 'failed to decode response json: ' . $response['body'] ); if ( $visible_err ) { $msg = esc_html__( 'Failed to request via WordPress', 'litespeed-cache' ) . ': ' . esc_html( $response['body'] ) . ' [server] ' . esc_html( $server ) . ' [service] ' . esc_html( $service ); Admin_Display::error( $msg ); // Tmp disabled this node from reusing in 1 day if ( empty( $this->_summary['disabled_node'] ) ) { $this->_summary['disabled_node'] = []; } $this->_summary['disabled_node'][ $server ] = time(); self::save_summary(); // Force redetect node self::debugErr( 'Node error, redetecting node [svc] ' . $service ); $this->detect_cloud( $service, true ); } return false; } // Check and save TTL data if ( ! empty( $json['_ttl'] ) ) { $ttl = (int) $json['_ttl']; self::debug( 'Service TTL to save: ' . $ttl ); if ( $ttl > 0 && $ttl < 86400 ) { self::save_summary([ 'ttl.' . $service_tag => $ttl + time(), ]); } } if ( ! empty( $json['_code'] ) ) { self::debugErr( 'Hit err _code: ' . $json['_code'] ); if ( 'unpulled_images' === $json['_code'] ) { $msg = __( 'Cloud server refused the current request due to unpulled images. Please pull the images first.', 'litespeed-cache' ); Admin_Display::error( $msg ); return false; } if ( 'blocklisted' === $json['_code'] ) { $msg = __( 'Your domain_key has been temporarily blocklisted to prevent abuse. You may contact support at QUIC.cloud to learn more.', 'litespeed-cache' ); Admin_Display::error( $msg ); return false; } if ( 'rate_limit' === $json['_code'] ) { self::debugErr( 'Cloud server rate limit exceeded.' ); $msg = __( 'Cloud server refused the current request due to rate limiting. Please try again later.', 'litespeed-cache' ); Admin_Display::error( $msg ); return false; } if ( 'heavy_load' === $json['_code'] || 'redetect_node' === $json['_code'] ) { // Force redetect node self::debugErr( 'Node redetecting node [svc] ' . $service ); Admin_Display::info( __( 'Redetected node', 'litespeed-cache' ) . ': ' . Error::msg( $json['_code'] ) ); $this->detect_cloud( $service, true ); } } if ( ! empty( $json['_503'] ) ) { self::debugErr( 'service 503 unavailable temporarily. ' . $json['_503'] ); $msg = __( 'We are working hard to improve your online service experience. The service will be unavailable while we work. We apologize for any inconvenience.', 'litespeed-cache' ); $msg .= ' ' . $json['_503'] . ' [server] ' . esc_html( $server ) . ' [service] ' . esc_html( $service ); Admin_Display::error( $msg ); // Force redetect node self::debugErr( 'Node error, redetecting node [svc] ' . $service ); $this->detect_cloud( $service, true ); return false; } list( $json, $return ) = $this->extract_msg( $json, $service, $server ); if ( $return ) { return false; } $curr_request = $this->_summary[ 'curr_request.' . $service_tag ]; self::save_summary([ 'last_request.' . $service_tag => $curr_request, 'curr_request.' . $service_tag => 0, ]); File::save( $this->_qc_time_file( $service_tag ), $curr_request, true ); File::save( $this->_qc_time_file( $service_tag, 'curr' ), 0, true ); if ( $json ) { self::debug2( 'response ok', $json ); } else { self::debug2( 'response ok' ); } // Only successful request return Array return $json; } /** * Extract msg from json * * @since 5.0 * * @param array $json Response JSON. * @param string $service Service name. * @param string|bool $server Server URL or false. * @param bool $is_callback Whether called from callback context. * @return array Array with [json array, bool should_return_false] */ public function extract_msg( $json, $service, $server = false, $is_callback = false ) { if ( ! empty( $json['_info'] ) ) { self::debug( '_info: ' . $json['_info'] ); $msg = __( 'Message from QUIC.cloud server', 'litespeed-cache' ) . ': ' . $json['_info']; $msg .= $this->_parse_link( $json ); Admin_Display::info( $msg ); unset( $json['_info'] ); } if ( ! empty( $json['_note'] ) ) { self::debug( '_note: ' . $json['_note'] ); $msg = __( 'Message from QUIC.cloud server', 'litespeed-cache' ) . ': ' . $json['_note']; $msg .= $this->_parse_link( $json ); Admin_Display::note( $msg ); unset( $json['_note'] ); } if ( ! empty( $json['_success'] ) ) { self::debug( '_success: ' . $json['_success'] ); $msg = __( 'Good news from QUIC.cloud server', 'litespeed-cache' ) . ': ' . $json['_success']; $msg .= $this->_parse_link( $json ); Admin_Display::success( $msg ); unset( $json['_success'] ); } // Upgrade is required if ( ! empty( $json['_err_req_v'] ) ) { self::debug( '_err_req_v: ' . $json['_err_req_v'] ); $msg = sprintf( __( '%1$s plugin version %2$s required for this action.', 'litespeed-cache' ), Core::NAME, 'v' . $json['_err_req_v'] . '+' ) . ' [server] ' . esc_html( $server ) . ' [service] ' . esc_html( $service ); // Append upgrade link $msg2 = ' ' . GUI::plugin_upgrade_link( Core::NAME, Core::PLUGIN_NAME, $json['_err_req_v'] ); $msg2 .= $this->_parse_link( $json ); Admin_Display::error( $msg . $msg2 ); return [ $json, true ]; } // Parse _carry_on info if ( ! empty( $json['_carry_on'] ) ) { self::debug( 'Carry_on usage', $json['_carry_on'] ); // Store generic info foreach ( [ 'usage', 'promo', 'mini_html', 'partner', '_error', '_info', '_note', '_success' ] as $v ) { if ( isset( $json['_carry_on'][ $v ] ) ) { switch ( $v ) { case 'usage': $usage_svc_tag = in_array( $service, [ self::SVC_CCSS, self::SVC_UCSS, self::SVC_VPI ], true ) ? self::SVC_PAGE_OPTM : $service; $this->_summary[ 'usage.' . $usage_svc_tag ] = $json['_carry_on'][ $v ]; break; case 'promo': if ( empty( $this->_summary[ $v ] ) || ! is_array( $this->_summary[ $v ] ) ) { $this->_summary[ $v ] = []; } $this->_summary[ $v ][] = $json['_carry_on'][ $v ]; break; case 'mini_html': foreach ( $json['_carry_on'][ $v ] as $k2 => $v2 ) { if ( 0 === strpos( $k2, 'ttl.' ) ) { $v2 += time(); } $this->_summary[ $v ][ $k2 ] = $v2; } break; case 'partner': $this->_summary[ $v ] = $json['_carry_on'][ $v ]; break; case '_error': case '_info': case '_note': case '_success': $color_mode = substr( $v, 1 ); $msgs = $json['_carry_on'][ $v ]; Admin_Display::add_unique_notice( $color_mode, $msgs, true ); break; default: break; } } } self::save_summary(); unset( $json['_carry_on'] ); } // Parse general error msg if ( ! $is_callback && ( empty( $json['_res'] ) || 'ok' !== $json['_res'] ) ) { $json_msg = ! empty( $json['_msg'] ) ? $json['_msg'] : 'unknown'; self::debug( '❌ _err: ' . $json_msg, $json ); $str_translated = Error::msg( $json_msg ); $msg = __( 'Failed to communicate with QUIC.cloud server', 'litespeed-cache' ) . ': ' . $str_translated . ' [server] ' . esc_html( $server ) . ' [service] ' . esc_html( $service ); $msg .= $this->_parse_link( $json ); $visible_err = self::API_VER !== $service && self::API_NEWS !== $service && self::SVC_D_DASH !== $service; if ( $visible_err ) { Admin_Display::error( $msg ); } // QC may try auto alias // Store the domain as `err_domains` only for QC auto alias feature if ( 'err_alias' === $json_msg ) { if ( empty( $this->_summary['err_domains'] ) ) { $this->_summary['err_domains'] = []; } $site_url = site_url(); if ( ! array_key_exists( $site_url, $this->_summary['err_domains'] ) ) { $this->_summary['err_domains'][ $site_url ] = time(); } self::save_summary(); } // Site not on QC, reset QC connection registration if ( 'site_not_registered' === $json_msg || 'err_key' === $json_msg ) { $this->_reset_qc_reg(); } return [ $json, true ]; } unset( $json['_res'] ); if ( ! empty( $json['_msg'] ) ) { unset( $json['_msg'] ); } return [ $json, false ]; } /** * Parse _links from json * * @since 1.6.5 * @since 1.6.7 Self clean the parameter * @access private * * @param array $json JSON array (passed by reference). * @return string HTML link string. */ private function _parse_link( &$json ) { $msg = ''; if ( ! empty( $json['_links'] ) ) { foreach ( $json['_links'] as $v ) { $msg .= ' ' . sprintf( '%s', esc_url( $v['link'] ), ! empty( $v['cls'] ) ? esc_attr( $v['cls'] ) : '', esc_html( $v['title'] ) ); } unset( $json['_links'] ); } return $msg; } } src/guest.cls.php000064400000005405152075713330007765 0ustar00sync_lists(); } /** * Sync Guest Mode IP and UA lists. * * Fetches the latest IP and UA lists from QUIC.cloud API and saves them locally. * * @since 7.7 * @return array{success: bool, message: string} */ public function sync_lists() { self::debug( 'Starting Guest Mode lists sync' ); $cloud_dir = LITESPEED_STATIC_DIR . '/cloud'; $results = [ 'ips' => false, 'uas' => false, ]; foreach ( [ 'ips', 'uas' ] as $type ) { $data = $this->_fetch_api( $this->_cloud_server_wp . '/gm_' . $type ); if ( $data && File::save( $cloud_dir . '/gm_' . $type . '.txt', $data, true ) ) { self::debug( 'Guest Mode ' . $type . ' synced' ); $results[ $type ] = true; } } $success = $results['ips'] && $results['uas']; $message = $success ? __( 'Guest Mode lists synced successfully.', 'litespeed-cache' ) : __( 'Failed to sync Guest Mode lists.', 'litespeed-cache' ); return [ 'success' => $success, 'message' => $message, ]; } /** * Fetch data from API. * * @since 7.7 * @param string $url API URL. * @return string|false Data on success, false on failure. */ private function _fetch_api( $url ) { self::debug( 'Fetching: ' . $url ); $response = wp_remote_get( $url, [ 'timeout' => 15, ] ); if ( is_wp_error( $response ) ) { self::debug( 'Fetch error: ' . $response->get_error_message() ); return false; } $code = wp_remote_retrieve_response_code( $response ); if ( 200 !== $code ) { self::debug( 'Fetch failed with code: ' . $code ); return false; } $body = wp_remote_retrieve_body( $response ); if ( empty( $body ) ) { self::debug( 'Empty response body' ); return false; } return $body; } /** * Handle all request actions from main class. * * @since 7.7 * @return void */ public function handler() { $type = Router::verify_type(); switch ( $type ) { case self::TYPE_SYNC: $result = $this->sync_lists(); if ( Router::is_ajax() ) { wp_send_json( $result ); } if ( $result['success'] ) { Admin_Display::success( $result['message'] ); } else { Admin_Display::error( $result['message'] ); } break; default: break; } Admin::redirect(); } } src/cloud-misc.trait.php000064400000024514152075713330011241 0ustar00xxxxxxxx2` * * @since 7.0 * * @param string $type Type. * @param bool $force Force refresh. * @return string */ public function load_qc_status_for_dash( $type, $force = false ) { return Str::translate_qc_apis( $this->_load_qc_status_for_dash( $type, $force ) ); } /** * Internal: load QC status HTML for dash. * * @param string $type Type. * @param bool $force Force refresh. * @return string */ private function _load_qc_status_for_dash( $type, $force = false ) { if ( ! $force && ! empty( $this->_summary['mini_html'] ) && isset( $this->_summary['mini_html'][ $type ] ) && ! empty( $this->_summary['mini_html'][ 'ttl.' . $type ] ) && $this->_summary['mini_html'][ 'ttl.' . $type ] > time() ) { return Str::safe_html( $this->_summary['mini_html'][ $type ] ); } // Try to update dash content $data = self::post( self::SVC_D_DASH, [ 'action2' => ( 'cdn_dash_mini' === $type ? 'cdn_dash' : $type ) ] ); if ( ! empty( $data['qc_activated'] ) ) { // Sync conf as changed if ( empty( $this->_summary['qc_activated'] ) || $this->_summary['qc_activated'] !== $data['qc_activated'] ) { $msg = sprintf( __( 'Congratulations, %s successfully set this domain up for the online services with CDN service.', 'litespeed-cache' ), 'QUIC.cloud' ); Admin_Display::success( '🎊 ' . $msg ); $this->_clear_reset_qc_reg_msg(); // Turn on CDN option $this->cls( 'Conf' )->update_confs( [ self::O_CDN_QUIC => true ] ); $this->cls( 'CDN\Quic' )->try_sync_conf( true ); } $this->_summary['qc_activated'] = $data['qc_activated']; $this->save_summary(); } // Show the info if ( isset( $this->_summary['mini_html'][ $type ] ) ) { return Str::safe_html( $this->_summary['mini_html'][ $type ] ); } return ''; } /** * Show latest commit version always if is on dev * * @since 3.0 */ public function check_dev_version() { if ( ! preg_match( '/[^\d\.]/', Core::VER ) ) { return; } $last_check = empty( $this->_summary[ 'last_request.' . self::API_VER ] ) ? 0 : $this->_summary[ 'last_request.' . self::API_VER ]; if ( time() - $last_check > 86400 ) { $auto_v = self::version_check( 'dev' ); if ( ! empty( $auto_v['dev'] ) ) { self::save_summary( [ 'version.dev' => $auto_v['dev'] ] ); } } if ( empty( $this->_summary['version.dev'] ) ) { return; } self::debug( 'Latest dev version ' . $this->_summary['version.dev'] ); if ( version_compare( $this->_summary['version.dev'], Core::VER, '<=' ) ) { return; } // Show the dev banner require_once LSCWP_DIR . 'tpl/banner/new_version_dev.tpl.php'; } /** * Check latest version * * @since 2.9 * @access public * * @param string|false $src Source. * @return mixed */ public static function version_check( $src = false ) { $req_data = [ 'v' => defined( 'LSCWP_CUR_V' ) ? LSCWP_CUR_V : '', 'src' => $src, 'php' => phpversion(), ]; // If code ver is smaller than db ver, bypass if ( ! empty( $req_data['v'] ) && version_compare( Core::VER, $req_data['v'], '<' ) ) { return; } if ( defined( 'LITESPEED_ERR' ) ) { $litespeed_err = constant( 'LITESPEED_ERR' ); $req_data['err'] = base64_encode( ! is_string( $litespeed_err ) ? wp_json_encode( $litespeed_err ) : $litespeed_err ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode } $data = self::post( self::API_VER, $req_data ); return $data; } /** * Show latest news * * @since 3.0 */ public function news() { $this->_update_news(); if ( empty( $this->_summary['news.new'] ) ) { return; } if ( ! empty( $this->_summary['news.plugin'] ) && Activation::cls()->dash_notifier_is_plugin_active( $this->_summary['news.plugin'] ) ) { return; } require_once LSCWP_DIR . 'tpl/banner/cloud_news.tpl.php'; } /** * Update latest news * * @since 2.9.9.1 */ private function _update_news() { if ( ! empty( $this->_summary['news.utime'] ) && time() - (int) $this->_summary['news.utime'] < 86400 * 7 ) { return; } self::save_summary( [ 'news.utime' => time() ] ); $data = self::get( self::API_NEWS ); if ( empty( $data['id'] ) ) { return; } // Save news if ( ! empty( $this->_summary['news.id'] ) && (string) $this->_summary['news.id'] === (string) $data['id'] ) { return; } $this->_summary['news.id'] = $data['id']; $this->_summary['news.plugin'] = ! empty( $data['plugin'] ) ? $data['plugin'] : ''; $this->_summary['news.title'] = ! empty( $data['title'] ) ? $data['title'] : ''; $this->_summary['news.content'] = ! empty( $data['content'] ) ? $data['content'] : ''; $this->_summary['news.zip'] = ! empty( $data['zip'] ) ? $data['zip'] : ''; $this->_summary['news.new'] = 1; if ( $this->_summary['news.plugin'] ) { $plugin_info = Activation::cls()->dash_notifier_get_plugin_info( $this->_summary['news.plugin'] ); if ( $plugin_info && ! empty( $plugin_info->name ) ) { $this->_summary['news.plugin_name'] = $plugin_info->name; } } self::save_summary(); } /** * Check if contains a package in a service or not * * @since 4.0 * * @param string $service Service. * @param int $pkg Package flag. * @return bool */ public function has_pkg( $service, $pkg ) { if ( ! empty( $this->_summary[ 'usage.' . $service ]['pkgs'] ) && ( $this->_summary[ 'usage.' . $service ]['pkgs'] & $pkg ) ) { return true; } return false; } /** * Get allowance of current service * * @since 3.0 * @access private * * @param string $service Service. * @param string|bool $err Error code by ref. * @return int */ public function allowance( $service, &$err = false ) { // Only auto sync usage at most one time per day if ( empty( $this->_summary[ 'last_request.' . self::SVC_D_USAGE ] ) || time() - (int) $this->_summary[ 'last_request.' . self::SVC_D_USAGE ] > 86400 ) { $this->sync_usage(); } if ( in_array( $service, [ self::SVC_CCSS, self::SVC_UCSS, self::SVC_VPI ], true ) ) { // @since 4.2 $service = self::SVC_PAGE_OPTM; } if ( empty( $this->_summary[ 'usage.' . $service ] ) ) { return 0; } $usage = $this->_summary[ 'usage.' . $service ]; // Image optm is always free $allowance_max = 0; if ( self::SVC_IMG_OPTM === $service ) { $allowance_max = self::IMG_OPTM_DEFAULT_GROUP; } $allowance = (int) $usage['quota'] - (int) $usage['used']; $err = 'out_of_quota'; if ( $allowance > 0 ) { if ( $allowance_max && $allowance_max < $allowance ) { $allowance = $allowance_max; } // Daily limit @since 4.2 if ( isset( $usage['remaining_daily_quota'] ) && $usage['remaining_daily_quota'] >= 0 && $usage['remaining_daily_quota'] < $allowance ) { $allowance = $usage['remaining_daily_quota']; if ( ! $allowance ) { $err = 'out_of_daily_quota'; } } return $allowance; } // Check Pay As You Go balance if ( empty( $usage['pag_bal'] ) ) { return $allowance_max; } if ( $allowance_max && $allowance_max < $usage['pag_bal'] ) { return $allowance_max; } return (int) $usage['pag_bal']; } /** * Sync Cloud usage summary data * * @since 3.0 * @access public */ public function sync_usage() { $usage = $this->_post( self::SVC_D_USAGE ); if ( ! $usage ) { return; } self::debug( 'sync_usage ' . wp_json_encode( $usage ) ); foreach ( self::$services as $v ) { $this->_summary[ 'usage.' . $v ] = ! empty( $usage[ $v ] ) ? $usage[ $v ] : false; } self::save_summary(); return $this->_summary; } /** * REST call: check if the error domain is valid call for auto alias purpose * * @since 5.0 */ public function rest_err_domains() { // phpcs:ignore WordPress.Security.NonceVerification.Missing $alias = !empty( $_POST['alias'] ) ? sanitize_text_field( wp_unslash( $_POST['alias'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( empty( $_POST['main_domain'] ) || !$alias ) { return self::err( 'lack_of_param' ); } // phpcs:ignore WordPress.Security.NonceVerification.Missing $this->extract_msg( $_POST, 'Quic.cloud', false, true ); if ( $this->_is_err_domain( $alias ) ) { if ( site_url() === $alias ) { $this->_remove_domain_from_err_list( $alias ); } return self::ok(); } return self::err( 'Not an alias req from here' ); } /** * Remove a domain from err domain * * @since 5.0 * * @param string $url URL to remove. */ private function _remove_domain_from_err_list( $url ) { unset( $this->_summary['err_domains'][ $url ] ); self::save_summary(); } /** * Check if is err domain * * @since 5.0 * * @param string $site_url Site URL. * @return bool */ private function _is_err_domain( $site_url ) { if ( empty( $this->_summary['err_domains'] ) ) { return false; } if ( ! array_key_exists( $site_url, $this->_summary['err_domains'] ) ) { return false; } // Auto delete if too long ago if ( time() - (int) $this->_summary['err_domains'][ $site_url ] > 86400 * 10 ) { $this->_remove_domain_from_err_list( $site_url ); return false; } if ( time() - (int) $this->_summary['err_domains'][ $site_url ] > 86400 ) { return false; } return true; } /** * Show promo from cloud * * @since 3.0 * @access public */ public function show_promo() { if ( empty( $this->_summary['promo'] ) ) { return; } require_once LSCWP_DIR . 'tpl/banner/cloud_promo.tpl.php'; } /** * Clear promo from cloud * * @since 3.0 * @access private */ private function _clear_promo() { if ( count( $this->_summary['promo'] ) > 1 ) { array_shift( $this->_summary['promo'] ); } else { $this->_summary['promo'] = []; } self::save_summary(); } /** * Display a banner for dev env if using preview QC node. * * @since 7.0 */ public function maybe_preview_banner() { if ( false !== strpos( $this->_cloud_server, 'preview.' ) ) { Admin_Display::note( __( 'Linked to QUIC.cloud preview environment, for testing purpose only.', 'litespeed-cache' ), true, true, 'litespeed-warning-bg' ); } } } src/error.cls.php000064400000016552152075713330007774 0ustar00 4300, // .htaccess did not find. 'HTA_DNF' => 4500, // .htaccess did not find. 'HTA_BK' => 9010, // backup 'HTA_R' => 9041, // read htaccess 'HTA_W' => 9042, // write 'HTA_GET' => 9030, // failed to get ]; /** * Throw an error with message * * Throws an exception with the translated error message. * * @since 3.0 * @access public * @param string $code Error code. * @param mixed $args Optional arguments for message formatting. * @throws \Exception Always throws an exception with the error message. */ public static function t( $code, $args = null ) { throw new \Exception( wp_kses_post( self::msg( $code, $args ) ) ); } /** * Translate an error to description * * Converts error codes to human-readable messages. * * @since 3.0 * @access public * @param string $code Error code. * @param mixed $args Optional arguments for message formatting. * @return string Translated error message. */ public static function msg( $code, $args = null ) { switch ( $code ) { case 'qc_setup_required': $msg = sprintf( __( 'You will need to finish %s setup to use the online services.', 'litespeed-cache' ), 'QUIC.cloud' ) . Doc::learn_more( admin_url( 'admin.php?page=litespeed-general' ), __( 'Click here to set.', 'litespeed-cache' ), true, false, true ); break; case 'out_of_daily_quota': $msg = __( 'You have used all of your daily quota for today.', 'litespeed-cache' ); $msg .= ' ' . Doc::learn_more( 'https://docs.quic.cloud/billing/services/#daily-limits-on-free-quota-usage', __( 'Learn more or purchase additional quota.', 'litespeed-cache' ), false, false, true ); break; case 'out_of_quota': $msg = __( 'You have used all of your quota left for current service this month.', 'litespeed-cache' ); $msg .= ' ' . Doc::learn_more( 'https://docs.quic.cloud/billing/services/#daily-limits-on-free-quota-usage', __( 'Learn more or purchase additional quota.', 'litespeed-cache' ), false, false, true ); break; case 'too_many_requested': $msg = __( 'You have too many requested images, please try again in a few minutes.', 'litespeed-cache' ); break; case 'too_many_notified': $msg = __( 'You have images waiting to be pulled. Please wait for the automatic pull to complete, or pull them down manually now.', 'litespeed-cache' ); break; case 'empty_list': $msg = __( 'The image list is empty.', 'litespeed-cache' ); break; case 'lack_of_param': $msg = __( 'Not enough parameters. Please check if the QUIC.cloud connection is set correctly', 'litespeed-cache' ); break; case 'unfinished_queue': $msg = __( 'There is proceeding queue not pulled yet.', 'litespeed-cache' ); break; case 0 === strpos( $code, 'unfinished_queue ' ): $msg = sprintf( __( 'There is proceeding queue not pulled yet. Queue info: %s.', 'litespeed-cache' ), '' . substr( $code, strlen( 'unfinished_queue ' ) ) . '' ); break; case 'err_alias': $msg = __( 'The site is not a valid alias on QUIC.cloud.', 'litespeed-cache' ); break; case 'site_not_registered': $msg = __( 'The site is not registered on QUIC.cloud.', 'litespeed-cache' ); break; case 'err_key': $msg = __( 'The QUIC.cloud connection is not correct. Please try to sync your QUIC.cloud connection again.', 'litespeed-cache' ); break; case 'heavy_load': $msg = __( 'The current server is under heavy load.', 'litespeed-cache' ); break; case 'redetect_node': $msg = __( 'Online node needs to be redetected.', 'litespeed-cache' ); break; case 'err_overdraw': $msg = __( 'Credits are not enough to proceed the current request.', 'litespeed-cache' ); break; case 'W': $msg = __( '%s file not writable.', 'litespeed-cache' ); break; case 'HTA_DNF': if ( ! is_array( $args ) ) { $args = [ '' . $args . '' ]; } $args[] = '.htaccess'; $msg = __( 'Could not find %1$s in %2$s.', 'litespeed-cache' ); break; case 'HTA_LOGIN_COOKIE_INVALID': $msg = sprintf( __( 'Invalid login cookie. Please check the %s file.', 'litespeed-cache' ), '.htaccess' ); break; case 'HTA_BK': $msg = sprintf( __( 'Failed to back up %s file, aborted changes.', 'litespeed-cache' ), '.htaccess' ); break; case 'HTA_R': $msg = sprintf( __( '%s file not readable.', 'litespeed-cache' ), '.htaccess' ); break; case 'HTA_W': $msg = sprintf( __( '%s file not writable.', 'litespeed-cache' ), '.htaccess' ); break; case 'HTA_GET': $msg = sprintf( __( 'Failed to get %s file contents.', 'litespeed-cache' ), '.htaccess' ); break; case 'failed_tb_creation': $msg = __( 'Failed to create table %1$s! SQL: %2$s.', 'litespeed-cache' ); break; case 'crawler_disabled': $msg = __( 'Crawler disabled by the server admin.', 'litespeed-cache' ); break; case 'try_later': // QC error code $msg = __( 'Previous request too recent. Please try again later.', 'litespeed-cache' ); break; case 0 === strpos( $code, 'try_later ' ): $msg = sprintf( __( 'Previous request too recent. Please try again after %s.', 'litespeed-cache' ), '' . Utility::readable_time( substr( $code, strlen( 'try_later ' ) ), 3600, true ) . '' ); break; case 'waiting_for_approval': $msg = __( 'Your application is waiting for approval.', 'litespeed-cache' ); break; case 'callback_fail_hash': $msg = __( 'The callback validation to your domain failed due to hash mismatch.', 'litespeed-cache' ); break; case 'callback_fail': $msg = __( 'The callback validation to your domain failed. Please make sure there is no firewall blocking our servers.', 'litespeed-cache' ); break; case substr( $code, 0, 14 ) === 'callback_fail ': $msg = __( 'The callback validation to your domain failed. Please make sure there is no firewall blocking our servers. Response code: ', 'litespeed-cache' ) . substr( $code, 14 ); break; case 'forbidden': $msg = __( 'Your domain has been forbidden from using our services due to a previous policy violation.', 'litespeed-cache' ); break; case 'err_dns_active': $msg = __( 'You cannot remove this DNS zone, because it is still in use. Please update the domain\'s nameservers, then try to delete this zone again, otherwise your site will become inaccessible.', 'litespeed-cache' ); break; default: $msg = __( 'Unknown error', 'litespeed-cache' ) . ': ' . $code; break; } if ( null !== $args ) { $msg = is_array( $args ) ? vsprintf( $msg, $args ) : sprintf( $msg, $args ); } if ( isset( self::$code_set[ $code ] ) ) { $msg = 'ERROR ' . self::$code_set[ $code ] . ': ' . $msg; } return $msg; } } src/cloud-auth-ip.trait.php000064400000010521152075713330011646 0ustar00_summary['pk_b64'], 0, 4 ) ) !== $hash ) { self::debug( '__callback IP request decryption failed' ); return self::err( 'err_hash' ); } Control::set_nocache( 'Cloud IP hash validation' ); $resp_hash = md5( substr( $this->_summary['pk_b64'], 2, 4 ) ); self::debug( '__callback IP request hash: ' . $resp_hash ); return self::ok( [ 'hash' => $resp_hash ] ); } /** * Check if this visit is from cloud or not * * @since 3.0 */ public function is_from_cloud() { $check_point = time() - 86400 * self::TTL_IPS; if ( empty( $this->_summary['ips'] ) || empty( $this->_summary['ips_ts'] ) || $this->_summary['ips_ts'] < $check_point ) { self::debug( 'Force updating ip as ips_ts is older than ' . self::TTL_IPS . ' days' ); $this->_update_ips(); } $res = $this->cls( 'Router' )->ip_access( $this->_summary['ips'] ); if ( ! $res ) { self::debug( '❌ Not our cloud IP' ); // Auto check ip list again but need an interval limit safety. if ( empty( $this->_summary['ips_ts_runner'] ) || time() - (int) $this->_summary['ips_ts_runner'] > 600 ) { self::debug( 'Force updating ip as ips_ts_runner is older than 10mins' ); // Refresh IP list for future detection $this->_update_ips(); $res = $this->cls( 'Router' )->ip_access( $this->_summary['ips'] ); if ( ! $res ) { self::debug( '❌ 2nd time: Not our cloud IP' ); } else { self::debug( '✅ Passed Cloud IP verification' ); } return $res; } } else { self::debug( '✅ Passed Cloud IP verification' ); } return $res; } /** * Update Cloud IP list * * @since 4.2 * * @throws \Exception When fetching whitelist fails. */ private function _update_ips() { self::debug( 'Load remote Cloud IP list from ' . $this->_cloud_ips ); // Prevent multiple call in a short period self::save_summary([ 'ips_ts' => time(), 'ips_ts_runner' => time(), ]); $response = wp_safe_remote_get( $this->_cloud_ips . '?json' ); if ( is_wp_error( $response ) ) { $error_message = $response->get_error_message(); self::debug( 'failed to get ip whitelist: ' . $error_message ); throw new \Exception( 'Failed to fetch QUIC.cloud whitelist ' . esc_html($error_message) ); } $json = \json_decode( $response['body'], true ); self::debug( 'Load ips', $json ); self::save_summary( [ 'ips' => $json ] ); } /** * Return pong for ping to check PHP function availability * * @since 6.5 * * @return array */ public function ping() { $resp = [ 'v_lscwp' => Core::VER, 'v_lscwp_db' => $this->conf( self::_VER ), 'v_php' => PHP_VERSION, 'v_wp' => $GLOBALS['wp_version'], 'home_url' => home_url(), 'site_url' => site_url(), ]; // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( ! empty( $_POST['funcs'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized foreach ( wp_unslash($_POST['funcs']) as $v ) { $resp[ $v ] = function_exists( $v ) ? 'y' : 'n'; } } // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( ! empty( $_POST['classes'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized foreach ( wp_unslash($_POST['classes']) as $v ) { $resp[ $v ] = class_exists( $v ) ? 'y' : 'n'; } } // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( ! empty( $_POST['consts'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized foreach ( wp_unslash($_POST['consts']) as $v ) { $resp[ $v ] = defined( $v ) ? 'y' : 'n'; } } return self::ok( $resp ); } } src/utility.cls.php000064400000064016152075713330010344 0ustar00|null */ private static $_internal_domains; /** * Validate a list of regex rules by attempting to compile them. * * @since 1.0.9 * @since 3.0 Moved here from admin-settings.cls * @param array $rules Regex fragments (without delimiters). * @return bool True for valid rules, false otherwise. */ public static function syntax_checker( $rules ) { return false !== preg_match( self::arr2regex( $rules ), '' ); } /** * Combine an array of strings into a single alternation regex. * * @since 3.0 * * @param array $arr List of strings. * @param bool $drop_delimiter When true, return without regex delimiters. * @return string Regex pattern. */ public static function arr2regex( $arr, $drop_delimiter = false ) { $arr = self::sanitize_lines( $arr ); $new_arr = []; foreach ( $arr as $v ) { $new_arr[] = preg_quote( $v, '#' ); } $regex = implode( '|', $new_arr ); $regex = str_replace( ' ', '\\ ', $regex ); if ( $drop_delimiter ) { return $regex; } return '#' . $regex . '#'; } /** * Replace wildcard characters in a string/array with their regex equivalents. * * @since 3.2.2 * * @param string|array $value String or list of strings. * @return string|array */ public static function wildcard2regex( $value ) { if ( is_array( $value ) ) { return array_map( __CLASS__ . '::wildcard2regex', $value ); } if ( false !== strpos( $value, '*' ) ) { $value = preg_quote( $value, '#' ); $value = str_replace( '\*', '.*', $value ); } return $value; } /** * Get current page type string. * * @since 2.9 * * @return string Page type. */ public static function page_type() { global $wp_query; $page_type = 'default'; if ( $wp_query->is_page ) { $page_type = is_front_page() ? 'front' : 'page'; } elseif ( $wp_query->is_home ) { $page_type = 'home'; } elseif ( $wp_query->is_single ) { $page_type = get_post_type(); } elseif ( $wp_query->is_category ) { $page_type = 'category'; } elseif ( $wp_query->is_tag ) { $page_type = 'tag'; } elseif ( $wp_query->is_tax ) { $page_type = 'tax'; } elseif ( $wp_query->is_archive ) { if ( $wp_query->is_day ) { $page_type = 'day'; } elseif ( $wp_query->is_month ) { $page_type = 'month'; } elseif ( $wp_query->is_year ) { $page_type = 'year'; } elseif ( $wp_query->is_author ) { $page_type = 'author'; } else { $page_type = 'archive'; } } elseif ( $wp_query->is_search ) { $page_type = 'search'; } elseif ( $wp_query->is_404 ) { $page_type = '404'; } return $page_type; } /** * Get ping speed to a domain via HTTP HEAD timing. * * @since 2.9 * * @param string $domain Domain or URL. * @return int Milliseconds (99999 on error). */ public static function ping( $domain ) { if ( false !== strpos( $domain, ':' ) ) { $host = wp_parse_url( $domain, PHP_URL_HOST ); $domain = $host ? $host : $domain; } $starttime = microtime(true); $file = fsockopen($domain, 443, $errno, $errstr, 10); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fsockopen $stoptime = microtime(true); $status = 0; if (!$file) { $status = 99999; } else { // Site is up fclose($file); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose $status = ($stoptime - $starttime) * 1000; $status = floor($status); } Debug2::debug("[Util] ping [Domain] $domain \t[Speed] $status"); return $status; } /** * Convert seconds/timestamp to a readable relative time. * * @since 1.6.5 * * @param int $seconds_or_timestamp Seconds or 10-digit timestamp. * @param int $timeout If older than this, show absolute time. * @param bool $forward When true, omit "ago". * @return string Human readable time. */ public static function readable_time( $seconds_or_timestamp, $timeout = 3600, $forward = false ) { if ( 10 === strlen( (string) $seconds_or_timestamp ) ) { $seconds = time() - (int) $seconds_or_timestamp; if ( $seconds > $timeout ) { return gmdate( 'm/d/Y H:i:s', (int) $seconds_or_timestamp + (int) LITESPEED_TIME_OFFSET ); } } else { $seconds = (int) $seconds_or_timestamp; } $res = ''; if ( $seconds > 86400 ) { $num = (int) floor( $seconds / 86400 ); $res .= $num . 'd'; $seconds %= 86400; } if ( $seconds > 3600 ) { if ( $res ) { $res .= ', '; } $num = (int) floor( $seconds / 3600 ); $res .= $num . 'h'; $seconds %= 3600; } if ( $seconds > 60 ) { if ( $res ) { $res .= ', '; } $num = (int) floor( $seconds / 60 ); $res .= $num . 'm'; $seconds %= 60; } if ( $seconds > 0 ) { if ( $res ) { $res .= ' '; } $res .= $seconds . 's'; } if ( ! $res ) { return $forward ? __( 'right now', 'litespeed-cache' ) : __( 'just now', 'litespeed-cache' ); } return $forward ? $res : sprintf( __( ' %s ago', 'litespeed-cache' ), $res ); } /** * Convert array to a compact base64 JSON string. * * @since 1.6 * * @param mixed $arr Input array or scalar. * @return string|mixed Encoded string or original value. */ public static function arr2str( $arr ) { if ( ! is_array( $arr ) ) { return $arr; } return base64_encode( wp_json_encode( $arr ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode } /** * Convert size in bytes to human readable form. * * @since 1.6 * * @param int $filesize Bytes. * @param bool $is_1000 When true, use 1000-based units. * @return string */ public static function real_size( $filesize, $is_1000 = false ) { $unit = $is_1000 ? 1000 : 1024; if ( $filesize >= pow( $unit, 3 ) ) { $filesize = round( ( $filesize / pow( $unit, 3 ) ) * 100 ) / 100 . 'G'; } elseif ( $filesize >= pow( $unit, 2 ) ) { $filesize = round( ( $filesize / pow( $unit, 2 ) ) * 100 ) / 100 . 'M'; } elseif ( $filesize >= $unit ) { $filesize = round( ( $filesize / $unit ) * 100 ) / 100 . 'K'; } else { $filesize = $filesize . 'B'; } return $filesize; } /** * Parse HTML attribute string into an array. * * @since 1.2.2 * @since 1.4 Moved from optimize to utility * @access private * * @param string $str Raw attribute string. * @return array Attributes. */ public static function parse_attr( $str ) { $attrs = []; $parsed = wp_kses_hair( $str, self::_kses_protocols() ); foreach ( $parsed as $name => $data ) { $attrs[ $name ] = $data['value']; } return $attrs; } /** * Remove an attribute from an HTML attribute string using wp_kses_hair. * * @since 7.8 * * @param string $attr_str Raw attribute string (e.g. ' type="text/javascript" src="..."'). * @param string $attr_name Attribute name to remove (e.g. 'type', 'async'). * @return string Attribute string with the named attribute removed. */ public static function remove_attr( $attr_str, $attr_name ) { $parsed = wp_kses_hair( $attr_str, self::_kses_protocols() ); if ( ! isset( $parsed[ $attr_name ] ) ) { return $attr_str; } $whole = $parsed[ $attr_name ]['whole']; // For valueless attrs (e.g. async), use word boundary to avoid partial match (e.g. async-fallback) if ( 'y' === $parsed[ $attr_name ]['vless'] ) { return preg_replace( '# ' . preg_quote( $whole, '#' ) . '(?=\s|>|/|$)#i', '', $attr_str, 1 ); } // For attrs with value (e.g. type="text/javascript"), straight replace is safe $result = str_replace( ' ' . $whole, '', $attr_str ); // Handle edge case: attr at the very start of string (no leading space) if ( $result === $attr_str && 0 === strpos( $attr_str, $whole ) ) { $result = ltrim( substr( $attr_str, strlen( $whole ) ) ); } return $result; } /** * Return allowed protocols including data: for attribute parsing. * * WordPress wp_allowed_protocols() does not include data:, but our parse/remove * helpers must preserve data: URIs (e.g. base64 placeholder images). * * @since 7.8 * @return string[] */ private static function _kses_protocols() { static $protocols; if ( null === $protocols ) { $protocols = array_merge( wp_allowed_protocols(), [ 'data' ] ); } return $protocols; } /** * Search for a hit within an array of strings/rules. * * Supports ^prefix, suffix$, ^exact$, and substring. * * @since 1.3 * @access private * * @param string $needle The string to compare. * @param array $haystack Array of rules/strings. * @param bool $has_ttl When true, support "rule TTL" format. * @return bool|string|array False if not found; matched item or [item, ttl] if has_ttl. */ public static function str_hit_array( $needle, $haystack, $has_ttl = false ) { if ( ! $haystack ) { return false; } if ( ! is_array( $haystack ) ) { Debug2::debug( '[Util] ❌ bad param in str_hit_array()!' ); return false; } $hit = false; $this_ttl = 0; foreach ( $haystack as $item ) { if ( ! $item ) { continue; } if ( $has_ttl ) { $this_ttl = 0; $item = explode( ' ', $item ); if ( ! empty( $item[1] ) ) { $this_ttl = $item[1]; } $item = $item[0]; } if ( '^' === substr( $item, 0, 1 ) && '$' === substr( $item, -1 ) ) { if ( substr( $item, 1, -1 ) === $needle ) { $hit = $item; break; } } elseif ( '$' === substr( $item, -1 ) ) { if ( substr( $item, 0, -1 ) === substr( $needle, -strlen( $item ) + 1 ) ) { $hit = $item; break; } } elseif ( '^' === substr( $item, 0, 1 ) ) { if ( substr( $item, 1 ) === substr( $needle, 0, strlen( $item ) - 1 ) ) { $hit = $item; break; } } elseif ( false !== strpos( $needle, $item ) ) { $hit = $item; break; } } if ( $hit ) { return $has_ttl ? [ $hit, $this_ttl ] : $hit; } return false; } /** * Load PHP-compat library. * * @since 1.2.2 * @return void */ public static function compatibility() { require_once LSCWP_DIR . 'lib/php-compatibility.func.php'; } /** * Convert URI path to absolute URL. * * @since 1.3 * * @param string $uri Relative path `/a/b.html` or `a/b.html`. * @return string Absolute URL. */ public static function uri2url( $uri ) { if ( '/' === substr( $uri, 0, 1 ) ) { self::domain_const(); $url = LSCWP_DOMAIN . $uri; } else { $url = home_url( '/' ) . $uri; } return $url; } /** * Get basename from URL. * * @since 4.7 * * @param string $url URL. * @return string Basename. */ public static function basename( $url ) { $url = trim( $url ); $uri = wp_parse_url( $url, PHP_URL_PATH ); $basename = pathinfo( (string) $uri, PATHINFO_BASENAME ); return $basename; } /** * Drop .webp and .avif suffix from a filename. * * @since 4.7 * * @param string $filename Filename. * @return string Cleaned filename. */ public static function drop_webp( $filename ) { if ( in_array( substr( $filename, -5 ), [ '.webp', '.avif' ], true ) ) { $filename = substr( $filename, 0, -5 ); } return $filename; } /** * Convert URL to URI (optionally keep query). * * @since 1.2.2 * @since 1.6.2.1 Added 2nd param keep_qs * * @param string $url URL. * @param bool $keep_qs Keep query string. * @return string URI. */ public static function url2uri( $url, $keep_qs = false ) { $url = trim( $url ); $uri = wp_parse_url( $url, PHP_URL_PATH ); $qs = wp_parse_url( $url, PHP_URL_QUERY ); if ( ! $keep_qs || ! $qs ) { return (string) $uri; } return (string) $uri . '?' . $qs; } /** * Get attachment relative path to upload folder. * * @since 3.0 * * @param string $url Full attachment URL. * @return string Relative upload path like `2018/08/file.jpg`. */ public static function att_short_path( $url ) { if ( ! defined( 'LITESPEED_UPLOAD_PATH' ) ) { $_wp_upload_dir = wp_upload_dir(); $upload_path = self::url2uri( $_wp_upload_dir['baseurl'] ); define( 'LITESPEED_UPLOAD_PATH', $upload_path ); } $local_file = self::url2uri( $url ); $short_path = substr( $local_file, strlen( LITESPEED_UPLOAD_PATH ) + 1 ); return $short_path; } /** * Make URL relative to the site root (preserves subdir). * * @param string $url Absolute URL. * @return string Relative URL starting with '/'. */ public static function make_relative( $url ) { self::domain_const(); if ( 0 === strpos( $url, LSCWP_DOMAIN ) ) { $url = substr( $url, strlen( LSCWP_DOMAIN ) ); } return trim( $url ); } /** * Extract just the scheme+host portion from a URL. * * @since 1.7.1 * * @param string $url URL. * @return string Host-only URL (with scheme if available). */ public static function parse_domain( $url ) { $parsed = wp_parse_url( $url ); if ( empty( $parsed['host'] ) ) { return ''; } if ( ! empty( $parsed['scheme'] ) ) { return $parsed['scheme'] . '://' . $parsed['host']; } return '//' . $parsed['host']; } /** * Drop protocol from URL (e.g., https://example.com -> //example.com). * * @since 3.3 * * @param string $url URL. * @return string Protocol-relative URL. */ public static function noprotocol( $url ) { $tmp = wp_parse_url( trim( $url ) ); if ( ! empty( $tmp['scheme'] ) ) { $url = str_replace( $tmp['scheme'] . ':', '', $url ); } return $url; } /** * Validate IPv4 public address. * * @since 5.5 * * @param string $ip IP address. * @return string|false IP or false when invalid. */ public static function valid_ipv4( $ip ) { return filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ); } /** * Define LSCWP_DOMAIN using the home URL (no trailing slash). * * @since 1.3 * @return void */ public static function domain_const() { if ( defined( 'LSCWP_DOMAIN' ) ) { return; } self::compatibility(); $domain = http_build_url( get_home_url(), [], HTTP_URL_STRIP_ALL ); // phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url define( 'LSCWP_DOMAIN', $domain ); } /** * Sanitize lines based on requested transforms. * * @since 1.3 * * @param array|string $arr Lines as array or newline-separated string. * @param string|null $type Comma-separated transforms: uri,basename,drop_webp,relative,domain,noprotocol,trailingslash,string. * @return array|string Sanitized list or string. */ public static function sanitize_lines( $arr, $type = null ) { $types = $type ? explode( ',', $type ) : []; if ( ! $arr ) { if ( 'string' === $type ) { return ''; } return []; } if ( ! is_array( $arr ) ) { $arr = explode( "\n", $arr ); } $arr = array_map( 'trim', $arr ); $changed = false; if ( in_array( 'uri', $types, true ) ) { $arr = array_map( __CLASS__ . '::url2uri', $arr ); $changed = true; } if ( in_array( 'basename', $types, true ) ) { $arr = array_map( __CLASS__ . '::basename', $arr ); $changed = true; } if ( in_array( 'drop_webp', $types, true ) ) { $arr = array_map( __CLASS__ . '::drop_webp', $arr ); $changed = true; } if ( in_array( 'relative', $types, true ) ) { $arr = array_map( __CLASS__ . '::make_relative', $arr ); $changed = true; } if ( in_array( 'domain', $types, true ) ) { $arr = array_map( __CLASS__ . '::parse_domain', $arr ); $changed = true; } if ( in_array( 'noprotocol', $types, true ) ) { $arr = array_map( __CLASS__ . '::noprotocol', $arr ); $changed = true; } if ( in_array( 'trailingslash', $types, true ) ) { $arr = array_map( 'trailingslashit', $arr ); $changed = true; } if ( $changed ) { $arr = array_map( 'trim', $arr ); } $arr = array_unique( $arr ); $arr = array_filter( $arr ); if ( in_array( 'string', $types, true ) ) { return implode( "\n", $arr ); } return $arr; } /** * Build an admin URL with action & nonce. * * Assumes user capabilities are already checked. * * @since 1.6 Changed order of 2nd&3rd param, changed 3rd param `append_str` to 2nd `type` * * @param string $action Action name. * @param string|false $type Optional type query value. * @param bool $is_ajax Whether to build for admin-ajax.php. * @param string|null|bool $page Page filename or true for admin.php. * @param array $append_arr Extra query parameters. * @param bool $unescape Return unescaped URL. * @return string Built URL. */ public static function build_url( $action, $type = false, $is_ajax = false, $page = null, $append_arr = [], $unescape = false ) { $prefix = '?'; if ( '_ori' === $page ) { $page = true; $append_arr['_litespeed_ori'] = 1; } if ( ! $is_ajax ) { if ( $page ) { if ( true === $page ) { $page = 'admin.php'; } elseif ( false !== strpos( $page, '?' ) ) { $prefix = '&'; } $combined = $page . $prefix . Router::ACTION . '=' . $action; } else { // Current page rebuild URL. $params = $_GET; // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! empty( $params ) ) { if ( isset( $params[ Router::ACTION ] ) ) { unset( $params[ Router::ACTION ] ); } if ( isset( $params['_wpnonce'] ) ) { unset( $params['_wpnonce'] ); } if ( ! empty( $params ) ) { $prefix .= http_build_query( $params ) . '&'; } } global $pagenow; $combined = $pagenow . $prefix . Router::ACTION . '=' . $action; } } else { $combined = 'admin-ajax.php?action=litespeed_ajax&' . Router::ACTION . '=' . $action; } $prenonce = is_network_admin() ? network_admin_url( $combined ) : admin_url( $combined ); $url = wp_nonce_url( $prenonce, $action, Router::NONCE ); if ( $type ) { // Remove potential param `type` from url. $parsed = wp_parse_url( htmlspecialchars_decode( $url ) ); $query = []; if ( isset( $parsed['query'] ) ) { parse_str( $parsed['query'], $query ); } $built_arr = array_merge( $query, [ Router::TYPE => $type ] ); $parsed['query'] = http_build_query( array_merge( $built_arr, (array) $append_arr ) ); self::compatibility(); $url = http_build_url( $parsed ); // phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url $url = htmlspecialchars( $url, ENT_QUOTES, 'UTF-8' ); } if ( $unescape ) { $url = wp_specialchars_decode( $url ); } return $url; } /** * Check if a host is internal (same as site host or filtered list). * * @since 1.2.3 * * @param string $host Host to test. * @return bool True if internal. */ public static function internal( $host ) { if ( ! defined( 'LITESPEED_FRONTEND_HOST' ) ) { if ( defined( 'WP_HOME' ) ) { $home_host = constant( 'WP_HOME' ); } else { $home_host = get_option( 'home' ); } define( 'LITESPEED_FRONTEND_HOST', (string) wp_parse_url( $home_host, PHP_URL_HOST ) ); } if ( LITESPEED_FRONTEND_HOST === $host ) { return true; } if ( ! isset( self::$_internal_domains ) ) { self::$_internal_domains = apply_filters( 'litespeed_internal_domains', [] ); } if ( self::$_internal_domains ) { return in_array( $host, self::$_internal_domains, true ); } return false; } /** * Check if a URL is an internal existing file and return its real path and size. * * @since 1.2.2 * @since 1.6.2 Moved here from optm.cls due to usage of media.cls * * @param string $url URL. * @param string|false $addition_postfix Optional postfix to append to path before checking. * @return array{0:string,1:int}|false [realpath, size] or false. */ public static function is_internal_file( $url, $addition_postfix = false ) { if ( 'data:' === substr( $url, 0, 5 ) ) { Debug2::debug2( '[Util] data: content not file' ); return false; } $url_parsed = wp_parse_url( $url ); if ( isset( $url_parsed['host'] ) && ! self::internal( $url_parsed['host'] ) ) { // Check if is cdn path. if ( ! CDN::internal( $url_parsed['host'] ) ) { Debug2::debug2( '[Util] external' ); return false; } } if ( empty( $url_parsed['path'] ) ) { return false; } // Replace child blog path for assets (multisite). if ( is_multisite() && defined( 'PATH_CURRENT_SITE' ) ) { $pattern = '#^' . PATH_CURRENT_SITE . '([_0-9a-zA-Z-]+/)(wp-(content|admin|includes))#U'; $replacement = PATH_CURRENT_SITE . '$2'; $url_parsed['path'] = preg_replace( $pattern, $replacement, $url_parsed['path'] ); } // Parse file path. if ( '/' === substr( $url_parsed['path'], 0, 1 ) ) { $docroot = isset( $_SERVER['DOCUMENT_ROOT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['DOCUMENT_ROOT'] ) ) : ''; if ( defined( 'LITESPEED_WP_REALPATH' ) ) { $file_path_ori = $docroot . constant( 'LITESPEED_WP_REALPATH' ) . $url_parsed['path']; } else { $file_path_ori = $docroot . $url_parsed['path']; } } else { $file_path_ori = Router::frontend_path() . '/' . $url_parsed['path']; } // Optional postfix. if ( $addition_postfix ) { $file_path_ori .= '.' . $addition_postfix; } $file_path_ori = apply_filters( 'litespeed_realpath', $file_path_ori ); $file_path = realpath( $file_path_ori ); if ( ! is_file( $file_path ) ) { Debug2::debug2( '[Util] file not exist: ' . $file_path_ori ); return false; } return [ $file_path, (int) filesize( $file_path ) ]; } /** * Safely parse URL and component. * * @since 3.4.3 * * @param string $url URL to parse. * @param int $component One of the PHP_URL_* constants. * @return mixed */ public static function parse_url_safe( $url, $component = -1 ) { if ( '//' === substr( $url, 0, 2 ) ) { $url = 'https:' . $url; } return wp_parse_url( $url, $component ); } /** * Replace URLs in a srcset attribute using a callback. * * @since 2.2.3 * * @param string $content HTML content containing srcset. * @param callable $callback Callback that receives old URL and returns new URL or false. * @return string Modified content. */ public static function srcset_replace( $content, $callback ) { preg_match_all( '# srcset=([\'"])(.+)\g{1}#iU', $content, $matches ); $srcset_ori = []; $srcset_final = []; if ( ! empty( $matches[2] ) ) { foreach ( $matches[2] as $k => $urls_ori ) { $urls_final = explode( ',', $urls_ori ); $changed = false; foreach ( $urls_final as $k2 => $url_info ) { $url_info_arr = explode( ' ', trim( $url_info ) ); $new_url = call_user_func( $callback, $url_info_arr[0] ); if ( ! $new_url ) { continue; } $changed = true; $urls_final[ $k2 ] = str_replace( $url_info_arr[0], $new_url, $url_info ); Debug2::debug2( '[Util] - srcset replaced to ' . $new_url . ( ! empty( $url_info_arr[1] ) ? ' ' . $url_info_arr[1] : '' ) ); } if ( ! $changed ) { continue; } $urls_final = implode( ',', $urls_final ); $srcset_ori[] = $matches[0][ $k ]; $srcset_final[] = str_replace( $urls_ori, $urls_final, $matches[0][ $k ] ); } } if ( $srcset_ori ) { $content = str_replace( $srcset_ori, $srcset_final, $content ); Debug2::debug2( '[Util] - srcset replaced' ); } return $content; } /** * Generate pagination HTML or return offset. * * @since 3.0 * * @param int $total Total items. * @param int $limit Items per page. * @param bool $return_offset When true, return numeric offset instead of HTML. * @return int|string */ public static function pagination( $total, $limit, $return_offset = false ) { $pagenum = isset( $_GET['pagenum'] ) ? absint( $_GET['pagenum'] ) : 1; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $offset = ( $pagenum - 1 ) * $limit; $num_of_pages = (int) ceil( $total / $limit ); if ( $offset > $total ) { $offset = $total - $limit; } if ( $offset < 0 ) { $offset = 0; } if ( $return_offset ) { return $offset; } $page_links = paginate_links( [ 'base' => add_query_arg( 'pagenum', '%#%' ), 'format' => '', 'prev_text' => '«', 'next_text' => '»', 'total' => $num_of_pages, 'current' => $pagenum, ] ); return '
' . $page_links . '
'; } /** * Build a GROUP placeholder like "(%s,%s),(%s,%s)" for a list of rows. * * @since 2.0 * * @param array> $data Data rows (values already prepared). * @param string $fields Fields CSV (only used to count columns). * @return string Placeholder string. */ public static function chunk_placeholder( $data, $fields ) { $division = substr_count( $fields, ',' ) + 1; $q = implode( ',', array_map( function ( $el ) { return '(' . implode( ',', $el ) . ')'; }, array_chunk( array_fill( 0, count( $data ), '%s' ), $division ) ) ); return $q; } /** * Prepare image sizes list for optimization UI. * * @since 7.5 * * @param bool $detailed When true, return detailed objects; otherwise size names. * @return array> */ public static function prepare_image_sizes_array( $detailed = false ) { $image_sizes = wp_get_registered_image_subsizes(); $sizes = []; foreach ( $image_sizes as $current_size_name => $current_size ) { if ( empty( $current_size['width'] ) && empty( $current_size['height'] ) ) { continue; } if ( ! $detailed ) { $sizes[] = $current_size_name; } else { $label = $current_size['width'] . 'x' . $current_size['height']; if ( $current_size_name !== $label ) { $label = ucfirst( $current_size_name ) . ' ( ' . $label . ' )'; } $sizes[] = [ 'label' => $label, 'file_size' => $current_size_name, 'width' => (int) $current_size['width'], 'height' => (int) $current_size['height'], ]; } } return $sizes; } } src/object-cache-wp.cls.php000064400000045504152075713330011575 0ustar00_object_cache = \LiteSpeed\Object_Cache::cls(); $this->multisite = is_multisite(); $this->blog_prefix = $this->multisite ? get_current_blog_id() . ':' : ''; /** * Fix multiple instance using same oc issue * * @since 1.8.2 */ if ( ! defined( 'LSOC_PREFIX' ) ) { define( 'LSOC_PREFIX', substr( md5( __FILE__ ), -5 ) ); } } /** * Makes private properties readable for backward compatibility. * * @since 5.4 * @access public * * @param string $name Property to get. * @return mixed Property. */ public function __get( $name ) { return $this->$name; } /** * Makes private properties settable for backward compatibility. * * @since 5.4 * @access public * * @param string $name Property to set. * @param mixed $value Property value. * @return mixed Newly-set property. */ public function __set( $name, $value ) { $this->$name = $value; return $this->$name; } /** * Makes private properties checkable for backward compatibility. * * @since 5.4 * @access public * * @param string $name Property to check if set. * @return bool Whether the property is set. */ public function __isset( $name ) { return isset( $this->$name ); } /** * Makes private properties un-settable for backward compatibility. * * @since 5.4 * @access public * * @param string $name Property to unset. */ public function __unset( $name ) { unset( $this->$name ); } /** * Serves as a utility function to determine whether a key is valid. * * @since 5.4 * @access protected * * @param int|string $key Cache key to check for validity. * @return bool Whether the key is valid. */ protected function is_valid_key( $key ) { if ( is_int( $key ) ) { return true; } if ( is_string( $key ) && '' !== trim( $key ) ) { return true; } $type = gettype( $key ); if ( ! function_exists( '__' ) ) { wp_load_translations_early(); } $message = is_string( $key ) ? __( 'Cache key must not be an empty string.' ) : sprintf( /* translators: %s: The type of the given cache key. */ __( 'Cache key must be integer or non-empty string, %s given.' ), $type ); _doing_it_wrong( esc_html( __METHOD__ ), esc_html( $message ), '6.1.0' ); return false; } /** * Get the final key. * * Generates a unique cache key based on group and prefix. * * @since 1.8 * @access private * @param int|string $key Cache key. * @param string $group Optional. Cache group. Default 'default'. * @return string The final cache key. */ private function _key( $key, $group = 'default' ) { if ( empty( $group ) ) { $group = 'default'; } $prefix = $this->_object_cache->is_global( $group ) ? '' : $this->blog_prefix; return LSOC_PREFIX . $prefix . $group . '.' . $key; } /** * Output debug info. * * Returns cache statistics for debugging purposes. * * @since 1.8 * @access public * @return string Cache statistics. */ public function debug() { return ' [total] ' . $this->cache_total . ' [hit_incall] ' . $this->count_hit_incall . ' [hit] ' . $this->count_hit . ' [miss_incall] ' . $this->count_miss_incall . ' [miss] ' . $this->count_miss . ' [set] ' . $this->count_set; } /** * Adds data to the cache if it doesn't already exist. * * @since 1.8 * @access public * @see WP_Object_Cache::set() * * @param int|string $key What to call the contents in the cache. * @param mixed $data The contents to store in the cache. * @param string $group Optional. Where to group the cache contents. Default 'default'. * @param int $expire Optional. When to expire the cache contents, in seconds. * Default 0 (no expiration). * @return bool True on success, false if cache key and group already exist. */ public function add( $key, $data, $group = 'default', $expire = 0 ) { if ( wp_suspend_cache_addition() ) { return false; } if ( ! $this->is_valid_key( $key ) ) { return false; } if ( empty( $group ) ) { $group = 'default'; } $id = $this->_key( $key, $group ); if ( array_key_exists( $id, $this->_cache ) ) { return false; } return $this->set( $key, $data, $group, (int) $expire ); } /** * Adds multiple values to the cache in one call. * * @since 5.4 * @access public * * @param array $data Array of keys and values to be added. * @param string $group Optional. Where the cache contents are grouped. Default empty. * @param int $expire Optional. When to expire the cache contents, in seconds. * Default 0 (no expiration). * @return bool[] Array of return values, grouped by key. Each value is either * true on success, or false if cache key and group already exist. */ public function add_multiple( array $data, $group = '', $expire = 0 ) { $values = []; foreach ( $data as $key => $value ) { $values[ $key ] = $this->add( $key, $value, $group, $expire ); } return $values; } /** * Replaces the contents in the cache, if contents already exist. * * @since 1.8 * @access public * @see WP_Object_Cache::set() * * @param int|string $key What to call the contents in the cache. * @param mixed $data The contents to store in the cache. * @param string $group Optional. Where to group the cache contents. Default 'default'. * @param int $expire Optional. When to expire the cache contents, in seconds. * Default 0 (no expiration). * @return bool True if contents were replaced, false if original value does not exist. */ public function replace( $key, $data, $group = 'default', $expire = 0 ) { if ( ! $this->is_valid_key( $key ) ) { return false; } if ( empty( $group ) ) { $group = 'default'; } $id = $this->_key( $key, $group ); if ( ! array_key_exists( $id, $this->_cache ) ) { return false; } return $this->set( $key, $data, $group, (int) $expire ); } /** * Sets the data contents into the cache. * * The cache contents are grouped by the $group parameter followed by the * $key. This allows for duplicate IDs in unique groups. Therefore, naming of * the group should be used with care and should follow normal function * naming guidelines outside of core WordPress usage. * * The $expire parameter is not used, because the cache will automatically * expire for each time a page is accessed and PHP finishes. The method is * more for cache plugins which use files. * * @since 1.8 * @since 5.4 Returns false if cache key is invalid. * @access public * * @param int|string $key What to call the contents in the cache. * @param mixed $data The contents to store in the cache. * @param string $group Optional. Where to group the cache contents. Default 'default'. * @param int $expire Optional. When to expire the cache contents, in seconds. * Default 0 (no expiration). * @return bool True if contents were set, false if key is invalid. */ public function set( $key, $data, $group = 'default', $expire = 0 ) { if ( ! $this->is_valid_key( $key ) ) { return false; } if ( empty( $group ) ) { $group = 'default'; } $id = $this->_key( $key, $group ); if ( is_object( $data ) ) { $data = clone $data; } $this->_cache[ $id ] = $data; if ( array_key_exists( $id, $this->_cache_404 ) ) { unset( $this->_cache_404[ $id ] ); } if ( ! $this->_object_cache->is_non_persistent( $group ) ) { // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize $this->_object_cache->set( $id, serialize( [ 'data' => $data ] ), (int) $expire ); ++$this->count_set; } return true; } /** * Sets multiple values to the cache in one call. * * @since 5.4 * @access public * * @param array $data Array of key and value to be set. * @param string $group Optional. Where the cache contents are grouped. Default empty. * @param int $expire Optional. When to expire the cache contents, in seconds. * Default 0 (no expiration). * @return bool[] Array of return values, grouped by key. Each value is always true. */ public function set_multiple( array $data, $group = '', $expire = 0 ) { $values = []; foreach ( $data as $key => $value ) { $values[ $key ] = $this->set( $key, $value, $group, $expire ); } return $values; } /** * Retrieves the cache contents, if it exists. * * The contents will be first attempted to be retrieved by searching by the * key in the cache group. If the cache is hit (success) then the contents * are returned. * * On failure, the number of cache misses will be incremented. * * @since 1.8 * @access public * * @param int|string $key The key under which the cache contents are stored. * @param string $group Optional. Where the cache contents are grouped. Default 'default'. * @param bool $force Optional. Unused. Whether to force an update of the local cache * from the persistent cache. Default false. * @param bool $found Optional. Whether the key was found in the cache (passed by reference). * Disambiguates a return of false, a storable value. Default null. * @return mixed|false The cache contents on success, false on failure to retrieve contents. */ public function get( $key, $group = 'default', $force = false, &$found = null ) { if ( ! $this->is_valid_key( $key ) ) { return false; } if ( empty( $group ) ) { $group = 'default'; } $id = $this->_key( $key, $group ); $found = false; $found_in_oc = false; $cache_val = false; if ( array_key_exists( $id, $this->_cache ) && ! $force ) { $found = true; $cache_val = $this->_cache[ $id ]; ++$this->count_hit_incall; } elseif ( ! array_key_exists( $id, $this->_cache_404 ) && ! $this->_object_cache->is_non_persistent( $group ) ) { $v = $this->_object_cache->get( $id, $group ); if ( false !== $v ) { $v = maybe_unserialize( $v ); } // To be compatible with false val. if ( is_array( $v ) && array_key_exists( 'data', $v ) ) { ++$this->count_hit; $found = true; $found_in_oc = true; $cache_val = $v['data']; } else { // Can't find key, cache it to 404. $this->_cache_404[ $id ] = 1; ++$this->count_miss; } } else { ++$this->count_miss_incall; } if ( is_object( $cache_val ) ) { $cache_val = clone $cache_val; } if ( $found_in_oc ) { $this->_cache[ $id ] = $cache_val; } ++$this->cache_total; return $cache_val; } /** * Retrieves multiple values from the cache in one call. * * @since 5.4 * @access public * * @param array $keys Array of keys under which the cache contents are stored. * @param string $group Optional. Where the cache contents are grouped. Default 'default'. * @param bool $force Optional. Whether to force an update of the local cache * from the persistent cache. Default false. * @return array Array of return values, grouped by key. Each value is either * the cache contents on success, or false on failure. */ public function get_multiple( $keys, $group = 'default', $force = false ) { $values = []; foreach ( $keys as $key ) { $values[ $key ] = $this->get( $key, $group, $force ); } return $values; } /** * Removes the contents of the cache key in the group. * * If the cache key does not exist in the group, then nothing will happen. * * @since 1.8 * @access public * * @param int|string $key What the contents in the cache are called. * @param string $group Optional. Where the cache contents are grouped. Default 'default'. * @return bool True on success, false if the contents were not deleted. */ public function delete( $key, $group = 'default' ) { if ( ! $this->is_valid_key( $key ) ) { return false; } if ( empty( $group ) ) { $group = 'default'; } $id = $this->_key( $key, $group ); if ( array_key_exists( $id, $this->_cache ) ) { unset( $this->_cache[ $id ] ); } if ( $this->_object_cache->is_non_persistent( $group ) ) { return false; } return $this->_object_cache->delete( $id ); } /** * Deletes multiple values from the cache in one call. * * @since 5.4 * @access public * * @param array $keys Array of keys to be deleted. * @param string $group Optional. Where the cache contents are grouped. Default empty. * @return bool[] Array of return values, grouped by key. Each value is either * true on success, or false if the contents were not deleted. */ public function delete_multiple( array $keys, $group = '' ) { $values = []; foreach ( $keys as $key ) { $values[ $key ] = $this->delete( $key, $group ); } return $values; } /** * Increments numeric cache item's value. * * @since 5.4 * @access public * * @param int|string $key The cache key to increment. * @param int $offset Optional. The amount by which to increment the item's value. * Default 1. * @param string $group Optional. The group the key is in. Default 'default'. * @return int|false The item's new value on success, false on failure. */ public function incr( $key, $offset = 1, $group = 'default' ) { return $this->incr_desr( $key, $offset, $group, true ); } /** * Decrements numeric cache item's value. * * @since 5.4 * @access public * * @param int|string $key The cache key to decrement. * @param int $offset Optional. The amount by which to decrement the item's value. * Default 1. * @param string $group Optional. The group the key is in. Default 'default'. * @return int|false The item's new value on success, false on failure. */ public function decr( $key, $offset = 1, $group = 'default' ) { return $this->incr_desr( $key, $offset, $group, false ); } /** * Increments or decrements numeric cache item's value. * * @since 1.8 * @access public * * @param int|string $key The cache key to increment or decrement. * @param int $offset The amount by which to adjust the item's value. * @param string $group Optional. The group the key is in. Default 'default'. * @param bool $incr True to increment, false to decrement. * @return int|false The item's new value on success, false on failure. */ public function incr_desr( $key, $offset = 1, $group = 'default', $incr = true ) { if ( ! $this->is_valid_key( $key ) ) { return false; } if ( empty( $group ) ) { $group = 'default'; } $cache_val = $this->get( $key, $group ); if ( false === $cache_val ) { return false; } if ( ! is_numeric( $cache_val ) ) { $cache_val = 0; } $offset = (int) $offset; if ( $incr ) { $cache_val += $offset; } else { $cache_val -= $offset; } if ( $cache_val < 0 ) { $cache_val = 0; } $this->set( $key, $cache_val, $group ); return $cache_val; } /** * Clears the object cache of all data. * * @since 1.8 * @access public * * @return true Always returns true. */ public function flush() { $this->flush_runtime(); $this->_object_cache->flush(); return true; } /** * Removes all cache items from the in-memory runtime cache. * * @since 5.4 * @access public * * @return true Always returns true. */ public function flush_runtime() { $this->_cache = []; $this->_cache_404 = []; return true; } /** * Removes all cache items in a group. * * @since 5.4 * @access public * * @return true Always returns true. */ public function flush_group() { return true; } /** * Sets the list of global cache groups. * * @since 1.8 * @access public * * @param string|string[] $groups List of groups that are global. */ public function add_global_groups( $groups ) { $groups = (array) $groups; $this->_object_cache->add_global_groups( $groups ); } /** * Sets the list of non-persistent cache groups. * * @since 1.8 * @access public * * @param string|string[] $groups A group or an array of groups to add. */ public function add_non_persistent_groups( $groups ) { $groups = (array) $groups; $this->_object_cache->add_non_persistent_groups( $groups ); } /** * Switches the internal blog ID. * * This changes the blog ID used to create keys in blog specific groups. * * @since 1.8 * @access public * * @param int $blog_id Blog ID. */ public function switch_to_blog( $blog_id ) { $blog_id = (int) $blog_id; $this->blog_prefix = $this->multisite ? $blog_id . ':' : ''; } /** * Get the current instance object. * * @since 1.8 * @access public * * @return WP_Object_Cache The current instance. */ public static function get_instance() { if ( ! isset( self::$_instance ) ) { self::$_instance = new self(); } return self::$_instance; } } src/router.cls.php000064400000051410152075713330010153 0ustar00 $i )); $url = html_entity_decode($link); exit(""); } /** * Check if can run optimize * * @since 1.3 * @since 2.3.1 Relocated from cdn.cls * @access public */ public function can_optm() { $can = true; if (is_user_logged_in() && $this->conf(self::O_OPTM_GUEST_ONLY)) { $can = false; } elseif (is_admin()) { $can = false; } elseif (is_feed()) { $can = false; } elseif (is_preview()) { $can = false; } elseif (self::is_ajax()) { $can = false; } if (self::_is_login_page()) { Debug2::debug('[Router] Optm bypassed: login/reg page'); $can = false; } $can_final = apply_filters('litespeed_can_optm', $can); if ($can_final != $can) { Debug2::debug('[Router] Optm bypassed: filter'); } return $can_final; } /** * Check referer page to see if its from admin * * @since 2.4.2.1 * @access public */ public static function from_admin() { return !empty($_SERVER['HTTP_REFERER']) && strpos($_SERVER['HTTP_REFERER'], get_admin_url()) === 0; } /** * Check if it can use CDN replacement * * @since 1.2.3 * @since 2.3.1 Relocated from cdn.cls * @access public */ public static function can_cdn() { $can = true; if (is_admin()) { if (!self::is_ajax()) { Debug2::debug2('[Router] CDN bypassed: is not ajax call'); $can = false; } if (self::from_admin()) { Debug2::debug2('[Router] CDN bypassed: ajax call from admin'); $can = false; } } elseif (is_feed()) { $can = false; } elseif (is_preview()) { $can = false; } /** * Bypass cron to avoid deregister jq notice `Do not deregister the jquery-core script in the administration area.` * * @since 2.7.2 */ if (wp_doing_cron()) { $can = false; } /** * Bypass login/reg page * * @since 1.6 */ if (self::_is_login_page()) { Debug2::debug('[Router] CDN bypassed: login/reg page'); $can = false; } /** * Bypass post/page link setting * * @since 2.9.8.5 */ $rest_prefix = function_exists('rest_get_url_prefix') ? rest_get_url_prefix() : apply_filters('rest_url_prefix', 'wp-json'); if ( !empty($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], $rest_prefix . '/wp/v2/media') !== false && isset($_SERVER['HTTP_REFERER']) && strpos($_SERVER['HTTP_REFERER'], 'wp-admin') !== false ) { Debug2::debug('[Router] CDN bypassed: wp-json on admin page'); $can = false; } $can_final = apply_filters('litespeed_can_cdn', $can); if ($can_final != $can) { Debug2::debug('[Router] CDN bypassed: filter'); } return $can_final; } /** * Check if is login page or not * * @since 2.3.1 * @access protected */ protected static function _is_login_page() { if (in_array($GLOBALS['pagenow'], array( 'wp-login.php', 'wp-register.php' ), true)) { return true; } return false; } /** * UCSS/Crawler role simulator * * @since 1.9.1 * @since 3.3 Renamed from `is_crawler_role_simulation` */ public function is_role_simulation() { if (is_admin()) { return; } if (empty($_COOKIE['litespeed_hash']) && empty($_COOKIE['litespeed_flash_hash'])) { return; } self::debug('🪪 starting role validation'); // Check if is from crawler // if ( empty( $_SERVER[ 'HTTP_USER_AGENT' ] ) || strpos( $_SERVER[ 'HTTP_USER_AGENT' ], Crawler::FAST_USER_AGENT ) !== 0 ) { // Debug2::debug( '[Router] user agent not match' ); // return; // } $server_ip = $this->conf(self::O_SERVER_IP); if (!$server_ip || self::get_ip() !== $server_ip) { self::debug('❌❌ Role simulate uid denied! Not localhost visit!'); Control::set_nocache('Role simulate uid denied'); return; } // Flash hash validation if (!empty($_COOKIE['litespeed_flash_hash'])) { $hash_data = self::get_option(self::ITEM_FLASH_HASH, array()); if ($hash_data && is_array($hash_data) && !empty($hash_data['hash']) && !empty($hash_data['ts']) && !empty($hash_data['uid'])) { if (time() - $hash_data['ts'] < 120 && $_COOKIE['litespeed_flash_hash'] == $hash_data['hash']) { self::debug('🪪 Role simulator flash hash matched, escalating user to be uid=' . $hash_data['uid']); self::delete_option(self::ITEM_FLASH_HASH); wp_set_current_user($hash_data['uid']); return; } } } // Hash validation if (!empty($_COOKIE['litespeed_hash'])) { $hash_data = self::get_option(self::ITEM_HASH, array()); if ($hash_data && is_array($hash_data) && !empty($hash_data['hash']) && !empty($hash_data['ts']) && !empty($hash_data['uid'])) { $RUN_DURATION = $this->cls('Crawler')->get_crawler_duration(); if (time() - $hash_data['ts'] < $RUN_DURATION && $_COOKIE['litespeed_hash'] == $hash_data['hash']) { self::debug('🪪 Role simulator hash matched, escalating user to be uid=' . $hash_data['uid']); wp_set_current_user($hash_data['uid']); return; } } } self::debug('❌ WARNING: role simulator hash not match'); } /** * Get a short ttl hash (2mins) * * @since 6.4 */ public function get_flash_hash( $uid ) { $hash_data = self::get_option(self::ITEM_FLASH_HASH, array()); if ($hash_data && is_array($hash_data) && !empty($hash_data['hash']) && !empty($hash_data['ts'])) { if (time() - $hash_data['ts'] < 60) { return $hash_data['hash']; } } // Check if this user has editor access or not if (user_can($uid, 'edit_posts')) { self::debug('🛑 The user with id ' . $uid . ' has editor access, which is not allowed for the role simulator.'); return ''; } $hash = Str::rrand(32); self::update_option(self::ITEM_FLASH_HASH, array( 'hash' => $hash, 'ts' => time(), 'uid' => $uid, )); return $hash; } /** * Get a security hash * * @since 3.3 */ public function get_hash( $uid ) { // Check if this user has editor access or not if (user_can($uid, 'edit_posts')) { self::debug('🛑 The user with id ' . $uid . ' has editor access, which is not allowed for the role simulator.'); return ''; } // As this is called only when starting crawling, not per page, no need to reuse $hash = Str::rrand(32); self::update_option(self::ITEM_HASH, array( 'hash' => $hash, 'ts' => time(), 'uid' => $uid, )); return $hash; } /** * Get user role * * @since 1.6.2 */ public static function get_role( $uid = null ) { if (defined('LITESPEED_WP_ROLE')) { return LITESPEED_WP_ROLE; } if ($uid === null) { $uid = get_current_user_id(); } $role = false; if ($uid) { $user = get_userdata($uid); if (isset($user->roles) && is_array($user->roles)) { $tmp = array_values($user->roles); $role = implode(',', $tmp); // Combine for PHP5.3 const compatibility } } Debug2::debug('[Router] get_role: ' . $role); if (!$role) { return $role; // Guest user Debug2::debug('[Router] role: guest'); /** * Fix double login issue * The previous user init refactoring didn't fix this bcos this is in login process and the user role could change * * @see https://github.com/litespeedtech/lscache_wp/commit/69e7bc71d0de5cd58961bae953380b581abdc088 * @since 2.9.8 Won't assign const if in login process */ if (substr_compare(wp_login_url(), $GLOBALS['pagenow'], -strlen($GLOBALS['pagenow'])) === 0) { return $role; } } define('LITESPEED_WP_ROLE', $role); return LITESPEED_WP_ROLE; } /** * Get frontend path * * @since 1.2.2 * @access public * @return boolean */ public static function frontend_path() { // todo: move to htaccess.cls ? if (!isset(self::$_frontend_path)) { $frontend = rtrim(ABSPATH, '/'); // /home/user/public_html/frontend // get home path failed. Trac ticket #37668 (e.g. frontend:/blog backend:/wordpress) if (!$frontend) { Debug2::debug('[Router] No ABSPATH, generating from home option'); $frontend = parse_url(get_option('home')); $frontend = !empty($frontend['path']) ? $frontend['path'] : ''; $frontend = $_SERVER['DOCUMENT_ROOT'] . $frontend; } $frontend = realpath($frontend); self::$_frontend_path = $frontend; } return self::$_frontend_path; } /** * Check if ESI is enabled or not * * @since 1.2.0 * @access public * @return boolean */ public function esi_enabled() { if (!isset(self::$_esi_enabled)) { self::$_esi_enabled = defined('LITESPEED_ON') && $this->conf(self::O_ESI); if (!empty($_REQUEST[self::ACTION])) { self::$_esi_enabled = false; } } return self::$_esi_enabled; } /** * Check if crawler is enabled on server level * * @since 1.1.1 * @access public */ public static function can_crawl() { if (isset($_SERVER['X-LSCACHE']) && strpos($_SERVER['X-LSCACHE'], 'crawler') === false) { return false; } // CLI will bypass this check as crawler library can always do the 428 check if (defined('LITESPEED_CLI')) { return true; } return true; } /** * Check action * * @since 1.1.0 * @access public * @return string */ public static function get_action() { if (!isset(self::$_action)) { self::$_action = false; self::cls()->verify_action(); if (self::$_action) { defined('LSCWP_LOG') && Debug2::debug('[Router] LSCWP_CTRL verified: ' . var_export(self::$_action, true)); } } return self::$_action; } /** * Check if is logged in * * @since 1.1.3 * @access public * @return boolean */ public static function is_logged_in() { if (!isset(self::$_is_logged_in)) { self::$_is_logged_in = is_user_logged_in(); } return self::$_is_logged_in; } /** * Check if is ajax call * * @since 1.1.0 * @access public * @return boolean */ public static function is_ajax() { if (!isset(self::$_is_ajax)) { self::$_is_ajax = wp_doing_ajax(); } return self::$_is_ajax; } /** * Check if is admin ip * * @since 1.1.0 * @access public * @return boolean */ public function is_admin_ip() { if (!isset(self::$_is_admin_ip)) { $ips = $this->conf(self::O_DEBUG_IPS); self::$_is_admin_ip = $this->ip_access($ips); } return self::$_is_admin_ip; } /** * Get type value * * @since 1.6 * @access public */ public static function verify_type() { if (empty($_REQUEST[self::TYPE])) { Debug2::debug('[Router] no type', 2); return false; } Debug2::debug('[Router] parsed type: ' . $_REQUEST[self::TYPE], 2); return $_REQUEST[self::TYPE]; } /** * Check privilege and nonce for the action * * @since 1.1.0 * @access private */ private function verify_action() { if (empty($_REQUEST[self::ACTION])) { Debug2::debug2('[Router] LSCWP_CTRL bypassed empty'); return; } $action = stripslashes($_REQUEST[self::ACTION]); if (!$action) { return; } $_is_public_action = false; // Each action must have a valid nonce unless its from admin ip and is public action // Validate requests nonce (from admin logged in page or cli) if (!$this->verify_nonce($action)) { // check if it is from admin ip if (!$this->is_admin_ip()) { Debug2::debug('[Router] LSCWP_CTRL query string - did not match admin IP: ' . $action); return; } // check if it is public action if ( !in_array($action, array( Core::ACTION_QS_NOCACHE, Core::ACTION_QS_PURGE, Core::ACTION_QS_PURGE_SINGLE, Core::ACTION_QS_SHOW_HEADERS, Core::ACTION_QS_PURGE_ALL, Core::ACTION_QS_PURGE_EMPTYCACHE, )) ) { Debug2::debug('[Router] LSCWP_CTRL query string - did not match admin IP Actions: ' . $action); return; } if (apply_filters('litespeed_qs_forbidden', false)) { Debug2::debug('[Router] LSCWP_CTRL forbidden by hook litespeed_qs_forbidden'); return; } $_is_public_action = true; } /* Now it is a valid action, lets log and check the permission */ Debug2::debug('[Router] LSCWP_CTRL: ' . $action); // OK, as we want to do something magic, lets check if its allowed $_is_multisite = is_multisite(); $_is_network_admin = $_is_multisite && is_network_admin(); $_can_network_option = $_is_network_admin && current_user_can('manage_network_options'); $_can_option = current_user_can('manage_options'); switch ($action) { case self::ACTION_TMP_DISABLE: // Disable LSC for 24H Debug2::tmp_disable(); Admin::redirect("?page=litespeed-toolbox#settings-debug"); return; case self::ACTION_SAVE_SETTINGS_NETWORK: // Save network settings if ($_can_network_option) { self::$_action = $action; } return; case Core::ACTION_PURGE_BY: if (defined('LITESPEED_ON') && ($_can_network_option || $_can_option || self::is_ajax())) { // here may need more security self::$_action = $action; } return; case self::ACTION_DB_OPTM: if ($_can_network_option || $_can_option) { self::$_action = $action; } return; case Core::ACTION_PURGE_EMPTYCACHE: // todo: moved to purge.cls type action if ((defined('LITESPEED_ON') || $_is_network_admin) && ($_can_network_option || (!$_is_multisite && $_can_option))) { self::$_action = $action; } return; case Core::ACTION_QS_NOCACHE: case Core::ACTION_QS_PURGE: case Core::ACTION_QS_PURGE_SINGLE: case Core::ACTION_QS_SHOW_HEADERS: case Core::ACTION_QS_PURGE_ALL: case Core::ACTION_QS_PURGE_EMPTYCACHE: if (defined('LITESPEED_ON') && ($_is_public_action || self::is_ajax())) { self::$_action = $action; } return; case self::ACTION_ADMIN_DISPLAY: case self::ACTION_PLACEHOLDER: case self::ACTION_AVATAR: case self::ACTION_IMG_OPTM: case self::ACTION_MEDIA: case self::ACTION_CLOUD: case self::ACTION_CDN_CLOUDFLARE: case self::ACTION_CRAWLER: case self::ACTION_GUEST: case self::ACTION_PRESET: case self::ACTION_IMPORT: case self::ACTION_REPORT: case self::ACTION_CSS: case self::ACTION_UCSS: case self::ACTION_VPI: case self::ACTION_CONF: case self::ACTION_ACTIVATION: case self::ACTION_HEALTH: case self::ACTION_SAVE_SETTINGS: // Save settings if ($_can_option && !$_is_network_admin) { self::$_action = $action; } return; case self::ACTION_PURGE: case self::ACTION_DEBUG2: if ($_can_network_option || $_can_option) { self::$_action = $action; } return; case Core::ACTION_DISMISS: /** * Non ajax call can dismiss too * * @since 2.9 */ // if ( self::is_ajax() ) { self::$_action = $action; // } return; default: Debug2::debug('[Router] LSCWP_CTRL match failed: ' . $action); return; } } /** * Verify nonce * * @since 1.1.0 * @access public * @param string $action * @return bool */ public function verify_nonce( $action ) { if (!isset($_REQUEST[self::NONCE]) || !wp_verify_nonce($_REQUEST[self::NONCE], $action)) { return false; } else { return true; } } /** * Check if the ip is in the range * * @since 1.1.0 * @access public */ public function ip_access( $ip_list ) { if (!$ip_list) { return false; } if (!isset(self::$_ip)) { self::$_ip = self::get_ip(); } if (!self::$_ip) { return false; } // $uip = explode('.', $_ip); // if(empty($uip) || count($uip) != 4) Return false; // foreach($ip_list as $key => $ip) $ip_list[$key] = explode('.', trim($ip)); // foreach($ip_list as $key => $ip) { // if(count($ip) != 4) continue; // for($i = 0; $i <= 3; $i++) if($ip[$i] == '*') $ip_list[$key][$i] = $uip[$i]; // } return in_array(self::$_ip, $ip_list); } /** * Get client ip * * @since 1.1.0 * @since 1.6.5 changed to public * @access public * @return string */ public static function get_ip() { $_ip = ''; // if ( function_exists( 'apache_request_headers' ) ) { // $apache_headers = apache_request_headers(); // $_ip = ! empty( $apache_headers['True-Client-IP'] ) ? $apache_headers['True-Client-IP'] : false; // if ( ! $_ip ) { // $_ip = ! empty( $apache_headers['X-Forwarded-For'] ) ? $apache_headers['X-Forwarded-For'] : false; // $_ip = explode( ',', $_ip ); // $_ip = $_ip[ 0 ]; // } // } if (!$_ip) { $_ip = !empty($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : false; } return $_ip; } /** * Check if opcode cache is enabled * * @since 1.8.2 * @access public */ public static function opcache_enabled() { return function_exists('opcache_reset') && ini_get('opcache.enable'); } /** * Check if opcode cache is restricted and file that is requesting. * https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.restrict-api * * @since 7.3 * @access public */ public static function opcache_restricted($file) { $restrict_value = ini_get('opcache.restrict_api'); if ($restrict_value) { if ( !$file || false === strpos($restrict_value, $file) ) { return true; } } return false; } /** * Handle static files * * @since 3.0 */ public function serve_static() { if (!empty($_SERVER['SCRIPT_URI'])) { if (strpos($_SERVER['SCRIPT_URI'], LITESPEED_STATIC_URL . '/') !== 0) { return; } $path = substr($_SERVER['SCRIPT_URI'], strlen(LITESPEED_STATIC_URL . '/')); } elseif (!empty($_SERVER['REQUEST_URI'])) { $static_path = parse_url(LITESPEED_STATIC_URL, PHP_URL_PATH) . '/'; if (strpos($_SERVER['REQUEST_URI'], $static_path) !== 0) { return; } $path = substr(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), strlen($static_path)); } else { return; } $path = explode('/', $path, 2); if (empty($path[0]) || empty($path[1])) { return; } switch ($path[0]) { case 'avatar': $this->cls('Avatar')->serve_static($path[1]); break; case 'localres': $this->cls('Localization')->serve_static($path[1]); break; default: break; } } /** * Handle all request actions from main cls * * This is different than other handlers * * @since 3.0 * @access public */ public function handler( $cls ) { if (!in_array($cls, self::$_HANDLERS)) { return; } return $this->cls($cls)->handler(); } } src/report.cls.php000064400000014172152075713330010152 0ustar00post_env(); break; default: break; } Admin::redirect(); } /** * post env report number to ls center server * * @since 1.6.5 * @access public */ public function post_env() { $report_con = $this->generate_environment_report(); // Generate link $link = !empty($_POST['link']) ? esc_url($_POST['link']) : ''; $notes = !empty($_POST['notes']) ? esc_html($_POST['notes']) : ''; $php_info = !empty($_POST['attach_php']) ? esc_html($_POST['attach_php']) : ''; $report_php = $php_info === '1' ? $this->generate_php_report() : ''; if ($report_php) { $report_con .= "\nPHPINFO\n" . $report_php; } $data = array( 'env' => $report_con, 'link' => $link, 'notes' => $notes, ); $json = Cloud::post(Cloud::API_REPORT, $data); if (!is_array($json)) { return; } $num = !empty($json['num']) ? $json['num'] : '--'; $summary = array( 'num' => $num, 'dateline' => time(), ); self::save_summary($summary); return $num; } /** * Gathers the PHP information. * * @since 7.0 * @access public */ public function generate_php_report( $flags = INFO_GENERAL | INFO_CONFIGURATION | INFO_MODULES ) { // INFO_ENVIRONMENT $report = ''; ob_start(); phpinfo($flags); $report = ob_get_contents(); ob_end_clean(); preg_match('%.*?(.*?)%s', $report, $report); return $report[2]; } /** * Gathers the environment details and creates the report. * Will write to the environment report file. * * @since 1.0.12 * @access public */ public function generate_environment_report( $options = null ) { global $wp_version, $_SERVER; $frontend_htaccess = Htaccess::get_frontend_htaccess(); $backend_htaccess = Htaccess::get_backend_htaccess(); $paths = array( $frontend_htaccess ); if ($frontend_htaccess != $backend_htaccess) { $paths[] = $backend_htaccess; } if (is_multisite()) { $active_plugins = get_site_option('active_sitewide_plugins'); if (!empty($active_plugins)) { $active_plugins = array_keys($active_plugins); } } else { $active_plugins = get_option('active_plugins'); } if (function_exists('wp_get_theme')) { $theme_obj = wp_get_theme(); $active_theme = $theme_obj->get('Name'); } else { $active_theme = get_current_theme(); } $extras = array( 'wordpress version' => $wp_version, 'siteurl' => get_option('siteurl'), 'home' => get_option('home'), 'home_url' => home_url(), 'locale' => get_locale(), 'active theme' => $active_theme, ); $extras['active plugins'] = $active_plugins; $extras['cloud'] = Cloud::get_summary(); foreach (array( 'mini_html', 'pk_b64', 'sk_b64', 'cdn_dash', 'ips' ) as $v) { if (!empty($extras['cloud'][$v])) { unset($extras['cloud'][$v]); } } if (is_null($options)) { $options = $this->get_options(true); if (is_multisite()) { $options2 = $this->get_options(); foreach ($options2 as $k => $v) { if (isset($options[$k]) && $options[$k] !== $v) { $options['[Overwritten] ' . $k] = $v; } } } } if (!is_null($options) && is_multisite()) { $blogs = Activation::get_network_ids(); if (!empty($blogs)) { $i = 0; foreach ($blogs as $blog_id) { if (++$i > 3) { // Only log 3 subsites break; } $opts = $this->cls('Conf')->load_options($blog_id, true); if (isset($opts[self::O_CACHE])) { $options['blog ' . $blog_id . ' radio select'] = $opts[self::O_CACHE]; } } } } // Security: Remove cf key in report $secure_fields = array( self::O_CDN_CLOUDFLARE_KEY, self::O_OBJECT_PSWD ); foreach ($secure_fields as $v) { if (!empty($options[$v])) { $options[$v] = str_repeat('*', strlen($options[$v])); } } $report = $this->build_environment_report($_SERVER, $options, $extras, $paths); return $report; } /** * Builds the environment report buffer with the given parameters * * @access private */ private function build_environment_report( $server, $options, $extras = array(), $htaccess_paths = array() ) { $server_keys = array( 'DOCUMENT_ROOT' => '', 'SERVER_SOFTWARE' => '', 'X-LSCACHE' => '', 'HTTP_X_LSCACHE' => '', ); $server_vars = array_intersect_key($server, $server_keys); $server_vars[] = 'LSWCP_TAG_PREFIX = ' . LSWCP_TAG_PREFIX; $server_vars = array_merge($server_vars, $this->cls('Base')->server_vars()); $buf = $this->_format_report_section('Server Variables', $server_vars); $buf .= $this->_format_report_section('WordPress Specific Extras', $extras); $buf .= $this->_format_report_section('LSCache Plugin Options', $options); if (empty($htaccess_paths)) { return $buf; } foreach ($htaccess_paths as $path) { if (!file_exists($path) || !is_readable($path)) { $buf .= $path . " does not exist or is not readable.\n"; continue; } $content = file_get_contents($path); if ($content === false) { $buf .= $path . " returned false for file_get_contents.\n"; continue; } $buf .= $path . " contents:\n" . $content . "\n\n"; } return $buf; } /** * Creates a part of the environment report based on a section header and an array for the section parameters. * * @since 1.0.12 * @access private */ private function _format_report_section( $section_header, $section ) { $tab = ' '; // four spaces if (empty($section)) { return 'No matching ' . $section_header . "\n\n"; } $buf = $section_header; foreach ($section as $k => $v) { $buf .= "\n" . $tab; if (!is_numeric($k)) { $buf .= $k . ' = '; } if (!is_string($v)) { $v = var_export($v, true); } else { $v = esc_html($v); } $buf .= $v; } return $buf . "\n\n"; } } src/import.preset.cls.php000064400000013001152075713330011440 0ustar00dirlist(self::BACKUP_DIR) ?: array() ); rsort($backups); return $backups; } /** * Removes extra backup files * * @since 5.3.0 * @access public */ public static function prune_backups() { $backups = self::get_backups(); global $wp_filesystem; foreach (array_slice($backups, self::MAX_BACKUPS) as $backup) { $path = self::get_backup($backup); $wp_filesystem->delete($path); Debug2::debug('[Preset] Deleted old backup from ' . $backup); } } /** * Returns a settings file's extensionless basename given its filesystem path * * @since 5.3.0 * @access public */ public static function basename( $path ) { return basename($path, '.data'); } /** * Returns a standard preset's path given its extensionless basename * * @since 5.3.0 * @access public */ public static function get_standard( $name ) { return path_join(self::STANDARD_DIR, $name . '.data'); } /** * Returns a backup's path given its extensionless basename * * @since 5.3.0 * @access public */ public static function get_backup( $name ) { return path_join(self::BACKUP_DIR, $name . '.data'); } /** * Initializes the global $wp_filesystem object and clears stat cache * * @since 5.3.0 */ static function init_filesystem() { require_once ABSPATH . '/wp-admin/includes/file.php'; \WP_Filesystem(); clearstatcache(); } /** * Init * * @since 5.3.0 */ public function __construct() { Debug2::debug('[Preset] Init'); $this->_summary = self::get_summary(); } /** * Applies a standard preset's settings given its extensionless basename * * @since 5.3.0 * @access public */ public function apply( $preset ) { $this->make_backup($preset); $path = self::get_standard($preset); $result = $this->import_file($path) ? $preset : 'error'; $this->log($result); } /** * Restores settings from the backup file with the given timestamp, then deletes the file * * @since 5.3.0 * @access public */ public function restore( $timestamp ) { $backups = array(); foreach (self::get_backups() as $backup) { if (preg_match('/^backup-' . $timestamp . '(-|$)/', $backup) === 1) { $backups[] = $backup; } } if (empty($backups)) { $this->log('error'); return; } $backup = $backups[0]; $path = self::get_backup($backup); if (!$this->import_file($path)) { $this->log('error'); return; } self::init_filesystem(); global $wp_filesystem; $wp_filesystem->delete($path); Debug2::debug('[Preset] Deleted most recent backup from ' . $backup); $this->log('backup'); } /** * Saves current settings as a backup file, then prunes extra backup files * * @since 5.3.0 * @access public */ public function make_backup( $preset ) { $backup = 'backup-' . time() . '-before-' . $preset; $data = $this->export(true); $path = self::get_backup($backup); File::save($path, $data, true); Debug2::debug('[Preset] Backup saved to ' . $backup); self::prune_backups(); } /** * Tries to import from a given settings file * * @since 5.3.0 */ function import_file( $path ) { $debug = function ( $result, $name ) { $action = $result ? 'Applied' : 'Failed to apply'; Debug2::debug('[Preset] ' . $action . ' settings from ' . $name); return $result; }; $name = self::basename($path); $contents = file_get_contents($path); if (false === $contents) { Debug2::debug('[Preset] ❌ Failed to get file contents'); return $debug(false, $name); } $parsed = array(); try { // Check if the data is v4+ if (strpos($contents, '["_version",') === 0) { $contents = explode("\n", $contents); foreach ($contents as $line) { $line = trim($line); if (empty($line)) { continue; } list($key, $value) = \json_decode($line, true); $parsed[$key] = $value; } } else { $parsed = \json_decode(base64_decode($contents), true); } } catch (\Exception $ex) { Debug2::debug('[Preset] ❌ Failed to parse serialized data'); return $debug(false, $name); } if (empty($parsed)) { Debug2::debug('[Preset] ❌ Nothing to apply'); return $debug(false, $name); } $this->cls('Conf')->update_confs($parsed); return $debug(true, $name); } /** * Updates the log * * @since 5.3.0 */ function log( $preset ) { $this->_summary['preset'] = $preset; $this->_summary['preset_timestamp'] = time(); self::save_summary(); } /** * Handles all request actions from main cls * * @since 5.3.0 * @access public */ public function handler() { $type = Router::verify_type(); switch ($type) { case self::TYPE_APPLY: $this->apply(!empty($_GET['preset']) ? $_GET['preset'] : false); break; case self::TYPE_RESTORE: $this->restore(!empty($_GET['timestamp']) ? $_GET['timestamp'] : false); break; default: break; } Admin::redirect(); } } src/cloud.cls.php000064400000016514152075713330007747 0ustar00 */ protected $_summary; /** * Init * * @since 3.0 */ public function __construct() { $allowed_hosts = [ 'wpapi.quic.cloud' ]; if ( defined( 'LITESPEED_DEV' ) && constant( 'LITESPEED_DEV' ) ) { $allowed_hosts[] = 'my.preview.quic.cloud'; $allowed_hosts[] = 'api.preview.quic.cloud'; $this->_cloud_server = 'https://api.preview.quic.cloud'; $this->_cloud_ips = 'https://api.preview.quic.cloud/ips'; $this->_cloud_server_dash = 'https://my.preview.quic.cloud'; $this->_cloud_server_wp = 'https://wpapi.quic.cloud'; } else { $allowed_hosts[] = 'my.quic.cloud'; $allowed_hosts[] = 'api.quic.cloud'; } add_filter( 'allowed_redirect_hosts', function( $hosts ) use ( $allowed_hosts ) { if ( ! is_array ( $hosts ) ) { $hosts = []; } return array_merge( $hosts, $allowed_hosts ); } ); $this->_summary = self::get_summary(); } /** * Return succeeded response * * @since 3.0 * * @param array $data Additional data. * @return array */ public static function ok( $data = [] ) { $data['_res'] = 'ok'; return $data; } /** * Return error * * @since 3.0 * * @param string $code Error code. * @return array */ public static function err( $code ) { self::debug( '❌ Error response code: ' . $code ); return [ '_res' => 'err', '_msg' => $code, ]; } /** * Handle all request actions from main cls * * @since 3.0 * @access public */ public function handler() { $type = Router::verify_type(); switch ( $type ) { case self::TYPE_CLEAR_CLOUD: $this->clear_cloud(); break; case self::TYPE_REDETECT_CLOUD: // phpcs:ignore WordPress.Security.NonceVerification.Recommended $svc = ! empty( $_GET['svc'] ) ? sanitize_text_field( wp_unslash( $_GET['svc'] ) ) : ''; if ( $svc ) { $this->detect_cloud( $svc, true ); } break; case self::TYPE_CLEAR_PROMO: $this->_clear_promo(); break; case self::TYPE_RESET: $this->reset_qc(); break; case self::TYPE_ACTIVATE: $this->init_qc(); break; case self::TYPE_LINK: $this->link_qc(); break; case self::TYPE_ENABLE_CDN: $this->enable_cdn(); break; case self::TYPE_API: // phpcs:ignore WordPress.Security.NonceVerification.Recommended $action2 = ! empty( $_GET['action2'] ) ? sanitize_text_field( wp_unslash( $_GET['action2'] ) ) : ''; if ( $action2 ) { $this->api_link_call( $action2 ); } break; case self::TYPE_SYNC_STATUS: $this->load_qc_status_for_dash( 'cdn_dash', true ); $msg = __( 'Sync QUIC.cloud status successfully.', 'litespeed-cache' ); Admin_Display::success( $msg ); break; case self::TYPE_SYNC_USAGE: $this->sync_usage(); $msg = __( 'Sync credit allowance with Cloud Server successfully.', 'litespeed-cache' ); Admin_Display::success( $msg ); break; default: break; } Admin::redirect(); } } src/health.cls.php000064400000005523152075713330010104 0ustar00_summary = self::get_summary(); } /** * Test latest speed * * @since 3.0 */ private function _ping( $type ) { $data = array( 'action' => $type ); $json = Cloud::post(Cloud::SVC_HEALTH, $data, 600); if (empty($json['data']['before']) || empty($json['data']['after'])) { Debug2::debug('[Health] ❌ no data'); return false; } $this->_summary[$type . '.before'] = $json['data']['before']; $this->_summary[$type . '.after'] = $json['data']['after']; self::save_summary(); Debug2::debug('[Health] saved result'); } /** * Generate scores * * @since 3.0 */ public function scores() { $speed_before = $speed_after = $speed_improved = 0; if (!empty($this->_summary['speed.before']) && !empty($this->_summary['speed.after'])) { // Format loading time $speed_before = $this->_summary['speed.before'] / 1000; if ($speed_before < 0.01) { $speed_before = 0.01; } $speed_before = number_format($speed_before, 2); $speed_after = $this->_summary['speed.after'] / 1000; if ($speed_after < 0.01) { $speed_after = number_format($speed_after, 3); } else { $speed_after = number_format($speed_after, 2); } $speed_improved = (($this->_summary['speed.before'] - $this->_summary['speed.after']) * 100) / $this->_summary['speed.before']; if ($speed_improved > 99) { $speed_improved = number_format($speed_improved, 2); } else { $speed_improved = number_format($speed_improved); } } $score_before = $score_after = $score_improved = 0; if (!empty($this->_summary['score.before']) && !empty($this->_summary['score.after'])) { $score_before = $this->_summary['score.before']; $score_after = $this->_summary['score.after']; // Format Score $score_improved = (($score_after - $score_before) * 100) / $score_after; if ($score_improved > 99) { $score_improved = number_format($score_improved, 2); } else { $score_improved = number_format($score_improved); } } return array( 'speed_before' => $speed_before, 'speed_after' => $speed_after, 'speed_improved' => $speed_improved, 'score_before' => $score_before, 'score_after' => $score_after, 'score_improved' => $score_improved, ); } /** * Handle all request actions from main cls * * @since 3.0 * @access public */ public function handler() { $type = Router::verify_type(); switch ($type) { case self::TYPE_SPEED: case self::TYPE_SCORE: $this->_ping($type); break; default: break; } Admin::redirect(); } } src/lang.cls.php000064400000042026152075713330007557 0ustar00|string Array map when $status is null, otherwise a single status label or 'N/A'. */ public static function img_status( $status = null ) { $list = [ Img_Optm::STATUS_NEW => __( 'Images not requested', 'litespeed-cache' ), Img_Optm::STATUS_RAW => __( 'Images ready to request', 'litespeed-cache' ), Img_Optm::STATUS_REQUESTED => __( 'Images requested', 'litespeed-cache' ), Img_Optm::STATUS_NOTIFIED => __( 'Images notified to pull', 'litespeed-cache' ), Img_Optm::STATUS_PULLED => __( 'Images optimized and pulled', 'litespeed-cache' ), ]; if ( null !== $status ) { return ! empty( $list[ $status ] ) ? $list[ $status ] : 'N/A'; } return $list; } /** * Try translating a string. * * Optionally supports sprintf-style substitutions when $raw_string * contains a '::' separator (e.g. 'key::arg1::arg2'). * * @since 4.7 * * @param string $raw_string Raw translation key or key with ::-separated args. * @return string Translated string or original raw string if not found. */ public static function maybe_translate( $raw_string ) { $map = [ 'auto_alias_failed_cdn' => __( 'Unable to automatically add %1$s as a Domain Alias for main %2$s domain, due to potential CDN conflict.', 'litespeed-cache' ) . ' ' . Doc::learn_more( 'https://quic.cloud/docs/cdn/dns/how-to-setup-domain-alias/', false, false, false, true ), 'auto_alias_failed_uid' => __( 'Unable to automatically add %1$s as a Domain Alias for main %2$s domain.', 'litespeed-cache' ) . ' ' . __( 'Alias is in use by another QUIC.cloud account.', 'litespeed-cache' ) . ' ' . Doc::learn_more( 'https://quic.cloud/docs/cdn/dns/how-to-setup-domain-alias/', false, false, false, true ), ]; // Maybe has placeholder. if ( strpos( $raw_string, '::' ) ) { $replacements = explode( '::', $raw_string ); if ( empty( $map[ $replacements[0] ] ) ) { return $raw_string; } $tpl = $map[ $replacements[0] ]; unset( $replacements[0] ); return vsprintf( $tpl, array_values( $replacements ) ); } // Direct translation only. if ( empty( $map[ $raw_string ] ) ) { return $raw_string; } return $map[ $raw_string ]; } /** * Get the title/label for an option ID. * * @since 3.0 * @access public * * @param string|int $id Option identifier constant. * @return string Human-readable title or 'N/A' if not found. */ public static function title( $id ) { $_lang_list = [ self::O_SERVER_IP => __( 'Server IP', 'litespeed-cache' ), self::O_CACHE => __( 'Enable Cache', 'litespeed-cache' ), self::O_CACHE_BROWSER => __( 'Browser Cache', 'litespeed-cache' ), self::O_CACHE_TTL_PUB => __( 'Default Public Cache TTL', 'litespeed-cache' ), self::O_CACHE_TTL_PRIV => __( 'Default Private Cache TTL', 'litespeed-cache' ), self::O_CACHE_TTL_FRONTPAGE => __( 'Default Front Page TTL', 'litespeed-cache' ), self::O_CACHE_TTL_FEED => __( 'Default Feed TTL', 'litespeed-cache' ), self::O_CACHE_TTL_REST => __( 'Default REST TTL', 'litespeed-cache' ), self::O_CACHE_TTL_STATUS => __( 'Default HTTP Status Code Page TTL', 'litespeed-cache' ), self::O_CACHE_TTL_BROWSER => __( 'Browser Cache TTL', 'litespeed-cache' ), self::O_CACHE_AJAX_TTL => __( 'AJAX Cache TTL', 'litespeed-cache' ), self::O_AUTO_UPGRADE => __( 'Automatically Upgrade', 'litespeed-cache' ), self::O_GUEST => __( 'Guest Mode', 'litespeed-cache' ), self::O_GUEST_OPTM => __( 'Guest Optimization', 'litespeed-cache' ), self::O_NEWS => __( 'Notifications', 'litespeed-cache' ), self::O_CACHE_PRIV => __( 'Cache Logged-in Users', 'litespeed-cache' ), self::O_CACHE_COMMENTER => __( 'Cache Commenters', 'litespeed-cache' ), self::O_CACHE_REST => __( 'Cache REST API', 'litespeed-cache' ), self::O_CACHE_PAGE_LOGIN => __( 'Cache Login Page', 'litespeed-cache' ), self::O_CACHE_MOBILE => __( 'Cache Mobile', 'litespeed-cache' ), self::O_CACHE_MOBILE_RULES => __( 'List of Mobile User Agents', 'litespeed-cache' ), self::O_CACHE_PRIV_URI => __( 'Private Cached URIs', 'litespeed-cache' ), self::O_CACHE_DROP_QS => __( 'Drop Query String', 'litespeed-cache' ), self::O_OBJECT => __( 'Object Cache', 'litespeed-cache' ), self::O_OBJECT_KIND => __( 'Method', 'litespeed-cache' ), self::O_OBJECT_HOST => __( 'Host', 'litespeed-cache' ), self::O_OBJECT_PORT => __( 'Port', 'litespeed-cache' ), self::O_OBJECT_LIFE => __( 'Default Object Lifetime', 'litespeed-cache' ), self::O_OBJECT_USER => __( 'Username', 'litespeed-cache' ), self::O_OBJECT_PSWD => __( 'Password', 'litespeed-cache' ), self::O_OBJECT_DB_ID => __( 'Redis Database ID', 'litespeed-cache' ), self::O_OBJECT_GLOBAL_GROUPS => __( 'Global Groups', 'litespeed-cache' ), self::O_OBJECT_NON_PERSISTENT_GROUPS => __( 'Do Not Cache Groups', 'litespeed-cache' ), self::O_OBJECT_PERSISTENT => __( 'Persistent Connection', 'litespeed-cache' ), self::O_OBJECT_ADMIN => __( 'Cache WP-Admin', 'litespeed-cache' ), self::O_PURGE_ON_UPGRADE => __( 'Purge All On Upgrade', 'litespeed-cache' ), self::O_PURGE_STALE => __( 'Serve Stale', 'litespeed-cache' ), self::O_PURGE_TIMED_URLS => __( 'Scheduled Purge URLs', 'litespeed-cache' ), self::O_PURGE_TIMED_URLS_TIME => __( 'Scheduled Purge Time', 'litespeed-cache' ), self::O_CACHE_FORCE_URI => __( 'Force Cache URIs', 'litespeed-cache' ), self::O_CACHE_FORCE_PUB_URI => __( 'Force Public Cache URIs', 'litespeed-cache' ), self::O_CACHE_EXC => __( 'Do Not Cache URIs', 'litespeed-cache' ), self::O_CACHE_EXC_QS => __( 'Do Not Cache Query Strings', 'litespeed-cache' ), self::O_CACHE_EXC_CAT => __( 'Do Not Cache Categories', 'litespeed-cache' ), self::O_CACHE_EXC_TAG => __( 'Do Not Cache Tags', 'litespeed-cache' ), self::O_CACHE_EXC_ROLES => __( 'Do Not Cache Roles', 'litespeed-cache' ), self::O_OPTM_CSS_MIN => __( 'CSS Minify', 'litespeed-cache' ), self::O_OPTM_CSS_COMB => __( 'CSS Combine', 'litespeed-cache' ), self::O_OPTM_CSS_COMB_EXT_INL => __( 'CSS Combine External and Inline', 'litespeed-cache' ), self::O_OPTM_UCSS => __( 'Generate UCSS', 'litespeed-cache' ), self::O_OPTM_UCSS_INLINE => __( 'UCSS Inline', 'litespeed-cache' ), self::O_OPTM_UCSS_SELECTOR_WHITELIST => __( 'UCSS Selector Allowlist', 'litespeed-cache' ), self::O_OPTM_UCSS_FILE_EXC_INLINE => __( 'UCSS Inline Excluded Files', 'litespeed-cache' ), self::O_OPTM_UCSS_EXC => __( 'UCSS URI Excludes', 'litespeed-cache' ), self::O_OPTM_JS_MIN => __( 'JS Minify', 'litespeed-cache' ), self::O_OPTM_JS_COMB => __( 'JS Combine', 'litespeed-cache' ), self::O_OPTM_JS_COMB_EXT_INL => __( 'JS Combine External and Inline', 'litespeed-cache' ), self::O_OPTM_HTML_MIN => __( 'HTML Minify', 'litespeed-cache' ), self::O_OPTM_HTML_LAZY => __( 'HTML Lazy Load Selectors', 'litespeed-cache' ), self::O_OPTM_HTML_SKIP_COMMENTS => __( 'HTML Keep Comments', 'litespeed-cache' ), self::O_OPTM_CSS_ASYNC => __( 'Load CSS Asynchronously', 'litespeed-cache' ), self::O_OPTM_CCSS_PER_URL => __( 'CCSS Per URL', 'litespeed-cache' ), self::O_OPTM_CSS_ASYNC_INLINE => __( 'Inline CSS Async Lib', 'litespeed-cache' ), self::O_OPTM_CSS_FONT_DISPLAY => __( 'Font Display Optimization', 'litespeed-cache' ), self::O_OPTM_JS_DEFER => __( 'Load JS Deferred', 'litespeed-cache' ), self::O_OPTM_LOCALIZE => __( 'Localize Resources', 'litespeed-cache' ), self::O_OPTM_LOCALIZE_DOMAINS => __( 'Localization Files', 'litespeed-cache' ), self::O_OPTM_DNS_PREFETCH => __( 'DNS Prefetch', 'litespeed-cache' ), self::O_OPTM_DNS_PREFETCH_CTRL => __( 'DNS Prefetch Control', 'litespeed-cache' ), self::O_OPTM_DNS_PRECONNECT => __( 'DNS Preconnect', 'litespeed-cache' ), self::O_OPTM_CSS_EXC => __( 'CSS Excludes', 'litespeed-cache' ), self::O_OPTM_JS_DELAY_INC => __( 'JS Delayed Includes', 'litespeed-cache' ), self::O_OPTM_JS_EXC => __( 'JS Excludes', 'litespeed-cache' ), self::O_OPTM_QS_RM => __( 'Remove Query Strings', 'litespeed-cache' ), self::O_OPTM_GGFONTS_ASYNC => __( 'Load Google Fonts Asynchronously', 'litespeed-cache' ), self::O_OPTM_GGFONTS_RM => __( 'Remove Google Fonts', 'litespeed-cache' ), self::O_OPTM_CCSS_CON => __( 'Critical CSS Rules', 'litespeed-cache' ), self::O_OPTM_CCSS_SEP_POSTTYPE => __( 'Separate CCSS Cache Post Types', 'litespeed-cache' ), self::O_OPTM_CCSS_SEP_URI => __( 'Separate CCSS Cache URIs', 'litespeed-cache' ), self::O_OPTM_CCSS_SELECTOR_WHITELIST => __( 'CCSS Selector Allowlist', 'litespeed-cache' ), self::O_OPTM_JS_DEFER_EXC => __( 'JS Deferred / Delayed Excludes', 'litespeed-cache' ), self::O_OPTM_GM_JS_EXC => __( 'Guest Mode JS Excludes', 'litespeed-cache' ), self::O_OPTM_EMOJI_RM => __( 'Remove WordPress Emoji', 'litespeed-cache' ), self::O_OPTM_NOSCRIPT_RM => __( 'Remove Noscript Tags', 'litespeed-cache' ), self::O_OPTM_EXC => __( 'URI Excludes', 'litespeed-cache' ), self::O_OPTM_GUEST_ONLY => __( 'Optimize for Guests Only', 'litespeed-cache' ), self::O_OPTM_EXC_ROLES => __( 'Role Excludes', 'litespeed-cache' ), self::O_DISCUSS_AVATAR_CACHE => __( 'Gravatar Cache', 'litespeed-cache' ), self::O_DISCUSS_AVATAR_CRON => __( 'Gravatar Cache Cron', 'litespeed-cache' ), self::O_DISCUSS_AVATAR_CACHE_TTL => __( 'Gravatar Cache TTL', 'litespeed-cache' ), self::O_MEDIA_LAZY => __( 'Lazy Load Images', 'litespeed-cache' ), self::O_MEDIA_LAZY_EXC => __( 'Lazy Load Image Excludes', 'litespeed-cache' ), self::O_MEDIA_LAZY_CLS_EXC => __( 'Lazy Load Image Class Name Excludes', 'litespeed-cache' ), self::O_MEDIA_LAZY_PARENT_CLS_EXC => __( 'Lazy Load Image Parent Class Name Excludes', 'litespeed-cache' ), self::O_MEDIA_IFRAME_LAZY_CLS_EXC => __( 'Lazy Load Iframe Class Name Excludes', 'litespeed-cache' ), self::O_MEDIA_IFRAME_LAZY_PARENT_CLS_EXC => __( 'Lazy Load Iframe Parent Class Name Excludes', 'litespeed-cache' ), self::O_MEDIA_LAZY_URI_EXC => __( 'Lazy Load URI Excludes', 'litespeed-cache' ), self::O_MEDIA_LQIP_EXC => __( 'LQIP Excludes', 'litespeed-cache' ), self::O_MEDIA_LAZY_PLACEHOLDER => __( 'Basic Image Placeholder', 'litespeed-cache' ), self::O_MEDIA_PLACEHOLDER_RESP => __( 'Responsive Placeholder', 'litespeed-cache' ), self::O_MEDIA_PLACEHOLDER_RESP_COLOR => __( 'Responsive Placeholder Color', 'litespeed-cache' ), self::O_MEDIA_PLACEHOLDER_RESP_SVG => __( 'Responsive Placeholder SVG', 'litespeed-cache' ), self::O_MEDIA_LQIP => __( 'LQIP Cloud Generator', 'litespeed-cache' ), self::O_MEDIA_LQIP_QUAL => __( 'LQIP Quality', 'litespeed-cache' ), self::O_MEDIA_LQIP_MIN_W => __( 'LQIP Minimum Dimensions', 'litespeed-cache' ), self::O_MEDIA_PLACEHOLDER_RESP_ASYNC => __( 'Generate LQIP In Background', 'litespeed-cache' ), self::O_MEDIA_IFRAME_LAZY => __( 'Lazy Load Iframes', 'litespeed-cache' ), self::O_MEDIA_ADD_MISSING_SIZES => __( 'Add Missing Sizes', 'litespeed-cache' ), self::O_MEDIA_VPI => __( 'Viewport Images', 'litespeed-cache' ), self::O_MEDIA_VPI_CRON => __( 'Viewport Images Cron', 'litespeed-cache' ), self::O_MEDIA_AUTO_RESCALE_ORI => __( 'Auto Rescale Original Images', 'litespeed-cache' ), self::O_IMG_OPTM_AUTO => __( 'Auto Request Cron', 'litespeed-cache' ), self::O_IMG_OPTM_ORI => __( 'Optimize Original Images', 'litespeed-cache' ), self::O_IMG_OPTM_RM_BKUP => __( 'Remove Original Backups', 'litespeed-cache' ), self::O_IMG_OPTM_WEBP => __( 'Next-Gen Image Format', 'litespeed-cache' ), self::O_IMG_OPTM_LOSSLESS => __( 'Optimize Losslessly', 'litespeed-cache' ), self::O_IMG_OPTM_SIZES_SKIPPED => __( 'Optimize Image Sizes', 'litespeed-cache' ), self::O_IMG_OPTM_EXIF => __( 'Preserve EXIF/XMP data', 'litespeed-cache' ), self::O_IMG_OPTM_WEBP_ATTR => __( 'WebP/AVIF Attribute To Replace', 'litespeed-cache' ), self::O_IMG_OPTM_WEBP_REPLACE_SRCSET => __( 'WebP/AVIF For Extra srcset', 'litespeed-cache' ), self::O_IMG_OPTM_JPG_QUALITY => __( 'WordPress Image Quality Control', 'litespeed-cache' ), self::O_ESI => __( 'Enable ESI', 'litespeed-cache' ), self::O_ESI_CACHE_ADMBAR => __( 'Cache Admin Bar', 'litespeed-cache' ), self::O_ESI_CACHE_COMMFORM => __( 'Cache Comment Form', 'litespeed-cache' ), self::O_ESI_NONCE => __( 'ESI Nonces', 'litespeed-cache' ), self::O_CACHE_VARY_GROUP => __( 'Vary Group', 'litespeed-cache' ), self::O_PURGE_HOOK_ALL => __( 'Purge All Hooks', 'litespeed-cache' ), self::O_UTIL_NO_HTTPS_VARY => __( 'Improve HTTP/HTTPS Compatibility', 'litespeed-cache' ), self::O_UTIL_INSTANT_CLICK => __( 'Instant Click', 'litespeed-cache' ), self::O_CACHE_EXC_COOKIES => __( 'Do Not Cache Cookies', 'litespeed-cache' ), self::O_CACHE_EXC_USERAGENTS => __( 'Do Not Cache User Agents', 'litespeed-cache' ), self::O_CACHE_LOGIN_COOKIE => __( 'Login Cookie', 'litespeed-cache' ), self::O_CACHE_VARY_COOKIES => __( 'Vary Cookies', 'litespeed-cache' ), self::O_MISC_HEARTBEAT_FRONT => __( 'Frontend Heartbeat Control', 'litespeed-cache' ), self::O_MISC_HEARTBEAT_FRONT_TTL => __( 'Frontend Heartbeat TTL', 'litespeed-cache' ), self::O_MISC_HEARTBEAT_BACK => __( 'Backend Heartbeat Control', 'litespeed-cache' ), self::O_MISC_HEARTBEAT_BACK_TTL => __( 'Backend Heartbeat TTL', 'litespeed-cache' ), self::O_MISC_HEARTBEAT_EDITOR => __( 'Editor Heartbeat', 'litespeed-cache' ), self::O_MISC_HEARTBEAT_EDITOR_TTL => __( 'Editor Heartbeat TTL', 'litespeed-cache' ), self::O_CDN => __( 'Use CDN Mapping', 'litespeed-cache' ), self::CDN_MAPPING_URL => __( 'CDN URL', 'litespeed-cache' ), self::CDN_MAPPING_INC_IMG => __( 'Include Images', 'litespeed-cache' ), self::CDN_MAPPING_INC_CSS => __( 'Include CSS', 'litespeed-cache' ), self::CDN_MAPPING_INC_JS => __( 'Include JS', 'litespeed-cache' ), self::CDN_MAPPING_FILETYPE => __( 'Include File Types', 'litespeed-cache' ), self::O_CDN_ATTR => __( 'HTML Attribute To Replace', 'litespeed-cache' ), self::O_CDN_ORI => __( 'Original URLs', 'litespeed-cache' ), self::O_CDN_ORI_DIR => __( 'Included Directories', 'litespeed-cache' ), self::O_CDN_EXC => __( 'Exclude Path', 'litespeed-cache' ), self::O_CDN_CLOUDFLARE => __( 'Cloudflare API', 'litespeed-cache' ), self::O_CDN_CLOUDFLARE_CLEAR => __( 'Clear Cloudflare cache', 'litespeed-cache' ), self::O_CRAWLER => __( 'Crawler', 'litespeed-cache' ), self::O_CRAWLER_CRAWL_INTERVAL => __( 'Crawl Interval', 'litespeed-cache' ), self::O_CRAWLER_LOAD_LIMIT => __( 'Server Load Limit', 'litespeed-cache' ), self::O_CRAWLER_ROLES => __( 'Role Simulation', 'litespeed-cache' ), self::O_CRAWLER_COOKIES => __( 'Cookie Simulation', 'litespeed-cache' ), self::O_CRAWLER_SITEMAP => __( 'Custom Sitemap', 'litespeed-cache' ), self::O_DEBUG_DISABLE_ALL => __( 'Disable All Features', 'litespeed-cache' ), self::O_DEBUG => __( 'Debug Log', 'litespeed-cache' ), self::O_DEBUG_IPS => __( 'Admin IPs', 'litespeed-cache' ), self::O_DEBUG_LEVEL => __( 'Debug Level', 'litespeed-cache' ), self::O_DEBUG_FILESIZE => __( 'Log File Size Limit', 'litespeed-cache' ), self::O_DEBUG_COLLAPSE_QS => __( 'Collapse Query Strings', 'litespeed-cache' ), self::O_DEBUG_INC => __( 'Debug URI Includes', 'litespeed-cache' ), self::O_DEBUG_EXC => __( 'Debug URI Excludes', 'litespeed-cache' ), self::O_DEBUG_EXC_STRINGS => __( 'Debug String Excludes', 'litespeed-cache' ), self::O_DB_OPTM_REVISIONS_MAX => __( 'Revisions Max Number', 'litespeed-cache' ), self::O_DB_OPTM_REVISIONS_AGE => __( 'Revisions Max Age', 'litespeed-cache' ), self::O_OPTIMAX => __( 'OptimaX', 'litespeed-cache' ), ]; if ( array_key_exists( $id, $_lang_list ) ) { return $_lang_list[ $id ]; } return 'N/A'; } } src/vpi.cls.php000064400000022600152075713330007430 0ustar00_summary = self::get_summary(); } /** * Queue the current page for VPI generation. * * @since 4.7 * @return void */ public function add_to_queue() { $is_mobile = $this->_separate_mobile(); global $wp; $request_url = home_url( $wp->request ); if ( ! apply_filters( 'litespeed_vpi_should_queue', true, $request_url ) ) { return; } // Sanitize user agent coming from the server superglobal. $ua = ! empty( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : ''; // Store it to prepare for cron. $this->_queue = $this->load_queue( 'vpi' ); if ( count( $this->_queue ) > 500 ) { self::debug( 'Queue is full - 500' ); return; } $home_id = (int) get_option( 'page_for_posts' ); if ( ! is_singular() && ! ( $home_id > 0 && is_home() ) ) { self::debug( 'not single post ID' ); return; } $post_id = is_home() ? $home_id : get_the_ID(); $queue_k = ( $is_mobile ? 'mobile' : '' ) . ' ' . $request_url; if ( ! empty( $this->_queue[ $queue_k ] ) ) { self::debug( 'queue k existed ' . $queue_k ); return; } $this->_queue[ $queue_k ] = [ 'url' => apply_filters( 'litespeed_vpi_url', $request_url ), 'post_id' => $post_id, 'user_agent' => substr( $ua, 0, 200 ), 'is_mobile' => $is_mobile, ]; // Current UA will be used to request. $this->save_queue( 'vpi', $this->_queue ); self::debug( 'Added queue_vpi [url] ' . $queue_k . ' [UA] ' . $ua ); // Prepare cache tag for later purge. Tag::add( 'VPI.' . md5( $queue_k ) ); } /** * Handle finish notifications from remote service. * * Expects JSON body; falls back to $_POST for legacy callers. * * @since 4.7 * @return array Response object for the cloud layer. */ public function notify() { // phpcs:ignore WordPress.Security.NonceVerification.Missing $post_data = \json_decode( file_get_contents( 'php://input' ), true ); if ( is_null( $post_data ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing $post_data = $_POST; } self::debug( 'notify() data', $post_data ); $this->_queue = $this->load_queue( 'vpi' ); list( $post_data ) = $this->cls( 'Cloud' )->extract_msg( $post_data, 'vpi' ); $notified_data = $post_data['data']; if ( empty( $notified_data ) || ! is_array( $notified_data ) ) { self::debug( '❌ notify exit: no notified data' ); return Cloud::err( 'no notified data' ); } // Check if it's in queue or not. $valid_i = 0; foreach ( $notified_data as $v ) { if ( empty( $v['request_url'] ) ) { self::debug( '❌ notify bypass: no request_url', $v ); continue; } if ( empty( $v['queue_k'] ) ) { self::debug( '❌ notify bypass: no queue_k', $v ); continue; } $queue_k = $v['queue_k']; if ( empty( $this->_queue[ $queue_k ] ) ) { self::debug( '❌ notify bypass: no this queue [q_k]' . $queue_k ); continue; } // Save data. if ( ! empty( $v['data_vpi'] ) ) { $post_id = (int) $this->_queue[ $queue_k ]['post_id']; $name = ! empty( $v['is_mobile'] ) ? self::POST_META_MOBILE : self::POST_META; $urldecode = is_array( $v['data_vpi'] ) ? array_map( 'urldecode', $v['data_vpi'] ) : urldecode( $v['data_vpi'] ); self::debug( 'save data_vpi', $urldecode ); $this->cls( 'Metabox' )->save( $post_id, $name, $urldecode ); ++$valid_i; } unset( $this->_queue[ $queue_k ] ); self::debug( 'notify data handled, unset queue [q_k] ' . $queue_k ); } $this->save_queue( 'vpi', $this->_queue ); self::debug( 'notified' ); return Cloud::ok( [ 'count' => $valid_i ] ); } /** * Cron entry point. * * @since 4.7 * * @param bool $do_continue Continue processing multiple queue items within one cron tick. * @return mixed Result of the handler. */ public static function cron( $do_continue = false ) { $_instance = self::cls(); return $_instance->_cron_handler( $do_continue ); } /** * Cron queue processor. * * @since 4.7 * * @param bool $do_continue Continue processing multiple queue items within one cron tick. * @return void */ private function _cron_handler( $do_continue = false ) { self::debug( 'cron start' ); $this->_queue = $this->load_queue( 'vpi' ); if ( empty( $this->_queue ) ) { return; } // For cron, need to check request interval too. if ( ! $do_continue ) { if ( ! empty( $this->_summary['curr_request_vpi'] ) && time() - (int) $this->_summary['curr_request_vpi'] < 300 && ! $this->conf( self::O_DEBUG ) ) { self::debug( 'Last request not done' ); return; } } $i = 0; foreach ( $this->_queue as $k => $v ) { if ( ! empty( $v['_status'] ) ) { continue; } self::debug( 'cron job [tag] ' . $k . ' [url] ' . $v['url'] . ( $v['is_mobile'] ? ' 📱 ' : '' ) . ' [UA] ' . $v['user_agent'] ); ++$i; $res = $this->_send_req( $v['url'], $k, $v['user_agent'], $v['is_mobile'] ); if ( ! $res ) { // Status is wrong, drop this item from queue. $this->_queue = $this->load_queue( 'vpi' ); unset( $this->_queue[ $k ] ); $this->save_queue( 'vpi', $this->_queue ); if ( ! $do_continue ) { return; } GUI::print_loading( count( $this->_queue ), 'VPI' ); Router::self_redirect( Router::ACTION_VPI, self::TYPE_GEN ); return; } // Exit queue if out of quota or service is hot. if ( 'out_of_quota' === $res || 'svc_hot' === $res ) { return; } $this->_queue = $this->load_queue( 'vpi' ); $this->_queue[ $k ]['_status'] = 'requested'; $this->save_queue( 'vpi', $this->_queue ); self::debug( 'Saved to queue [k] ' . $k ); // only request first one if not continuing. if ( ! $do_continue ) { return; } GUI::print_loading( count( $this->_queue ), 'VPI' ); Router::self_redirect( Router::ACTION_VPI, self::TYPE_GEN ); return; } } /** * Send request to QUIC.cloud API to generate VPI. * * @since 4.7 * @access private * * @param string $request_url The URL to analyze for VPI. * @param string $queue_k Queue key for this job. * @param string $user_agent Sanitized User-Agent string (<=200 chars). * @param bool $is_mobile Whether the job is for mobile viewport. * @return bool|string True on queued successfully, 'out_of_quota'/'svc_hot' on throttling, or false on error. */ private function _send_req( $request_url, $queue_k, $user_agent, $is_mobile ) { $svc = Cloud::SVC_VPI; // Check if has credit to push or not. $err = false; $allowance = $this->cls( 'Cloud' )->allowance( $svc, $err ); if ( ! $allowance ) { self::debug( '❌ No credit: ' . $err ); $err && Admin_Display::error( Error::msg( $err ) ); return 'out_of_quota'; } set_time_limit( 120 ); // Update request status. self::save_summary( [ 'curr_request_vpi' => time() ], true ); // Gather guest HTML to send. $html = $this->cls( 'CSS' )->prepare_html( $request_url, $user_agent ); if ( ! $html ) { return false; } // Parse HTML to gather CSS content before requesting. $css = false; list( $css, $html ) = $this->cls( 'CSS' )->prepare_css( $html ); if ( ! $css ) { self::debug( '❌ No css' ); return false; } $data = [ 'url' => $request_url, 'queue_k' => $queue_k, 'user_agent' => $user_agent, 'is_mobile' => $is_mobile ? 1 : 0, // todo: compatible w/ tablet. 'html' => $html, 'css' => $css, ]; self::debug( 'Generating: ', $data ); $json = Cloud::post( $svc, $data, 30 ); if ( ! is_array( $json ) ) { return $json; } // Unknown status, remove this line. if ( 'queued' !== $json['status'] ) { return false; } // Save summary data. self::reload_summary(); $this->_summary['last_spent_vpi'] = time() - (int) $this->_summary['curr_request_vpi']; $this->_summary['last_request_vpi'] = $this->_summary['curr_request_vpi']; $this->_summary['curr_request_vpi'] = 0; self::save_summary(); return true; } /** * Handle all request actions from main controller. * * @since 4.7 * @return void */ public function handler() { $type = Router::verify_type(); switch ( $type ) { case self::TYPE_GEN: self::cron( true ); break; case self::TYPE_CLEAR_Q: $this->clear_q( 'vpi' ); break; default: break; } Admin::redirect(); } } src/base.cls.php000064400000113240152075713330007545 0ustar00 */ protected static $_default_options = [ self::_VER => '', self::HASH => '', self::O_API_KEY => '', self::O_AUTO_UPGRADE => false, self::O_SERVER_IP => '', self::O_GUEST => false, self::O_GUEST_OPTM => false, self::O_NEWS => false, // Cache self::O_CACHE => false, self::O_CACHE_PRIV => false, self::O_CACHE_COMMENTER => false, self::O_CACHE_REST => false, self::O_CACHE_PAGE_LOGIN => false, self::O_CACHE_MOBILE => false, self::O_CACHE_MOBILE_RULES => [], self::O_CACHE_BROWSER => false, self::O_CACHE_EXC_USERAGENTS => [], self::O_CACHE_EXC_COOKIES => [], self::O_CACHE_EXC_QS => [], self::O_CACHE_EXC_CAT => [], self::O_CACHE_EXC_TAG => [], self::O_CACHE_FORCE_URI => [], self::O_CACHE_FORCE_PUB_URI => [], self::O_CACHE_PRIV_URI => [], self::O_CACHE_EXC => [], self::O_CACHE_EXC_ROLES => [], self::O_CACHE_DROP_QS => [], self::O_CACHE_TTL_PUB => 0, self::O_CACHE_TTL_PRIV => 0, self::O_CACHE_TTL_FRONTPAGE => 0, self::O_CACHE_TTL_FEED => 0, self::O_CACHE_TTL_REST => 0, self::O_CACHE_TTL_BROWSER => 0, self::O_CACHE_TTL_STATUS => [], self::O_CACHE_LOGIN_COOKIE => '', self::O_CACHE_AJAX_TTL => [], self::O_CACHE_VARY_COOKIES => [], self::O_CACHE_VARY_GROUP => [], // Purge self::O_PURGE_ON_UPGRADE => false, self::O_PURGE_STALE => false, self::O_PURGE_POST_ALL => false, self::O_PURGE_POST_FRONTPAGE => false, self::O_PURGE_POST_HOMEPAGE => false, self::O_PURGE_POST_PAGES => false, self::O_PURGE_POST_PAGES_WITH_RECENT_POSTS => false, self::O_PURGE_POST_AUTHOR => false, self::O_PURGE_POST_YEAR => false, self::O_PURGE_POST_MONTH => false, self::O_PURGE_POST_DATE => false, self::O_PURGE_POST_TERM => false, self::O_PURGE_POST_POSTTYPE => false, self::O_PURGE_TIMED_URLS => [], self::O_PURGE_TIMED_URLS_TIME => '', self::O_PURGE_HOOK_ALL => [], // ESI self::O_ESI => false, self::O_ESI_CACHE_ADMBAR => false, self::O_ESI_CACHE_COMMFORM => false, self::O_ESI_NONCE => [], // Util self::O_UTIL_INSTANT_CLICK => false, self::O_UTIL_NO_HTTPS_VARY => false, // Debug self::O_DEBUG_DISABLE_ALL => false, self::O_DEBUG => false, self::O_DEBUG_IPS => [], self::O_DEBUG_LEVEL => false, self::O_DEBUG_FILESIZE => 0, self::O_DEBUG_COLLAPSE_QS => false, self::O_DEBUG_INC => [], self::O_DEBUG_EXC => [], self::O_DEBUG_EXC_STRINGS => [], // DB Optm self::O_DB_OPTM_REVISIONS_MAX => 0, self::O_DB_OPTM_REVISIONS_AGE => 0, // HTML Optm self::O_OPTM_CSS_MIN => false, self::O_OPTM_CSS_COMB => false, self::O_OPTM_CSS_COMB_EXT_INL => false, self::O_OPTM_UCSS => false, self::O_OPTM_UCSS_INLINE => false, self::O_OPTM_UCSS_SELECTOR_WHITELIST => [], self::O_OPTM_UCSS_FILE_EXC_INLINE => [], self::O_OPTM_UCSS_EXC => [], self::O_OPTM_CSS_EXC => [], self::O_OPTM_JS_MIN => false, self::O_OPTM_JS_COMB => false, self::O_OPTM_JS_COMB_EXT_INL => false, self::O_OPTM_JS_DELAY_INC => [], self::O_OPTM_JS_EXC => [], self::O_OPTM_HTML_MIN => false, self::O_OPTM_HTML_LAZY => [], self::O_OPTM_HTML_SKIP_COMMENTS => [], self::O_OPTM_QS_RM => false, self::O_OPTM_GGFONTS_RM => false, self::O_OPTM_CSS_ASYNC => false, self::O_OPTM_CCSS_PER_URL => false, self::O_OPTM_CCSS_SEP_POSTTYPE => [], self::O_OPTM_CCSS_SEP_URI => [], self::O_OPTM_CCSS_SELECTOR_WHITELIST => [], self::O_OPTM_CSS_ASYNC_INLINE => false, self::O_OPTM_CSS_FONT_DISPLAY => false, self::O_OPTM_JS_DEFER => false, self::O_OPTM_EMOJI_RM => false, self::O_OPTM_NOSCRIPT_RM => false, self::O_OPTM_GGFONTS_ASYNC => false, self::O_OPTM_EXC_ROLES => [], self::O_OPTM_CCSS_CON => '', self::O_OPTM_JS_DEFER_EXC => [], self::O_OPTM_GM_JS_EXC => [], self::O_OPTM_DNS_PREFETCH => [], self::O_OPTM_DNS_PREFETCH_CTRL => false, self::O_OPTM_DNS_PRECONNECT => [], self::O_OPTM_EXC => [], self::O_OPTM_GUEST_ONLY => false, // Object self::O_OBJECT => false, self::O_OBJECT_KIND => false, self::O_OBJECT_HOST => '', self::O_OBJECT_PORT => 0, self::O_OBJECT_LIFE => 0, self::O_OBJECT_PERSISTENT => false, self::O_OBJECT_ADMIN => false, self::O_OBJECT_DB_ID => 0, self::O_OBJECT_USER => '', self::O_OBJECT_PSWD => '', self::O_OBJECT_GLOBAL_GROUPS => [], self::O_OBJECT_NON_PERSISTENT_GROUPS => [], // Discuss self::O_DISCUSS_AVATAR_CACHE => false, self::O_DISCUSS_AVATAR_CRON => false, self::O_DISCUSS_AVATAR_CACHE_TTL => 0, self::O_OPTM_LOCALIZE => false, self::O_OPTM_LOCALIZE_DOMAINS => [], // Media self::O_MEDIA_LAZY => false, self::O_MEDIA_LAZY_PLACEHOLDER => '', self::O_MEDIA_PLACEHOLDER_RESP => false, self::O_MEDIA_PLACEHOLDER_RESP_COLOR => '', self::O_MEDIA_PLACEHOLDER_RESP_SVG => '', self::O_MEDIA_LQIP => false, self::O_MEDIA_LQIP_QUAL => 0, self::O_MEDIA_LQIP_MIN_W => 0, self::O_MEDIA_LQIP_MIN_H => 0, self::O_MEDIA_PLACEHOLDER_RESP_ASYNC => false, self::O_MEDIA_IFRAME_LAZY => false, self::O_MEDIA_ADD_MISSING_SIZES => false, self::O_MEDIA_LAZY_EXC => [], self::O_MEDIA_LAZY_CLS_EXC => [], self::O_MEDIA_LAZY_PARENT_CLS_EXC => [], self::O_MEDIA_IFRAME_LAZY_CLS_EXC => [], self::O_MEDIA_IFRAME_LAZY_PARENT_CLS_EXC => [], self::O_MEDIA_LAZY_URI_EXC => [], self::O_MEDIA_LQIP_EXC => [], self::O_MEDIA_VPI => false, self::O_MEDIA_VPI_CRON => false, self::O_MEDIA_AUTO_RESCALE_ORI => false, // Image Optm self::O_IMG_OPTM_AUTO => false, self::O_IMG_OPTM_ORI => false, self::O_IMG_OPTM_RM_BKUP => false, self::O_IMG_OPTM_WEBP => false, self::O_IMG_OPTM_LOSSLESS => false, self::O_IMG_OPTM_SIZES_SKIPPED => [], self::O_IMG_OPTM_EXIF => false, self::O_IMG_OPTM_WEBP_ATTR => [], self::O_IMG_OPTM_WEBP_REPLACE_SRCSET => false, self::O_IMG_OPTM_JPG_QUALITY => 0, // Crawler self::O_CRAWLER => false, self::O_CRAWLER_CRAWL_INTERVAL => 0, self::O_CRAWLER_LOAD_LIMIT => 0, self::O_CRAWLER_SITEMAP => '', self::O_CRAWLER_ROLES => [], self::O_CRAWLER_COOKIES => [], // Misc self::O_MISC_HEARTBEAT_FRONT => false, self::O_MISC_HEARTBEAT_FRONT_TTL => 0, self::O_MISC_HEARTBEAT_BACK => false, self::O_MISC_HEARTBEAT_BACK_TTL => 0, self::O_MISC_HEARTBEAT_EDITOR => false, self::O_MISC_HEARTBEAT_EDITOR_TTL => 0, // CDN self::O_CDN => false, self::O_CDN_ORI => [], self::O_CDN_ORI_DIR => [], self::O_CDN_EXC => [], self::O_CDN_QUIC => false, self::O_CDN_CLOUDFLARE => false, self::O_CDN_CLOUDFLARE_EMAIL => '', self::O_CDN_CLOUDFLARE_KEY => '', self::O_CDN_CLOUDFLARE_NAME => '', self::O_CDN_CLOUDFLARE_ZONE => '', self::O_CDN_CLOUDFLARE_CLEAR => false, self::O_CDN_MAPPING => [], self::O_CDN_ATTR => [], self::O_QC_NAMESERVERS => '', self::O_QC_CNAME => '', self::DEBUG_TMP_DISABLE => 0, ]; /** * Default options for multisite (site-level options stored network-wide). * * @var array */ protected static $_default_site_options = [ self::_VER => '', self::O_CACHE => false, self::NETWORK_O_USE_PRIMARY => false, self::O_AUTO_UPGRADE => false, self::O_GUEST => false, self::O_CACHE_BROWSER => false, self::O_CACHE_MOBILE => false, self::O_CACHE_MOBILE_RULES => [], self::O_CACHE_DROP_QS => [], self::O_CACHE_LOGIN_COOKIE => '', self::O_CACHE_VARY_COOKIES => [], self::O_CACHE_EXC_COOKIES => [], self::O_CACHE_EXC_USERAGENTS => [], self::O_CACHE_TTL_BROWSER => 0, self::O_PURGE_ON_UPGRADE => false, self::O_OBJECT => false, self::O_OBJECT_KIND => false, self::O_OBJECT_HOST => '', self::O_OBJECT_PORT => 0, self::O_OBJECT_LIFE => 0, self::O_OBJECT_PERSISTENT => false, self::O_OBJECT_ADMIN => false, self::O_OBJECT_DB_ID => 0, self::O_OBJECT_USER => '', self::O_OBJECT_PSWD => '', self::O_OBJECT_GLOBAL_GROUPS => [], self::O_OBJECT_NON_PERSISTENT_GROUPS => [], // Debug self::O_DEBUG_DISABLE_ALL => false, self::O_DEBUG => false, self::O_DEBUG_IPS => [], self::O_DEBUG_LEVEL => false, self::O_DEBUG_FILESIZE => 0, self::O_DEBUG_COLLAPSE_QS => false, self::O_DEBUG_INC => [], self::O_DEBUG_EXC => [], self::O_DEBUG_EXC_STRINGS => [], self::O_IMG_OPTM_WEBP => false, ]; /** * Multi-switch options: option ID => max state (int). * NOTE: all the val of following items will be int while not bool * * @var array */ protected static $_multi_switch_list = [ self::O_DEBUG => 2, self::O_OPTM_JS_DEFER => 2, self::O_IMG_OPTM_WEBP => 2, ]; /** * Cache for blog options to avoid repeated switch_to_blog calls. * Structure: [ blog_id => [ option_name => value, ... ], ... ] * * @since 7.8 * @var array> */ private static $_blog_options_cache = []; /** * Get option from another blog with batch preload optimization. * * Reduces N*2 switch_to_blog calls to just 2 by preloading all options on first call. * * @since 7.8 * @param int $blog_id Blog ID to get option from. * @param string $id Option ID (without prefix). * @param mixed $default_v Default value if option not found. * @return mixed Option value. */ public static function get_blog_option( $blog_id, $id, $default_v = false ) { $blog_id = (int) $blog_id; // If current blog, use get_option directly (no switch needed). if ( get_current_blog_id() === $blog_id ) { return self::get_option( $id, $default_v ); } // Preload all options for this blog if not cached. if ( ! isset( self::$_blog_options_cache[ $blog_id ] ) ) { self::_preload_blog_options( $blog_id ); } $v = self::$_blog_options_cache[ $blog_id ][ $id ]; // Maybe decode array. if ( is_array( $default_v ) ) { $v = self::_maybe_decode( $v ); } return $v; } /** * Preload all conf options for a blog into cache. * * @since 7.8 * @param int $blog_id Blog ID to preload options from. */ private static function _preload_blog_options( $blog_id ) { self::$_blog_options_cache[ $blog_id ] = []; switch_to_blog( $blog_id ); foreach ( self::$_default_options as $id => $default_v ) { self::$_blog_options_cache[ $blog_id ][ $id ] = get_option( self::name( $id ), $default_v ); } restore_current_blog(); } /** * Correct the option type. * * TODO: add similar network func * * @since 3.0.3 * * @param mixed $val Incoming value. * @param string $id Option ID. * @param bool $is_site_conf Whether using site-level defaults. * @return mixed */ protected function type_casting( $val, $id, $is_site_conf = false ) { $default_v = ! $is_site_conf ? self::$_default_options[ $id ] : self::$_default_site_options[ $id ]; if ( is_bool( $default_v ) ) { if ( 'true' === $val ) { $val = true; } if ( 'false' === $val ) { $val = false; } $max = $this->_conf_multi_switch( $id ); if ( $max ) { $val = (int) $val; $val %= $max + 1; } else { $val = (bool) $val; } } elseif ( is_array( $default_v ) ) { // from textarea input if ( ! is_array( $val ) ) { $val = Utility::sanitize_lines( $val, $this->_conf_filter( $id ) ); } } elseif ( ! is_string( $default_v ) ) { $val = (int) $val; } else { // Check if the string has a limit set $val = $this->_conf_string_val( $id, $val ); } return $val; } /** * Load default network settings from data.ini * * @since 3.0 * @return array */ public function load_default_site_vals() { // Load network_default.json if ( file_exists( LSCWP_DIR . 'data/const.network_default.json' ) ) { $default_ini_cfg = json_decode( File::read( LSCWP_DIR . 'data/const.network_default.json' ), true ); foreach ( self::$_default_site_options as $k => $v ) { if ( ! array_key_exists( $k, $default_ini_cfg ) ) { continue; } // Parse value in ini file $ini_v = $this->type_casting( $default_ini_cfg[ $k ], $k, true ); if ( $ini_v === $v ) { continue; } self::$_default_site_options[ $k ] = $ini_v; } } self::$_default_site_options[ self::_VER ] = Core::VER; return self::$_default_site_options; } /** * Load default values from default.json * * @since 3.0 * @access public * @return array */ public function load_default_vals() { // Load default.json if ( file_exists( LSCWP_DIR . 'data/const.default.json' ) ) { $default_ini_cfg = json_decode( File::read( LSCWP_DIR . 'data/const.default.json' ), true ); foreach ( self::$_default_options as $k => $v ) { if ( ! array_key_exists( $k, $default_ini_cfg ) ) { continue; } // Parse value in ini file $ini_v = $this->type_casting( $default_ini_cfg[ $k ], $k ); // NOTE: Multiple lines value must be stored as array /** * Special handler for CDN_mapping * * Format in .ini: * [cdn-mapping] * url[0] = 'https://example.com/' * inc_js[0] = true * filetype[0] = '.css * .js * .jpg' * * format out: * [0] = [ 'url' => 'https://example.com', 'inc_js' => true, 'filetype' => [ '.css', '.js', '.jpg' ] ] */ if ( self::O_CDN_MAPPING === $k ) { $mapping_fields = [ self::CDN_MAPPING_URL, self::CDN_MAPPING_INC_IMG, self::CDN_MAPPING_INC_CSS, self::CDN_MAPPING_INC_JS, self::CDN_MAPPING_FILETYPE, // Array ]; $ini_v2 = []; foreach ( $ini_v[ self::CDN_MAPPING_URL ] as $k2 => $v2 ) { // $k2 is numeric $this_row = []; foreach ( $mapping_fields as $v3 ) { $this_v = ! empty( $ini_v[ $v3 ][ $k2 ] ) ? $ini_v[ $v3 ][ $k2 ] : false; if ( self::CDN_MAPPING_URL === $v3 ) { if ( empty( $this_v ) ) { $this_v = ''; } } if ( self::CDN_MAPPING_FILETYPE === $v3 ) { $this_v = $this_v ? Utility::sanitize_lines( $this_v ) : []; // Note: Since v3.0 its already an array } $this_row[ $v3 ] = $this_v; } $ini_v2[ $k2 ] = $this_row; } $ini_v = $ini_v2; } if ( $ini_v === $v ) { continue; } self::$_default_options[ $k ] = $ini_v; } } // Load internal default vals // Setting the default bool to int is also to avoid type casting override it back to bool self::$_default_options[ self::O_CACHE ] = is_multisite() ? self::VAL_ON2 : self::VAL_ON; // For multi site, default is 2 (Use Network Admin Settings). For single site, default is 1 (Enabled). // Load default vals containing variables if ( ! self::$_default_options[ self::O_CDN_ORI_DIR ] ) { self::$_default_options[ self::O_CDN_ORI_DIR ] = LSCWP_CONTENT_FOLDER . "\nwp-includes"; self::$_default_options[ self::O_CDN_ORI_DIR ] = explode( "\n", self::$_default_options[ self::O_CDN_ORI_DIR ] ); self::$_default_options[ self::O_CDN_ORI_DIR ] = array_map( 'trim', self::$_default_options[ self::O_CDN_ORI_DIR ] ); } // Set security key if not initialized yet if ( ! self::$_default_options[ self::HASH ] ) { self::$_default_options[ self::HASH ] = Str::rrand( 32 ); } self::$_default_options[ self::_VER ] = Core::VER; return self::$_default_options; } /** * Format the string value. * * @since 3.0 * * @param string $id Option ID. * @param mixed $val Value. * @return string */ protected function _conf_string_val( $id, $val ) { return (string) $val; } /** * If the switch setting is a triple value or not. * * @since 3.0 * * @param string $id Option ID. * @return int|false */ protected function _conf_multi_switch( $id ) { if ( ! empty( self::$_multi_switch_list[ $id ] ) ) { return self::$_multi_switch_list[ $id ]; } if ( self::O_CACHE === $id && is_multisite() ) { return self::VAL_ON2; } return false; } /** * Append a new multi switch max limit for the bool option. * * @since 3.0 * * @param string $id Option ID. * @param int $v Max state. * @return void */ public static function set_multi_switch( $id, $v ) { self::$_multi_switch_list[ $id ] = $v; } /** * Generate const name based on $id. * * @since 3.0 * * @param string $id Option ID. * @return string */ public static function conf_const( $id ) { return 'LITESPEED_CONF__' . strtoupper( str_replace( '-', '__', $id ) ); } /** * Filter to be used when saving setting. * * @since 3.0 * * @param string $id Option ID. * @return string|false */ protected function _conf_filter( $id ) { $filters = [ self::O_MEDIA_LAZY_EXC => 'uri', self::O_DEBUG_INC => 'relative', self::O_DEBUG_EXC => 'relative', self::O_MEDIA_LAZY_URI_EXC => 'relative', self::O_CACHE_PRIV_URI => 'relative', self::O_PURGE_TIMED_URLS => 'relative', self::O_CACHE_FORCE_URI => 'relative', self::O_CACHE_FORCE_PUB_URI => 'relative', self::O_CACHE_EXC => 'relative', // self::O_OPTM_CSS_EXC => 'uri', // Need to comment out for inline & external CSS // self::O_OPTM_JS_EXC => 'uri', self::O_OPTM_EXC => 'relative', self::O_OPTM_CCSS_SEP_URI => 'uri', // self::O_OPTM_JS_DEFER_EXC => 'uri', self::O_OPTM_DNS_PREFETCH => 'domain', self::O_CDN_ORI => 'noprotocol,trailingslash', // `Original URLs` // self::O_OPTM_LOCALIZE_DOMAINS => 'noprotocol', // `Localize Resources` // self:: => '', // self:: => '', ]; if ( ! empty( $filters[ $id ] ) ) { return $filters[ $id ]; } return false; } /** * If the setting changes worth a purge or not. * * @since 3.0 * * @param string $id Option ID. * @return bool */ protected function _conf_purge( $id ) { $check_ids = [ self::O_MEDIA_LAZY_URI_EXC, self::O_OPTM_EXC, self::O_CACHE_PRIV_URI, self::O_PURGE_TIMED_URLS, self::O_CACHE_FORCE_URI, self::O_CACHE_FORCE_PUB_URI, self::O_CACHE_EXC, ]; return in_array( $id, $check_ids, true ); } /** * If the setting changes worth a purge ALL or not. * * @since 3.0 * * @param string $id Option ID. * @return bool */ protected function _conf_purge_all( $id ) { $check_ids = [ self::O_CACHE, self::O_ESI, self::O_DEBUG_DISABLE_ALL, self::NETWORK_O_USE_PRIMARY ]; return in_array( $id, $check_ids, true ); } /** * If the setting is a password or not. * * @since 3.0 * * @param string $id Option ID. * @return bool */ protected function _conf_pswd( $id ) { $check_ids = [ self::O_CDN_CLOUDFLARE_KEY, self::O_OBJECT_PSWD ]; return in_array( $id, $check_ids, true ); } /** * If the setting is cron related or not. * * @since 3.0 * * @param string $id Option ID. * @return bool */ protected function _conf_cron( $id ) { $check_ids = [ self::O_OPTM_CSS_ASYNC, self::O_MEDIA_PLACEHOLDER_RESP_ASYNC, self::O_DISCUSS_AVATAR_CRON, self::O_IMG_OPTM_AUTO, self::O_CRAWLER ]; return in_array( $id, $check_ids, true ); } /** * If the setting changes worth a purge, return the tag. * * @since 3.0 * * @param string $id Option ID. * @return string|false */ protected function _conf_purge_tag( $id ) { $check_ids = [ self::O_CACHE_PAGE_LOGIN => Tag::TYPE_LOGIN, ]; if ( ! empty( $check_ids[ $id ] ) ) { return $check_ids[ $id ]; } return false; } /** * Generate server vars. * * @since 2.4.1 * * @return array Map of constant name => value|null. */ public function server_vars() { $consts = [ 'WP_SITEURL', 'WP_HOME', 'WP_CONTENT_DIR', 'SHORTINIT', 'LSCWP_CONTENT_DIR', 'LSCWP_CONTENT_FOLDER', 'LSCWP_DIR', 'LITESPEED_TIME_OFFSET', 'LITESPEED_SERVER_TYPE', 'LITESPEED_CLI', 'LITESPEED_ALLOWED', 'LITESPEED_ON', 'LSWCP_TAG_PREFIX', 'COOKIEHASH', ]; $server_vars = []; foreach ( $consts as $v ) { $server_vars[ $v ] = defined( $v ) ? constant( $v ) : null; } return $server_vars; } /** * Save CSS content (UCSS/CCSS) to file and register in DB. * * Shared by UCSS, CSS, and Optimax classes. * * @since 8.0 * * @param string $type CSS type ('ucss' or 'ccss'). * @param string $css CSS content. * @param string $url_tag URL tag for DB mapping. * @param string $vary Vary string. * @param string $queue_k Queue key (for filter and purge tag). * @param bool $is_mobile Whether is mobile. * @param bool $is_webp Whether supports webp. * @return void */ protected function _save_css_con( $type, $css, $url_tag, $vary, $queue_k, $is_mobile, $is_webp ) { $css = apply_filters( 'litespeed_' . $type, $css, $queue_k ); // Font optimize $css = $this->cls('Optimizer')->optm_font_face( $css ); // Sanitize: CSS must not contain HTML tags $css = wp_strip_all_tags( $css ); self::debug2( 'con: ', $css ); if ( '/*' === substr( $css, 0, 2 ) && '*/' === substr( $css, -2 ) ) { self::debug( '❌ empty ' . $type . ' [content] ' . $css ); } $filecon_md5 = md5( $css ); $filepath_prefix = $this->_build_filepath_prefix( $type ); $static_file = LITESPEED_STATIC_DIR . $filepath_prefix . $filecon_md5 . '.css'; File::save( $static_file, $css, true ); self::debug2( "Save URL to file [file] $static_file [vary] $vary" ); $this->cls( 'Data' )->save_url( $url_tag, $vary, $type, $filecon_md5, dirname( $static_file ), $is_mobile, $is_webp ); Purge::add( strtoupper( $type ) . '.' . md5( $queue_k ) ); } } src/cloud-auth-callback.trait.php000064400000024673152075713330013007 0ustar00_summary['sk_b64'] ) ) { self::debugErr( 'No sk to sign.' ); return false; } $sk = base64_decode( $this->_summary['sk_b64'] ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode if ( strlen( $sk ) !== SODIUM_CRYPTO_SIGN_SECRETKEYBYTES ) { self::debugErr( 'Invalid local sign sk length.' ); // Reset local pk/sk unset( $this->_summary['pk_b64'] ); unset( $this->_summary['sk_b64'] ); $this->save_summary(); self::debug( 'Clear local sign pk/sk pair.' ); return false; } $signature = sodium_crypto_sign_detached( (string) $data, $sk ); return base64_encode( $signature ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode } /** * Load server pk from cloud * * @since 7.0 * * @param bool $from_wpapi Load from WP API server. * @return string|false Binary public key or false. */ private function _load_server_pk( $from_wpapi = false ) { // Load cloud pk $server_key_url = $this->_cloud_server . '/' . self::API_SERVER_KEY_SIGN; if ( $from_wpapi ) { $server_key_url = $this->_cloud_server_wp . '/' . self::API_SERVER_KEY_SIGN; } $resp = wp_safe_remote_get( $server_key_url ); if ( is_wp_error( $resp ) ) { self::debugErr( 'Failed to load key: ' . $resp->get_error_message() ); return false; } $pk = trim( $resp['body'] ); self::debug( 'Loaded key from ' . $server_key_url . ': ' . $pk ); $cloud_pk = base64_decode( $pk ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode if ( strlen( $cloud_pk ) !== SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES ) { self::debugErr( 'Invalid cloud public key length.' ); return false; } $sk = base64_decode( $this->_summary['sk_b64'] ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode if ( strlen( $sk ) !== SODIUM_CRYPTO_SIGN_SECRETKEYBYTES ) { self::debugErr( 'Invalid local secret key length.' ); // Reset local pk/sk unset( $this->_summary['pk_b64'] ); unset( $this->_summary['sk_b64'] ); $this->save_summary(); self::debug( 'Unset local pk/sk pair.' ); return false; } return $cloud_pk; } /** * WPAPI echo back to notify the sealed databox * * @since 7.0 */ public function wp_rest_echo() { // phpcs:ignore WordPress.Security.NonceVerification.Missing self::debug( 'Parsing echo', $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.Missing $ts = !empty( $_POST['wpapi_ts'] ) ? sanitize_text_field( wp_unslash( $_POST['wpapi_ts'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing $sig = !empty( $_POST['wpapi_signature_b64'] ) ? sanitize_text_field( wp_unslash( $_POST['wpapi_signature_b64'] ) ) : ''; if ( empty( $ts ) || empty( $sig ) ) { return self::err( 'No echo data' ); } $is_valid = $this->_validate_signature( $sig, $ts, true ); if ( ! $is_valid ) { return self::err( 'Data validation from WPAPI REST Echo failed' ); } $diff = time() - (int) $ts; if ( abs( $diff ) > 86400 ) { self::debugErr( 'WPAPI echo data timeout [diff] ' . $diff ); return self::err( 'Echo data expired' ); } $signature_b64 = $this->_sign_b64( $ts ); self::debug( 'Response to echo [signature_b64] ' . $signature_b64 ); return self::ok( [ 'signature_b64' => $signature_b64 ] ); } /** * Validate cloud data * * @since 7.0 * * @param string $signature_b64 Base64 signature. * @param string $data Data to validate. * @param bool $from_wpapi Whether the signature is from WP API server. * @return bool */ private function _validate_signature( $signature_b64, $data, $from_wpapi = false ) { // Try validation try { $cloud_pk = $this->_load_server_pk( $from_wpapi ); if ( ! $cloud_pk ) { return false; } $signature = base64_decode( $signature_b64 ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode $is_valid = sodium_crypto_sign_verify_detached( $signature, (string) $data, $cloud_pk ); } catch ( \SodiumException $e ) { self::debugErr( 'Decryption failed: ' . esc_html( $e->getMessage() ) ); return false; } self::debug( 'Signature validation result: ' . ( $is_valid ? 'true' : 'false' ) ); return $is_valid; } /** * Finish qc activation after redirection back from QC * * @since 7.0 * * @param string|false $ref Ref slug. */ public function finish_qc_activation( $ref = false ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended $qc_activated = !empty( $_GET['qc_activated'] ) ? sanitize_text_field( wp_unslash( $_GET['qc_activated'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended $qc_ts = !empty( $_GET['qc_ts'] ) ? sanitize_text_field( wp_unslash( $_GET['qc_ts'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended $qc_sig = !empty( $_GET['qc_signature_b64'] ) ? sanitize_text_field( wp_unslash( $_GET['qc_signature_b64'] ) ) : ''; if ( ! $qc_activated || ! $qc_ts || ! $qc_sig ) { return; } $data_to_validate_signature = [ 'wp_pk_b64' => $this->_summary['pk_b64'], 'qc_ts' => $qc_ts, ]; $is_valid = $this->_validate_signature( $qc_sig, implode( '', $data_to_validate_signature ) ); if ( ! $is_valid ) { self::debugErr( 'Failed to validate qc activation data' ); Admin_Display::error( sprintf( __( 'Failed to validate %s activation data.', 'litespeed-cache' ), 'QUIC.cloud' ) ); return; } self::debug( 'QC activation status: ' . $qc_activated ); if ( ! in_array( $qc_activated, [ 'anonymous', 'linked', 'cdn' ], true ) ) { self::debugErr( 'Failed to parse qc activation status' ); Admin_Display::error( sprintf( __( 'Failed to parse %s activation status.', 'litespeed-cache' ), 'QUIC.cloud' ) ); return; } $diff = time() - (int) $qc_ts; if ( abs( $diff ) > 86400 ) { self::debugErr( 'QC activation data timeout [diff] ' . $diff ); Admin_Display::error( sprintf( __( '%s activation data expired.', 'litespeed-cache' ), 'QUIC.cloud' ) ); return; } // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended $main_domain = ! empty( $_GET['main_domain'] ) ? sanitize_text_field( wp_unslash( $_GET['main_domain'] ) ) : false; $this->update_qc_activation( $qc_activated, $main_domain ); wp_safe_redirect( $this->_get_ref_url( $ref ) ); exit; } /** * Finish qc activation process * * @since 7.0 * * @param string $qc_activated Activation status. * @param string|bool $main_domain Main domain. * @param bool $quite Quiet flag. */ public function update_qc_activation( $qc_activated, $main_domain = false, $quite = false ) { $this->_summary['qc_activated'] = $qc_activated; if ( $main_domain ) { $this->_summary['main_domain'] = $main_domain; } $this->save_summary(); $msg = sprintf( __( 'Congratulations, %s successfully set this domain up for the anonymous online services.', 'litespeed-cache' ), 'QUIC.cloud' ); if ( 'linked' === $qc_activated ) { $msg = sprintf( __( 'Congratulations, %s successfully set this domain up for the online services.', 'litespeed-cache' ), 'QUIC.cloud' ); // Sync possible partner info $this->sync_usage(); } if ( 'cdn' === $qc_activated ) { $msg = sprintf( __( 'Congratulations, %s successfully set this domain up for the online services with CDN service.', 'litespeed-cache' ), 'QUIC.cloud' ); // Turn on CDN option $this->cls( 'Conf' )->update_confs( [ self::O_CDN_QUIC => true ] ); } if ( ! $quite ) { Admin_Display::success( '🎊 ' . $msg ); } $this->_clear_reset_qc_reg_msg(); $this->clear_cloud(); } /** * Update QC status * * @since 7.0 */ public function update_cdn_status() { // phpcs:ignore WordPress.Security.NonceVerification.Missing $qc_activated = !empty( $_POST['qc_activated'] ) ? sanitize_text_field( wp_unslash( $_POST['qc_activated'] ) ) : ''; if ( !$qc_activated || ! in_array( $qc_activated, [ 'anonymous', 'linked', 'cdn', 'deleted' ], true ) ) { return self::err( 'lack_of_params' ); } self::debug( 'update_cdn_status request hash: ' . $qc_activated ); if ( 'deleted' === $qc_activated ) { $this->_reset_qc_reg(); } else { $this->_summary['qc_activated'] = $qc_activated; $this->save_summary(); } if ( 'cdn' === $qc_activated ) { $msg = sprintf( __( 'Congratulations, %s successfully set this domain up for the online services with CDN service.', 'litespeed-cache' ), 'QUIC.cloud' ); Admin_Display::success( '🎊 ' . $msg ); $this->_clear_reset_qc_reg_msg(); // Turn on CDN option $this->cls( 'Conf' )->update_confs( [ self::O_CDN_QUIC => true ] ); $this->cls( 'CDN\Quic' )->try_sync_conf( true ); } return self::ok( [ 'qc_activated' => $qc_activated ] ); } /** * Clear QC linked status * * @since 5.0 */ private function _reset_qc_reg() { unset( $this->_summary['qc_activated'] ); if ( ! empty( $this->_summary['partner'] ) ) { unset( $this->_summary['partner'] ); } self::save_summary(); $msg = $this->_reset_qc_reg_content(); Admin_Display::error( $msg, false, true ); } /** * Build reset QC registration content. * * @since 7.0 * @return string */ private function _reset_qc_reg_content() { $msg = __( 'Site not recognized. QUIC.cloud deactivated automatically. Please reactivate your QUIC.cloud account.', 'litespeed-cache' ); $msg .= Doc::learn_more( admin_url( 'admin.php?page=litespeed' ), __( 'Click here to proceed.', 'litespeed-cache' ), true, false, true ); $msg .= Doc::learn_more( 'https://docs.litespeedtech.com/lscache/lscwp/general/', false, false, false, true ); return $msg; } /** * Clear reset QC reg msg if exist * * @since 7.0 */ private function _clear_reset_qc_reg_msg() { self::debug( 'Removed pinned reset QC reg content msg' ); $msg = $this->_reset_qc_reg_content(); Admin_Display::dismiss_pin_by_content( $msg, Admin_Display::NOTICE_RED, true ); } } src/data_structure/url.sql000064400000000315152075713330011714 0ustar00`id` bigint(20) NOT NULL AUTO_INCREMENT, `url` varchar(500) NOT NULL, `cache_tags` varchar(1000) NOT NULL DEFAULT '', PRIMARY KEY (`id`), UNIQUE KEY `url` (`url`(191)), KEY `cache_tags` (`cache_tags`(191))src/data_structure/crawler_blacklist.sql000064400000000626152075713330014606 0ustar00 `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `url` varchar(1000) NOT NULL DEFAULT '', `res` varchar(255) NOT NULL DEFAULT '' COMMENT '-=Not Blacklist, B=blacklist', `reason` text NOT NULL COMMENT 'Reason for blacklist, comma separated', `mtime` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), PRIMARY KEY (`id`), KEY `url` (`url`(191)), KEY `res` (`res`) src/data_structure/img_optming.sql000064400000000526152075713330013427 0ustar00 `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `post_id` bigint(20) unsigned NOT NULL DEFAULT '0', `optm_status` tinyint(4) NOT NULL DEFAULT '0', `src` varchar(1000) NOT NULL DEFAULT '', `server_info` text NOT NULL, PRIMARY KEY (`id`), KEY `post_id` (`post_id`), KEY `optm_status` (`optm_status`), KEY `src` (`src`(191)) src/data_structure/avatar.sql000064400000000404152075713330012367 0ustar00 `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `url` varchar(1000) NOT NULL DEFAULT '', `md5` varchar(128) NOT NULL DEFAULT '', `dateline` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `md5` (`md5`), KEY `dateline` (`dateline`) src/data_structure/url_file.sql000064400000001210152075713330012706 0ustar00`id` bigint(20) NOT NULL AUTO_INCREMENT, `url_id` bigint(20) NOT NULL, `vary` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT 'md5 of final vary', `filename` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT 'md5 of file content', `type` tinyint(4) NOT NULL COMMENT 'css=1,js=2,ccss=3,ucss=4', `mobile` tinyint(4) NOT NULL COMMENT 'mobile=1', `webp` tinyint(4) NOT NULL COMMENT 'webp=1', `expired` int(11) NOT NULL DEFAULT 0, PRIMARY KEY (`id`), KEY `filename` (`filename`), KEY `type` (`type`), KEY `url_id_2` (`url_id`,`vary`,`type`), KEY `filename_2` (`filename`,`expired`), KEY `url_id` (`url_id`,`expired`) src/data_structure/crawler.sql000064400000000632152075713330012553 0ustar00 `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `url` varchar(1000) NOT NULL DEFAULT '', `res` varchar(255) NOT NULL DEFAULT '' COMMENT '-=not crawl, H=hit, M=miss, B=blacklist', `reason` text NOT NULL COMMENT 'response code, comma separated', `mtime` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), PRIMARY KEY (`id`), KEY `url` (`url`(191)), KEY `res` (`res`) src/data_structure/img_optm.sql000064400000000632152075713330012727 0ustar00 `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `post_id` bigint(20) unsigned NOT NULL DEFAULT '0', `optm_status` tinyint(4) NOT NULL DEFAULT '0', `src` text NOT NULL, `src_filesize` int(11) NOT NULL DEFAULT '0', `target_filesize` int(11) NOT NULL DEFAULT '0', `webp_filesize` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `post_id` (`post_id`), KEY `optm_status` (`optm_status`) src/vary.cls.php000064400000052522152075713330007621 0ustar00conf( Base::O_CACHE_LOGIN_COOKIE ); // network aware in v3.0. // If no vary set in rewrite rule. if ( ! isset( $_SERVER['LSCACHE_VARY_COOKIE'] ) ) { if ( $db_cookie ) { // Check for ESI no-vary control. $something_wrong = true; if ( ! empty( $_GET[ ESI::QS_ACTION ] ) && ! empty( $_GET['_control'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $control_raw = wp_unslash( (string) $_GET['_control'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $control = array_map( 'sanitize_text_field', explode( ',', $control_raw ) ); if ( in_array( 'no-vary', $control, true ) ) { self::debug( 'no-vary control existed, bypass vary_name update' ); $something_wrong = false; self::$_vary_name = $db_cookie; } } if ( defined( 'LITESPEED_CLI' ) || wp_doing_cron() ) { $something_wrong = false; } if ( $something_wrong ) { // Display cookie error msg to admin. if ( is_multisite() ? is_network_admin() : is_admin() ) { Admin_Display::show_error_cookie(); } Control::set_nocache( '❌❌ vary cookie setting error' ); } } return; } // DB setting does not exist – nothing to check. if ( ! $db_cookie ) { return; } // Beyond this point, ensure DB vary is present in $_SERVER env. $server_raw = wp_unslash( (string) $_SERVER['LSCACHE_VARY_COOKIE'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $vary_arr = array_map( 'trim', explode( ',', $server_raw ) ); if ( in_array( $db_cookie, $vary_arr, true ) ) { self::$_vary_name = $db_cookie; return; } if ( is_multisite() ? is_network_admin() : is_admin() ) { Admin_Display::show_error_cookie(); } Control::set_nocache( 'vary cookie setting lost error' ); } /** * Run after user init to set up vary/caching for current request. * * @since 4.0 * @return void */ public function after_user_init() { $this->_update_vary_name(); // Logged-in user. if ( Router::is_logged_in() ) { // If not ESI, check cache logged-in user setting. if ( ! $this->cls( 'Router' )->esi_enabled() ) { // Cache logged-in => private cache. if ( $this->conf( Base::O_CACHE_PRIV ) && ! is_admin() ) { add_action( 'wp_logout', __NAMESPACE__ . '\Purge::purge_on_logout' ); $this->cls( 'Control' )->init_cacheable(); Control::set_private( 'logged in user' ); } else { // No cache for logged-in user. Control::set_nocache( 'logged in user' ); } } elseif ( ! is_admin() ) { // ESI is on; can be public cache, but ensure cacheable is initialized. $this->cls( 'Control' )->init_cacheable(); } // Clear login state on logout. add_action( 'clear_auth_cookie', [ $this, 'remove_logged_in' ] ); } else { // Only after vary init we can detect guest mode. $this->_maybe_guest_mode(); // Set vary cookie when user logs in (to avoid guest vary). add_action( 'set_logged_in_cookie', [ $this, 'add_logged_in' ], 10, 4 ); add_action( 'wp_login', __NAMESPACE__ . '\Purge::purge_on_logout' ); $this->cls( 'Control' )->init_cacheable(); // Check login-page cacheable setting — login page doesn't go through main WP logic. add_action( 'login_init', [ $this->cls( 'Tag' ), 'check_login_cacheable' ], 5 ); // Optional lightweight guest vary updater. if ( ! empty( $_GET['litespeed_guest'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended add_action( 'wp_loaded', [ $this, 'update_guest_vary' ], 20 ); } } // Commenter checks. add_filter( 'comments_array', [ $this, 'check_commenter' ] ); // Set vary cookie for commenter. add_action( 'set_comment_cookies', [ $this, 'append_commenter' ] ); // REST: don't change vary because they don't carry on user info usually. add_action( 'rest_api_init', function () { self::debug( 'Rest API init disabled vary change' ); add_filter( 'litespeed_can_change_vary', '__return_false' ); } ); } /** * Mark request as Guest mode when applicable. * * @since 4.0 * @return void */ private function _maybe_guest_mode() { if ( defined( 'LITESPEED_GUEST' ) ) { self::debug( '👒👒 Guest mode ' . ( LITESPEED_GUEST ? 'predefined' : 'turned off' ) ); return; } if ( ! $this->conf( Base::O_GUEST ) ) { return; } // If vary is set, then not a guest. if ( self::has_vary() ) { return; } // Admin QS present? not a guest. if ( ! empty( $_GET[ Router::ACTION ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return; } if ( wp_doing_ajax() ) { return; } if ( wp_doing_cron() ) { return; } // Request to update vary? not a guest. if ( ! empty( $_GET['litespeed_guest'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return; } // User explicitly turned guest off. if ( ! empty( $_GET['litespeed_guest_off'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return; } self::debug( '👒👒 Guest mode' ); ! defined( 'LITESPEED_GUEST' ) && define( 'LITESPEED_GUEST', true ); if ( $this->conf( Base::O_GUEST_OPTM ) ) { ! defined( 'LITESPEED_GUEST_OPTM' ) && define( 'LITESPEED_GUEST_OPTM', true ); } } /** * Update Guest vary * * @since 4.0 * @deprecated 4.1 Use independent lightweight guest.vary.php instead. * @return void */ public function update_guest_vary() { // Must not be cached. ! defined( 'LSCACHE_NO_CACHE' ) && define( 'LSCACHE_NO_CACHE', true ); $_guest = new Lib\Guest(); if ( $_guest->always_guest() || self::has_vary() ) { // If contains vary already, don't reload (avoid loops). ! defined( 'LITESPEED_GUEST' ) && define( 'LITESPEED_GUEST', true ); self::debug( '🤠🤠 Guest' ); echo '[]'; exit; } self::debug( 'Will update guest vary in finalize' ); // Return JSON to trigger reload. echo wp_json_encode( [ 'reload' => 'yes' ] ); exit; } /** * Filter callback on `comments_array` to mark commenter state. * * @since 1.0.4 * * @param array $comments The comments to output. * @return array Filtered comments. */ public function check_commenter( $comments ) { /** * Allow bypassing pending comment check for comment plugins. * * @since 2.9.5 */ if ( apply_filters( 'litespeed_vary_check_commenter_pending', true ) ) { $pending = false; foreach ( $comments as $comment ) { if ( ! $comment->comment_approved ) { $pending = true; break; } } // No pending comments => ensure public cache state. if ( ! $pending ) { self::debug( 'No pending comment' ); $this->remove_commenter(); // Remove commenter prefilled info for public cache. foreach ( $_COOKIE as $cookie_name => $cookie_value ) { if ( strlen( $cookie_name ) >= 15 && 0 === strpos( $cookie_name, 'comment_author_' ) ) { unset( $_COOKIE[ $cookie_name ] ); } } return $comments; } } // Pending comments present — set commenter vary. $this->add_commenter(); if ( $this->conf( Base::O_CACHE_COMMENTER ) ) { Control::set_private( 'existing commenter' ); } else { Control::set_nocache( 'existing commenter' ); } return $comments; } /** * Check if default vary has a value * * @since 1.1.3 * * @return false|string Cookie value or false if missing. */ public static function has_vary() { if ( empty( $_COOKIE[ self::$_vary_name ] ) ) { return false; } // Cookie values are not user-displayed; unslash only. return wp_unslash( (string) $_COOKIE[ self::$_vary_name ] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized } /** * Append user status with logged-in. * * @since 1.1.3 * @since 1.6.2 Removed static referral. * * @param string|false $logged_in_cookie The logged-in cookie value. * @param int|false $expire Expiration timestamp. * @param int|false $expiration Unused (WordPress signature). * @param int|false $uid User ID. * @return void */ public function add_logged_in( $logged_in_cookie = false, $expire = false, $expiration = false, $uid = false ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed self::debug( 'add_logged_in' ); // Allow Ajax vary change during login flow. // NOTE: Run before `$this->_update_default_vary()` to make vary changeable self::can_ajax_vary(); // Ensure vary cookie exists/updated. $this->_update_default_vary( $uid, $expire ); } /** * Remove user logged-in status. * * @since 1.1.3 * @since 1.6.2 Removed static referral. * @return void */ public function remove_logged_in() { self::debug( 'remove_logged_in' ); // Allow Ajax vary change during logout flow. self::can_ajax_vary(); // Force update vary to remove login status. $this->_update_default_vary( -1 ); } /** * Allow vary to be changed for Ajax calls. * * @since 2.2.2 * @since 2.6 Changed to static. * @return void */ public static function can_ajax_vary() { self::debug( '_can_change_vary -> true' ); self::$_can_change_vary = true; } /** * Whether we can change the default vary right now. * * @since 1.6.2 * @return bool */ private function can_change_vary() { // Don't change on Ajax unless explicitly allowed (no webp header). if ( Router::is_ajax() && ! self::$_can_change_vary ) { self::debug( 'can_change_vary bypassed due to ajax call' ); return false; } // Allow only GET/POST. // POST request can set vary to fix #820789 login "loop" guest cache issue. if ( isset( $_SERVER['REQUEST_METHOD'] ) && 'GET' !== $_SERVER['REQUEST_METHOD'] && 'POST' !== $_SERVER['REQUEST_METHOD'] ) { self::debug( 'can_change_vary bypassed due to method not get/post' ); return false; } // Disable when crawler is making the request. if ( ! empty( $_SERVER['HTTP_USER_AGENT'] ) && 0 === strpos( wp_unslash( (string) $_SERVER['HTTP_USER_AGENT'] ), Crawler::FAST_USER_AGENT ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized ) { self::debug( 'can_change_vary bypassed due to crawler' ); return false; } if ( ! apply_filters( 'litespeed_can_change_vary', true ) ) { self::debug( 'can_change_vary bypassed due to litespeed_can_change_vary hook' ); return false; } return true; } /** * Update default vary cookie (idempotent within a request). * * @since 1.6.2 * @since 1.6.6.1 Guard to ensure single run. * * @param int|false $uid User ID or false. * @param int|false $expire Expiration timestamp (default: +2 days). * @return void */ private function _update_default_vary( $uid = false, $expire = false ) { // Ensure header output only runs once. if ( ! defined( 'LITESPEED_DID_' . __FUNCTION__ ) ) { define( 'LITESPEED_DID_' . __FUNCTION__, true ); } else { self::debug2( '_update_default_vary bypassed due to run already' ); return; } // ESI shouldn't change vary (main page only). if ( defined( 'LSCACHE_IS_ESI' ) && LSCACHE_IS_ESI ) { self::debug2( '_update_default_vary bypassed due to ESI' ); return; } $vary = $this->finalize_default_vary( $uid ); $current_vary = self::has_vary(); if ( $current_vary !== $vary && 'commenter' !== $current_vary && $this->can_change_vary() ) { if ( ! $expire ) { $expire = time() + 2 * DAY_IN_SECONDS; } $this->_cookie( $vary, (int) $expire ); } } /** * Get the current vary cookie name. * * @since 1.9.1 * @return string */ public function get_vary_name() { return self::$_vary_name; } /** * Check if a user role is in a configured vary group. * * @since 1.2.0 * @since 3.0 Moved here from conf.cls. * * @param string $role User role(s), comma-separated. * @return int|string Group ID or 0. */ public function in_vary_group( $role ) { $group = 0; $vary_groups = $this->conf( Base::O_CACHE_VARY_GROUP ); $roles = explode( ',', $role ); $found = array_intersect( $roles, array_keys( (array) $vary_groups ) ); if ( $found ) { $groups = []; foreach ( $found as $curr_role ) { $groups[] = $vary_groups[ $curr_role ]; } $group = implode( ',', array_unique( $groups ) ); } elseif ( in_array( 'administrator', $roles, true ) ) { $group = 99; } if ( $group ) { self::debug2( 'role in vary_group [group] ' . $group ); } return $group; } /** * Finalize default vary cookie value for current user. * NOTE: Login process will also call this because it does not call wp hook as normal page loading. * * @since 1.6.2 * * @param int|false $uid Optional user ID. * @return false|string False for guests when no vary needed, or hashed vary. */ public function finalize_default_vary( $uid = false ) { // Bypass vary for guests where applicable (avoid non-guest filenames for assets). if ( defined( 'LITESPEED_GUEST' ) && LITESPEED_GUEST ) { return false; } $vary = []; if ( $this->conf( Base::O_GUEST ) ) { $vary['guest_mode'] = 1; } if ( ! $uid ) { $uid = get_current_user_id(); } else { self::debug( 'uid: ' . $uid ); } // Get user role/group. $role = Router::get_role( $uid ); if ( $uid > 0 ) { $vary['logged-in'] = 1; if ( $role ) { // Parse role group from settings. $role_group = $this->in_vary_group( $role ); if ( $role_group ) { $vary['role'] = $role_group; } } // Admin bar preference. $pref = get_user_option( 'show_admin_bar_front', $uid ); self::debug2( 'show_admin_bar_front: ' . var_export( $pref, true ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export $admin_bar = ( false === $pref || 'true' === $pref ); if ( $admin_bar ) { $vary['admin_bar'] = 1; self::debug2( 'admin bar : true' ); } } else { self::debug( 'role id: failed, guest' ); } /** * Filter vary entries before hashing. * * @since 1.6 Added for Role Excludes for optimization cls * @since 1.6.2 Hooked to webp (legacy) * @since 3.0 Used by 3rd hooks too */ $vary = apply_filters( 'litespeed_vary', $vary ); if ( ! $vary ) { return false; } ksort( $vary ); $list = []; foreach ( $vary as $key => $val ) { $list[] = $key . ':' . $val; } $res = implode( ';', $list ); if ( defined( 'LSCWP_LOG' ) ) { return $res; } // Encrypt in production. return md5( $this->conf( Base::HASH ) . $res ); } /** * Get hash of all varies that affect caching (current cookies + default + env). * * @since 4.0 * @return string */ public function finalize_full_varies() { $vary = $this->_finalize_curr_vary_cookies( true ); $vary .= $this->finalize_default_vary( get_current_user_id() ); $vary .= $this->get_env_vary(); return $vary; } /** * Get request environment vary value (from server variables). * * @since 4.0 * @return string|false */ public function get_env_vary() { $env_vary = isset( $_SERVER['LSCACHE_VARY_VALUE'] ) ? wp_unslash( (string) $_SERVER['LSCACHE_VARY_VALUE'] ) : false; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized if ( ! $env_vary ) { $env_vary = isset( $_SERVER['HTTP_X_LSCACHE_VARY_VALUE'] ) ? wp_unslash( (string) $_SERVER['HTTP_X_LSCACHE_VARY_VALUE'] ) : false; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized } return $env_vary; } /** * Mark current user as commenter (called on comment submit). * * @since 1.1.6 * @return void */ public function append_commenter() { $this->add_commenter( true ); } /** * Add commenter vary (optionally from redirect). * * @since 1.1.3 * * @param bool $from_redirect Whether request is from redirect page. * @return void */ private function add_commenter( $from_redirect = false ) { // If the cookie is lost somehow, set it. if ( 'commenter' !== self::has_vary() ) { self::debug( 'Add commenter' ); // Save commenter status only for current domain path. $this->_cookie( 'commenter', time() + (int) apply_filters( 'comment_cookie_lifetime', 30000000 ), self::_relative_path( $from_redirect ) ); } } /** * Remove commenter vary if set. * * @since 1.1.3 * @return void */ private function remove_commenter() { if ( 'commenter' === self::has_vary() ) { self::debug( 'Remove commenter' ); $this->_cookie( false, false, self::_relative_path() ); } } /** * Generate a relative cookie path from current request. * * @since 1.1.3 * * @param bool $from_redirect When true, uses HTTP_REFERER; otherwise SCRIPT_URL. * @return string|false Path or false. */ private static function _relative_path( $from_redirect = false ) { $path = false; $tag = $from_redirect ? 'HTTP_REFERER' : 'SCRIPT_URL'; if ( ! empty( $_SERVER[ $tag ] ) ) { $parsed = wp_parse_url( wp_unslash( (string) $_SERVER[ $tag ] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $path = ! empty( $parsed['path'] ) ? $parsed['path'] : false; self::debug( 'Cookie Vary path: ' . ( $path ? $path : 'false' ) ); } return $path; } /** * Build the final X-LiteSpeed-Vary header for current request. * NOTE: Non caccheable page can still set vary ( for logged in process ). * * @since 1.0.13 * * @return string|void Header string or nothing when not needed. */ public function finalize() { // Finalize default vary for non-guest. if ( ! defined( 'LITESPEED_GUEST' ) || ! LITESPEED_GUEST ) { $this->_update_default_vary(); } $tp_cookies = $this->_finalize_curr_vary_cookies(); if ( ! $tp_cookies ) { self::debug2( 'no customized vary' ); return; } self::debug( 'finalized 3rd party cookies', $tp_cookies ); return self::X_HEADER . ': ' . implode( ',', $tp_cookies ); } /** * Get vary cookies (names or values JSON) added for current page. * * @since 1.0.13 * * @param bool $values_json When true, returns JSON array of cookie values; else cookie=name items. * @return array|string|false List of vary cookie items, JSON string, or false when none. */ private function _finalize_curr_vary_cookies( $values_json = false ) { global $post; $cookies = []; // No need to append default vary cookie name. if ( ! empty( $post->post_password ) ) { $postpass_key = 'wp-postpass_' . COOKIEHASH; if ( $this->_get_cookie_val( $postpass_key ) ) { self::debug( 'finalize bypassed due to password protected vary ' ); // If user has password cookie, do not cache & ignore existing vary cookies. Control::set_nocache( 'password protected vary' ); return false; } $cookies[] = $values_json ? $this->_get_cookie_val( $postpass_key ) : $postpass_key; } $cookies = apply_filters( 'litespeed_vary_curr_cookies', $cookies ); if ( $cookies ) { $cookies = array_filter( array_unique( $cookies ) ); self::debug( 'vary cookies changed by filter litespeed_vary_curr_cookies', $cookies ); } if ( ! $cookies ) { return false; } // Format cookie name data or value data. sort( $cookies ); // Maintain stable order for $values_json=true. foreach ( $cookies as $k => $v ) { $cookies[ $k ] = $values_json ? $this->_get_cookie_val( $v ) : 'cookie=' . $v; } return $values_json ? wp_json_encode( $cookies ) : $cookies; } /** * Get a cookie value safely. * * @since 4.0 * * @param string $key Cookie name. * @return false|string Cookie value or false. */ private function _get_cookie_val( $key ) { if ( ! empty( $_COOKIE[ $key ] ) ) { return wp_unslash( (string) $_COOKIE[ $key ] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized } return false; } /** * Set or clear the vary cookie. * * If the vary cookie changed, mark page as non-cacheable for this response. * * @since 1.0.4 * * @param int|false $val Cookie value to set, or false to clear. * @param int $expire Expiration timestamp (ignored when $val is false). * @param string $path Cookie path (false to use COOKIEPATH). * @return void */ private function _cookie( $val = false, $expire = 0, $path = false ) { if ( ! $val ) { $expire = 1; } // HTTPS bypass toggle for clients using both HTTP/HTTPS. $is_ssl = $this->conf( Base::O_UTIL_NO_HTTPS_VARY ) ? false : is_ssl(); setcookie( self::$_vary_name, $val, (int) $expire, $path ? $path : COOKIEPATH, COOKIE_DOMAIN, $is_ssl, true ); self::debug( 'set_cookie ---> [k] ' . self::$_vary_name . ' [v] ' . ( false === $val ? 'false' : $val ) . ' [ttl] ' . ( (int) $expire - time() ) ); } } src/object.lib.php000064400000034250152075713330010071 0ustar00add( $key, $data, $group, (int) $expire ); } /** * Adds multiple values to the cache in one call. * * @since 5.4 * @access public * @see WP_Object_Cache::add_multiple() * @global WP_Object_Cache $wp_object_cache Object cache global instance. * * @param array $data Array of keys and values to be set. * @param string $group Optional. Where the cache contents are grouped. Default empty. * @param int $expire Optional. When to expire the cache contents, in seconds. * Default 0 (no expiration). * @return bool[] Array of return values, grouped by key. Each value is either * true on success, or false if cache key and group already exist. */ function wp_cache_add_multiple( array $data, $group = '', $expire = 0 ) { global $wp_object_cache; return $wp_object_cache->add_multiple( $data, $group, $expire ); } /** * Replaces the contents of the cache with new data. * * @since 1.8 * @access public * @see WP_Object_Cache::replace() * @global WP_Object_Cache $wp_object_cache Object cache global instance. * * @param int|string $key The key for the cache data that should be replaced. * @param mixed $data The new data to store in the cache. * @param string $group Optional. The group for the cache data that should be replaced. * Default empty. * @param int $expire Optional. When to expire the cache contents, in seconds. * Default 0 (no expiration). * @return bool True if contents were replaced, false if original value does not exist. */ function wp_cache_replace( $key, $data, $group = '', $expire = 0 ) { global $wp_object_cache; return $wp_object_cache->replace( $key, $data, $group, (int) $expire ); } /** * Saves the data to the cache. * * Differs from wp_cache_add() and wp_cache_replace() in that it will always write data. * * @since 1.8 * @access public * @see WP_Object_Cache::set() * @global WP_Object_Cache $wp_object_cache Object cache global instance. * * @param int|string $key The cache key to use for retrieval later. * @param mixed $data The contents to store in the cache. * @param string $group Optional. Where to group the cache contents. Enables the same key * to be used across groups. Default empty. * @param int $expire Optional. When to expire the cache contents, in seconds. * Default 0 (no expiration). * @return bool True on success, false on failure. */ function wp_cache_set( $key, $data, $group = '', $expire = 0 ) { global $wp_object_cache; return $wp_object_cache->set( $key, $data, $group, (int) $expire ); } /** * Sets multiple values to the cache in one call. * * @since 5.4 * @access public * @see WP_Object_Cache::set_multiple() * @global WP_Object_Cache $wp_object_cache Object cache global instance. * * @param array $data Array of keys and values to be set. * @param string $group Optional. Where the cache contents are grouped. Default empty. * @param int $expire Optional. When to expire the cache contents, in seconds. * Default 0 (no expiration). * @return bool[] Array of return values, grouped by key. Each value is either * true on success, or false on failure. */ function wp_cache_set_multiple( array $data, $group = '', $expire = 0 ) { global $wp_object_cache; return $wp_object_cache->set_multiple( $data, $group, $expire ); } /** * Retrieves the cache contents from the cache by key and group. * * @since 1.8 * @access public * @see WP_Object_Cache::get() * @global WP_Object_Cache $wp_object_cache Object cache global instance. * * @param int|string $key The key under which the cache contents are stored. * @param string $group Optional. Where the cache contents are grouped. Default empty. * @param bool $force Optional. Whether to force an update of the local cache * from the persistent cache. Default false. * @param bool $found Optional. Whether the key was found in the cache (passed by reference). * Disambiguates a return of false, a storable value. Default null. * @return mixed|false The cache contents on success, false on failure to retrieve contents. */ function wp_cache_get( $key, $group = '', $force = false, &$found = null ) { global $wp_object_cache; return $wp_object_cache->get( $key, $group, $force, $found ); } /** * Retrieves multiple values from the cache in one call. * * @since 5.4 * @access public * @see WP_Object_Cache::get_multiple() * @global WP_Object_Cache $wp_object_cache Object cache global instance. * * @param array $keys Array of keys under which the cache contents are stored. * @param string $group Optional. Where the cache contents are grouped. Default empty. * @param bool $force Optional. Whether to force an update of the local cache * from the persistent cache. Default false. * @return array Array of return values, grouped by key. Each value is either * the cache contents on success, or false on failure. */ function wp_cache_get_multiple( $keys, $group = '', $force = false ) { global $wp_object_cache; return $wp_object_cache->get_multiple( $keys, $group, $force ); } /** * Removes the cache contents matching key and group. * * @since 1.8 * @access public * @see WP_Object_Cache::delete() * @global WP_Object_Cache $wp_object_cache Object cache global instance. * * @param int|string $key What the contents in the cache are called. * @param string $group Optional. Where the cache contents are grouped. Default empty. * @return bool True on successful removal, false on failure. */ function wp_cache_delete( $key, $group = '' ) { global $wp_object_cache; return $wp_object_cache->delete( $key, $group ); } /** * Deletes multiple values from the cache in one call. * * @since 5.4 * @access public * @see WP_Object_Cache::delete_multiple() * @global WP_Object_Cache $wp_object_cache Object cache global instance. * * @param array $keys Array of keys under which the cache to deleted. * @param string $group Optional. Where the cache contents are grouped. Default empty. * @return bool[] Array of return values, grouped by key. Each value is either * true on success, or false if the contents were not deleted. */ function wp_cache_delete_multiple( array $keys, $group = '' ) { global $wp_object_cache; return $wp_object_cache->delete_multiple( $keys, $group ); } /** * Increments numeric cache item's value. * * @since 1.8 * @access public * @see WP_Object_Cache::incr() * @global WP_Object_Cache $wp_object_cache Object cache global instance. * * @param int|string $key The key for the cache contents that should be incremented. * @param int $offset Optional. The amount by which to increment the item's value. * Default 1. * @param string $group Optional. The group the key is in. Default empty. * @return int|false The item's new value on success, false on failure. */ function wp_cache_incr( $key, $offset = 1, $group = '' ) { global $wp_object_cache; return $wp_object_cache->incr( $key, $offset, $group ); } /** * Decrements numeric cache item's value. * * @since 1.8 * @access public * @see WP_Object_Cache::decr() * @global WP_Object_Cache $wp_object_cache Object cache global instance. * * @param int|string $key The cache key to decrement. * @param int $offset Optional. The amount by which to decrement the item's value. * Default 1. * @param string $group Optional. The group the key is in. Default empty. * @return int|false The item's new value on success, false on failure. */ function wp_cache_decr( $key, $offset = 1, $group = '' ) { global $wp_object_cache; return $wp_object_cache->decr( $key, $offset, $group ); } /** * Removes all cache items. * * @since 1.8 * @access public * @see WP_Object_Cache::flush() * @global WP_Object_Cache $wp_object_cache Object cache global instance. * * @return bool True on success, false on failure. */ function wp_cache_flush() { global $wp_object_cache; return $wp_object_cache->flush(); } /** * Removes all cache items from the in-memory runtime cache. * * @since 5.4 * @access public * @see WP_Object_Cache::flush_runtime() * * @return bool True on success, false on failure. */ function wp_cache_flush_runtime() { global $wp_object_cache; return $wp_object_cache->flush_runtime(); } /** * Removes all cache items in a group, if the object cache implementation supports it. * * Before calling this function, always check for group flushing support using the * `wp_cache_supports( 'flush_group' )` function. * * @since 5.4 * @access public * @see WP_Object_Cache::flush_group() * @global WP_Object_Cache $wp_object_cache Object cache global instance. * * @param string $group Name of group to remove from cache. * @return bool True if group was flushed, false otherwise. */ function wp_cache_flush_group( $group ) { global $wp_object_cache; return $wp_object_cache->flush_group( $group ); } /** * Determines whether the object cache implementation supports a particular feature. * * @since 5.4 * @access public * * @param string $feature Name of the feature to check for. Possible values include: * 'add_multiple', 'set_multiple', 'get_multiple', 'delete_multiple', * 'flush_runtime', 'flush_group'. * @return bool True if the feature is supported, false otherwise. */ function wp_cache_supports( $feature ) { switch ( $feature ) { case 'add_multiple': case 'set_multiple': case 'get_multiple': case 'delete_multiple': case 'flush_runtime': return true; case 'flush_group': default: return false; } } /** * Closes the cache. * * This function has ceased to do anything since WordPress 2.5. The * functionality was removed along with the rest of the persistent cache. * * This does not mean that plugins can't implement this function when they need * to make sure that the cache is cleaned up after WordPress no longer needs it. * * @since 1.8 * @access public * * @return true Always returns true. */ function wp_cache_close() { return true; } /** * Adds a group or set of groups to the list of global groups. * * @since 1.8 * @access public * @see WP_Object_Cache::add_global_groups() * @global WP_Object_Cache $wp_object_cache Object cache global instance. * * @param string|string[] $groups A group or an array of groups to add. */ function wp_cache_add_global_groups( $groups ) { global $wp_object_cache; $wp_object_cache->add_global_groups( $groups ); } /** * Adds a group or set of groups to the list of non-persistent groups. * * @since 1.8 * @access public * * @param string|string[] $groups A group or an array of groups to add. */ function wp_cache_add_non_persistent_groups( $groups ) { global $wp_object_cache; $wp_object_cache->add_non_persistent_groups( $groups ); } /** * Switches the internal blog ID. * * This changes the blog id used to create keys in blog specific groups. * * @since 1.8 * @access public * @see WP_Object_Cache::switch_to_blog() * @global WP_Object_Cache $wp_object_cache Object cache global instance. * * @param int $blog_id Site ID. */ function wp_cache_switch_to_blog( $blog_id ) { global $wp_object_cache; $wp_object_cache->switch_to_blog( $blog_id ); } src/media.cls.php000064400000130126152075713330007714 0ustar00. * * @var array */ private $_vpi_preload_list = []; /** * The user-level next-gen format supported (''|webp|avif). * * @var string */ private $_format = ''; /** * The system-level chosen next-gen format (webp|avif). * * @var string */ private $_sys_format = ''; /** * Init. * * @since 1.4 */ public function __construct() { self::debug2( 'init' ); $this->_wp_upload_dir = wp_upload_dir(); if ( $this->conf( Base::O_IMG_OPTM_WEBP ) ) { $this->_sys_format = 'webp'; $this->_format = 'webp'; if ( 2 === $this->conf( Base::O_IMG_OPTM_WEBP ) ) { $this->_sys_format = 'avif'; $this->_format = 'avif'; } if ( ! $this->_browser_support_next_gen() ) { $this->_format = ''; } $this->_format = apply_filters( 'litespeed_next_gen_format', $this->_format ); } } /** * Hooks after user init. * * @since 7.2 * @since 7.4 Add media replace original with scaled. * @return void */ public function after_user_init() { // Hook to attachment delete action (PR#844, Issue#841) for AJAX del compatibility. add_action( 'delete_attachment', [ $this, 'delete_attachment' ], 11, 2 ); // For big images, allow to replace original with scaled image. if ( $this->conf( Base::O_MEDIA_AUTO_RESCALE_ORI ) ) { // Added priority 9 to happen before other functions added. add_filter( 'wp_update_attachment_metadata', [ $this, 'rescale_ori' ], 9, 2 ); } } /** * Init optm features. * * @since 3.0 * @access public * @return void */ public function init() { if ( is_admin() ) { return; } // Due to ajax call doesn't send correct accept header, have to limit webp to HTML only. if ( $this->webp_support() ) { // Hook to srcset. if ( function_exists( 'wp_calculate_image_srcset' ) ) { add_filter( 'wp_calculate_image_srcset', [ $this, 'webp_srcset' ], 988 ); } // Hook to mime icon // add_filter( 'wp_get_attachment_image_src', [ $this, 'webp_attach_img_src' ], 988 );// todo: need to check why not // add_filter( 'wp_get_attachment_url', [ $this, 'webp_url' ], 988 ); // disabled to avoid wp-admin display } if ( $this->conf( Base::O_MEDIA_LAZY ) && ! $this->cls( 'Metabox' )->setting( 'litespeed_no_image_lazy' ) ) { self::debug( 'Suppress default WP lazyload' ); add_filter( 'wp_lazy_loading_enabled', '__return_false' ); } /** * Replace gravatar. * * @since 3.0 */ $this->cls( 'Avatar' ); add_filter( 'litespeed_buffer_finalize', [ $this, 'finalize' ], 4 ); add_filter( 'litespeed_optm_html_head', [ $this, 'finalize_head' ] ); } /** * Handle attachment create (rescale original). * * @param array $metadata Current meta array. * @param int $attachment_id Attachment ID. * @return array Modified metadata. * @since 7.4 */ public function rescale_ori( $metadata, $attachment_id ) { // Test if create and image was resized. if ( $metadata && isset( $metadata['original_image'], $metadata['file'] ) && false !== strpos( $metadata['file'], '-scaled' ) ) { // Get rescaled file name. $path_exploded = explode( '/', strrev( $metadata['file'] ), 2 ); $rescaled_file_name = strrev( $path_exploded[0] ); // Create paths for images: resized and original. $base_path = $this->_wp_upload_dir['basedir'] . $this->_wp_upload_dir['subdir'] . '/'; $rescaled_path = $base_path . $rescaled_file_name; $new_path = $base_path . $metadata['original_image']; // Change array file key. $metadata['file'] = $this->_wp_upload_dir['subdir'] . '/' . $metadata['original_image']; if ( 0 === strpos( $metadata['file'], '/' ) ) { $metadata['file'] = substr( $metadata['file'], 1 ); } // Delete array "original_image" key. unset( $metadata['original_image'] ); if ( file_exists( $rescaled_path ) && file_exists( $new_path ) ) { // Move rescaled to original using WP_Filesystem. global $wp_filesystem; if ( ! $wp_filesystem ) { require_once ABSPATH . '/wp-admin/includes/file.php'; \WP_Filesystem(); } if ( $wp_filesystem ) { $wp_filesystem->move( $rescaled_path, $new_path, true ); } // Update meta "_wp_attached_file". update_post_meta( $attachment_id, '_wp_attached_file', $metadata['file'] ); } } return $metadata; } /** * Route media actions. * * @since 7.7 * @return void */ public function handler() { $type = Router::verify_type(); switch ( $type ) { case self::TYPE_BATCH_RESCALE_ORI: $this->_batch_rescale_ori(); break; default: break; } Admin::redirect(); } /** * Batch replace all scaled images with their originals. * * Follows the rm_bkup() pagination pattern. * * @since 7.7 * @access private */ private function _batch_rescale_ori() { global $wpdb; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $offset = ! empty( $_GET['litespeed_i'] ) ? absint( wp_unslash( $_GET['litespeed_i'] ) ) : 0; $limit = 500; $count = 0; $img_q = "SELECT a.ID, b.meta_value FROM `$wpdb->posts` a LEFT JOIN `$wpdb->postmeta` b ON b.post_id = a.ID WHERE b.meta_key = '_wp_attachment_metadata' AND a.post_type = 'attachment' AND a.post_status = 'inherit' AND a.post_mime_type IN ('image/jpeg', 'image/png', 'image/gif') ORDER BY a.ID LIMIT %d, %d "; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $list = $wpdb->get_results( $wpdb->prepare( $img_q, [ $offset * $limit, $limit ] ) ); foreach ( $list as $v ) { if ( ! $v->ID || ! $v->meta_value ) { continue; } // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged $meta_value = @maybe_unserialize( $v->meta_value ); if ( ! is_array( $meta_value ) ) { continue; } if ( empty( $meta_value['original_image'] ) || empty( $meta_value['file'] ) || false === strpos( $meta_value['file'], '-scaled' ) ) { continue; } $attachment_id = $v->ID; // Extract subdirectory from metadata file path (e.g. "2024/05/photo-scaled.jpg" → "2024/05"). $subdir = pathinfo( $meta_value['file'], PATHINFO_DIRNAME ); // Build relative paths for rename(). $scaled_filename = basename( $meta_value['file'] ); $scaled_path = $subdir . '/' . $scaled_filename; $original_path = $subdir . '/' . $meta_value['original_image']; // Verify scaled file exists before proceeding // TODO: need to ues isfile func to allow hook from offload plugins $basedir = $this->_wp_upload_dir['basedir'] . '/'; if ( ! file_exists( $basedir . $scaled_path ) ) { self::debug( 'Skipped: scaled file missing [pid] ' . $attachment_id ); continue; } // Move scaled file → original file using WP_Filesystem via rename(). $this->rename( $scaled_path, $original_path, $attachment_id ); // Update metadata: point file to original, remove original_image key. $meta_value['file'] = $subdir . '/' . $meta_value['original_image']; unset( $meta_value['original_image'] ); wp_update_attachment_metadata( $attachment_id, $meta_value ); update_post_meta( $attachment_id, '_wp_attached_file', $meta_value['file'] ); ++$count; } self::debug( 'batch_rescale_ori offset=' . $offset . ' processed=' . $count ); // Check if there are more rows to process. ++$offset; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $to_be_continued = $wpdb->get_row( $wpdb->prepare( $img_q, [ $offset * $limit, 1 ] ) ); if ( $to_be_continued ) { return Router::self_redirect( Router::ACTION_MEDIA, self::TYPE_BATCH_RESCALE_ORI ); } Admin_Display::success( sprintf( __( 'Batch rescale completed.', 'litespeed-cache' ) ) ); } /** * Add featured image and VPI preloads to head. * * @param string $content Current head HTML. * @return string Modified head HTML. */ public function finalize_head( $content ) { // if ( $this->_vpi_preload_list ) { foreach ( $this->_vpi_preload_list as $v ) { $content .= ''; } } return $content; } /** * Adjust WP default JPG quality. * * @since 3.0 * @access public * * @param int $quality Current quality. * @return int Adjusted quality. */ public function adjust_jpg_quality( $quality ) { $v = $this->conf( Base::O_IMG_OPTM_JPG_QUALITY ); if ( $v ) { return $v; } return $quality; } /** * Register admin menu. * * @since 1.6.3 * @access public * @return void */ public function after_admin_init() { /** * JPG quality control. * * @since 3.0 */ add_filter( 'jpeg_quality', [ $this, 'adjust_jpg_quality' ] ); add_filter( 'manage_media_columns', [ $this, 'media_row_title' ] ); add_filter( 'manage_media_custom_column', [ $this, 'media_row_actions' ], 10, 2 ); add_action( 'litespeed_media_row', [ $this, 'media_row_con' ] ); } /** * Media delete action hook. * * @since 2.4.3 * @access public * * @param int $post_id Post ID. * @return void */ public static function delete_attachment( $post_id ) { self::debug( 'delete_attachment [pid] ' . $post_id ); Img_Optm::cls()->reset_row( $post_id ); } /** * Return media file info if exists. * * This is for remote attachment plugins. * * @since 2.9.8 * @access public * * @param string $short_file_path Relative file path under uploads. * @param int $post_id Post ID. * @return array|false Array( url, md5, size ) or false. */ public function info( $short_file_path, $post_id ) { $short_file_path = wp_normalize_path( $short_file_path ); $basedir = $this->_wp_upload_dir['basedir'] . '/'; if ( 0 === strpos( $short_file_path, $basedir ) ) { $short_file_path = substr( $short_file_path, strlen( $basedir ) ); } $real_file = $basedir . $short_file_path; if ( file_exists( $real_file ) ) { return [ 'url' => $this->_wp_upload_dir['baseurl'] . '/' . $short_file_path, 'md5' => md5_file( $real_file ), 'size' => filesize( $real_file ), ]; } /** * WP Stateless compatibility #143 https://github.com/litespeedtech/lscache_wp/issues/143 * * @since 2.9.8 * Should return array( 'url', 'md5', 'size' ). */ $info = apply_filters( 'litespeed_media_info', [], $short_file_path, $post_id ); if ( ! empty( $info['url'] ) && ! empty( $info['md5'] ) && ! empty( $info['size'] ) ) { return $info; } return false; } /** * Delete media file. * * @since 2.9.8 * @access public * * @param string $short_file_path Relative file path under uploads. * @param int $post_id Post ID. * @return void */ public function del( $short_file_path, $post_id ) { $real_file = $this->_wp_upload_dir['basedir'] . '/' . $short_file_path; if ( file_exists( $real_file ) ) { wp_delete_file( $real_file ); self::debug( 'deleted ' . $real_file ); } do_action( 'litespeed_media_del', $short_file_path, $post_id ); } /** * Rename media file. * * @since 2.9.8 * @access public * * @param string $short_file_path Old relative path. * @param string $short_file_path_new New relative path. * @param int $post_id Post ID. * @return void */ public function rename( $short_file_path, $short_file_path_new, $post_id ) { $real_file = $this->_wp_upload_dir['basedir'] . '/' . $short_file_path; $real_file_new = $this->_wp_upload_dir['basedir'] . '/' . $short_file_path_new; if ( file_exists( $real_file ) ) { global $wp_filesystem; if ( ! $wp_filesystem ) { require_once ABSPATH . '/wp-admin/includes/file.php'; \WP_Filesystem(); } if ( $wp_filesystem ) { $wp_filesystem->move( $real_file, $real_file_new, true ); } self::debug( 'renamed ' . $real_file . ' to ' . $real_file_new ); } do_action( 'litespeed_media_rename', $short_file_path, $short_file_path_new, $post_id ); } /** * Media Admin Menu -> Image Optimization Column Title. * * @since 1.6.3 * @access public * * @param array $posts_columns Existing columns. * @return array Modified columns. */ public function media_row_title( $posts_columns ) { $posts_columns['imgoptm'] = esc_html__( 'LiteSpeed Optimization', 'litespeed-cache' ); return $posts_columns; } /** * Media Admin Menu -> Image Optimization Column. * * @since 1.6.2 * @access public * * @param string $column_name Current column name. * @param int $post_id Post ID. * @return void */ public function media_row_actions( $column_name, $post_id ) { if ( 'imgoptm' !== $column_name ) { return; } do_action( 'litespeed_media_row', $post_id ); } /** * Display image optimization info in the media list row. * * @since 3.0 * * @param int $post_id Attachment post ID. * @return void */ public function media_row_con( $post_id ) { $att_info = wp_get_attachment_metadata( $post_id ); if ( empty( $att_info['file'] ) ) { return; } $short_path = $att_info['file']; $size_meta = get_post_meta( $post_id, Img_Optm::DB_SIZE, true ); echo '

'; // Original image info. if ( $size_meta && ! empty( $size_meta['ori_saved'] ) ) { $percent = (int) ceil( ( (int) $size_meta['ori_saved'] * 100 ) / max( 1, (int) $size_meta['ori_total'] ) ); $extension = pathinfo( $short_path, PATHINFO_EXTENSION ); $bk_file = substr( $short_path, 0, -strlen( $extension ) ) . 'bk.' . $extension; $bk_optm_file = substr( $short_path, 0, -strlen( $extension ) ) . 'bk.optm.' . $extension; $link = Utility::build_url( Router::ACTION_IMG_OPTM, 'orig' . $post_id ); $desc = false; $cls = ''; if ( $this->info( $bk_file, $post_id ) ) { $curr_status = esc_html__( '(optm)', 'litespeed-cache' ); $desc = esc_attr__( 'Currently using optimized version of file.', 'litespeed-cache' ) . ' ' . esc_attr__( 'Click to switch to original (unoptimized) version.', 'litespeed-cache' ); } elseif ( $this->info( $bk_optm_file, $post_id ) ) { $cls .= ' litespeed-warning'; $curr_status = esc_html__( '(non-optm)', 'litespeed-cache' ); $desc = esc_attr__( 'Currently using original (unoptimized) version of file.', 'litespeed-cache' ) . ' ' . esc_attr__( 'Click to switch to optimized version.', 'litespeed-cache' ); } echo wp_kses( GUI::pie_tiny( $percent, 24, sprintf( esc_html__( 'Original file reduced by %1$s (%2$s)', 'litespeed-cache' ), $percent . '%', Utility::real_size( $size_meta['ori_saved'] ) ), 'left' ), GUI::allowed_svg_tags() ); printf( esc_html__( 'Orig saved %s', 'litespeed-cache' ), (int) $percent . '%' ); if ( $desc ) { printf( ' %4$s', esc_url( $link ), esc_attr( $cls ), wp_kses_post( $desc ), esc_html( $curr_status ) ); } else { printf( ' %2$s', esc_attr__( 'Using optimized version of file. ', 'litespeed-cache' ) . ' ' . esc_attr__( 'No backup of original file exists.', 'litespeed-cache' ), esc_html__( '(optm)', 'litespeed-cache' ) ); } } elseif ( $size_meta && 0 === (int) $size_meta['ori_saved'] ) { echo wp_kses( GUI::pie_tiny( 0, 24, esc_html__( 'Congratulation! Your file was already optimized', 'litespeed-cache' ), 'left' ), GUI::allowed_svg_tags() ); printf( esc_html__( 'Orig %s', 'litespeed-cache' ), '' . esc_html__( '(no savings)', 'litespeed-cache' ) . '' ); } else { echo esc_html__( 'Orig', 'litespeed-cache' ) . ''; } echo '

'; echo '

'; // WebP/AVIF info. if ( $size_meta && $this->webp_support( true ) && ! empty( $size_meta[ $this->_sys_format . '_saved' ] ) ) { $is_avif = 'avif' === $this->_sys_format; $size_meta_saved = $size_meta[ $this->_sys_format . '_saved' ]; $size_meta_total = $size_meta[ $this->_sys_format . '_total' ]; $percent = ceil( ( $size_meta_saved * 100 ) / max( 1, $size_meta_total ) ); $link = Utility::build_url( Router::ACTION_IMG_OPTM, $this->_sys_format . $post_id ); $desc = false; $cls = ''; if ( $this->info( $short_path . '.' . $this->_sys_format, $post_id ) ) { $curr_status = esc_html__( '(optm)', 'litespeed-cache' ); $desc = $is_avif ? esc_attr__( 'Currently using optimized version of AVIF file.', 'litespeed-cache' ) : esc_attr__( 'Currently using optimized version of WebP file.', 'litespeed-cache' ); $desc .= ' ' . esc_attr__( 'Click to switch to original (unoptimized) version.', 'litespeed-cache' ); } elseif ( $this->info( $short_path . '.optm.' . $this->_sys_format, $post_id ) ) { $cls .= ' litespeed-warning'; $curr_status = esc_html__( '(non-optm)', 'litespeed-cache' ); $desc = $is_avif ? esc_attr__( 'Currently using original (unoptimized) version of AVIF file.', 'litespeed-cache' ) : esc_attr__( 'Currently using original (unoptimized) version of WebP file.', 'litespeed-cache' ); $desc .= ' ' . esc_attr__( 'Click to switch to optimized version.', 'litespeed-cache' ); } echo wp_kses( GUI::pie_tiny( $percent, 24, sprintf( $is_avif ? esc_html__( 'AVIF file reduced by %1$s (%2$s)', 'litespeed-cache' ) : esc_html__( 'WebP file reduced by %1$s (%2$s)', 'litespeed-cache' ), $percent . '%', Utility::real_size( $size_meta_saved ) ), 'left' ), GUI::allowed_svg_tags() ); printf( $is_avif ? esc_html__( 'AVIF saved %s', 'litespeed-cache' ) : esc_html__( 'WebP saved %s', 'litespeed-cache' ), '' . esc_html( $percent ) . '%' ); if ( $desc ) { printf( ' %4$s', esc_url( $link ), esc_attr( $cls ), wp_kses_post( $desc ), esc_html( $curr_status ) ); } else { printf( ' %3$s', esc_attr__( 'Using optimized version of file. ', 'litespeed-cache' ), $is_avif ? esc_attr__( 'No backup of unoptimized AVIF file exists.', 'litespeed-cache' ) : esc_attr__( 'No backup of unoptimized WebP file exists.', 'litespeed-cache' ), esc_html__( '(optm)', 'litespeed-cache' ) ); } } else { echo esc_html( $this->next_gen_image_title() ) . ''; } echo '

'; // Delete row btn. if ( $size_meta ) { printf( '', esc_url( Utility::build_url( Router::ACTION_IMG_OPTM, Img_Optm::TYPE_RESET_ROW, false, null, [ 'id' => $post_id ] ) ), esc_html__( 'Restore from backup', 'litespeed-cache' ) ); echo '
'; } } /** * Get wp size info. * * NOTE: this is not used because it has to be after admin_init. * * @since 1.6.2 * @return array $sizes Data for all currently-registered image sizes. */ public function get_image_sizes() { global $_wp_additional_image_sizes; $sizes = []; foreach ( get_intermediate_image_sizes() as $_size ) { if ( in_array( $_size, [ 'thumbnail', 'medium', 'medium_large', 'large' ], true ) ) { $sizes[ $_size ]['width'] = get_option( $_size . '_size_w' ); $sizes[ $_size ]['height'] = get_option( $_size . '_size_h' ); $sizes[ $_size ]['crop'] = (bool) get_option( $_size . '_crop' ); } elseif ( isset( $_wp_additional_image_sizes[ $_size ] ) ) { $sizes[ $_size ] = [ 'width' => $_wp_additional_image_sizes[ $_size ]['width'], 'height' => $_wp_additional_image_sizes[ $_size ]['height'], 'crop' => $_wp_additional_image_sizes[ $_size ]['crop'], ]; } } return $sizes; } /** * Exclude role from optimization filter. * * @since 1.6.2 * @access public * * @param bool $sys_level Return system-level format if true. * @return string Next-gen format name or empty string. */ public function webp_support( $sys_level = false ) { if ( $sys_level ) { return $this->_sys_format; } return $this->_format; // User level next gen support. } /** * Detect if browser supports next-gen format. * * @return bool */ private function _browser_support_next_gen() { $accept = isset( $_SERVER['HTTP_ACCEPT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_ACCEPT'] ) ) : ''; if ( $accept ) { if ( false !== strpos( $accept, 'image/' . $this->_sys_format ) ) { return true; } } $ua = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : ''; if ( $ua ) { $user_agents = [ 'chrome-lighthouse', 'googlebot', 'page speed' ]; foreach ( $user_agents as $user_agent ) { if ( false !== stripos( $ua, $user_agent ) ) { return true; } } if ( preg_match( '/iPhone OS (\d+)_/i', $ua, $matches ) ) { if ( $matches[1] >= 14 ) { return true; } } if ( preg_match( '/Macintosh.+Version\/([0-9.]+)/i', $ua, $matches ) ) { if ( version_compare( $matches[1], '16.4', '>=' ) ) { return true; } } if ( preg_match( '/Firefox\/(\d+)/i', $ua, $matches ) ) { if ( $matches[1] >= 65 ) { return true; } } } return false; } /** * Get next gen image title. * * @since 7.0 * @return string */ public function next_gen_image_title() { $next_gen_img = 'WebP'; if ( 2 === $this->conf( Base::O_IMG_OPTM_WEBP ) ) { $next_gen_img = 'AVIF'; } return $next_gen_img; } /** * Run lazy load process. * NOTE: As this is after cache finalized, can NOT set any cache control anymore. * * Only do for main page. Do NOT do for esi or dynamic content. * * @since 1.4 * @access public * * @param string $content Final buffer. * @return string The buffer. */ public function finalize( $content ) { if ( defined( 'LITESPEED_NO_LAZY' ) ) { self::debug2( 'bypass: NO_LAZY const' ); return $content; } if ( ! defined( 'LITESPEED_IS_HTML' ) ) { self::debug2( 'bypass: Not frontend HTML type' ); return $content; } if ( ! Control::is_cacheable() ) { self::debug( 'bypass: Not cacheable' ); return $content; } self::debug( 'finalize' ); $this->content = $content; $this->_finalize(); return $this->content; } /** * Run lazyload replacement for images in buffer. * * @since 1.4 * @access private * @return void */ private function _finalize() { /** * Use webp for optimized images. * * @since 1.6.2 */ if ( $this->webp_support() ) { $this->content = $this->_replace_buffer_img_webp( $this->content ); } /** * Check if URI is excluded. * * @since 3.0 */ $excludes = $this->conf( Base::O_MEDIA_LAZY_URI_EXC ); if ( ! defined( 'LITESPEED_GUEST_OPTM' ) ) { $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : ''; $result = $request_uri ? Utility::str_hit_array( $request_uri, $excludes ) : false; if ( $result ) { self::debug( 'bypass lazyload: hit URI Excludes setting: ' . $result ); return; } } $cfg_lazy = ( defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( Base::O_MEDIA_LAZY ) ) && ! $this->cls( 'Metabox' )->setting( 'litespeed_no_image_lazy' ); $cfg_iframe_lazy = defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( Base::O_MEDIA_IFRAME_LAZY ); $cfg_js_delay = defined( 'LITESPEED_GUEST_OPTM' ) || 2 === $this->conf( Base::O_OPTM_JS_DEFER ); $cfg_trim_noscript = defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( Base::O_OPTM_NOSCRIPT_RM ); $cfg_vpi = defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( Base::O_MEDIA_VPI ); // Preload VPI. if ( $cfg_vpi ) { $this->_parse_img_for_preload(); } if ( $cfg_lazy ) { if ( $cfg_vpi ) { add_filter( 'litespeed_media_lazy_img_excludes', [ $this->cls( 'Metabox' ), 'lazy_img_excludes' ] ); } list( $src_list, $html_list, $placeholder_list ) = $this->_parse_img(); $html_list_ori = $html_list; } else { self::debug( 'lazyload disabled' ); } // image lazy load. if ( $cfg_lazy ) { $__placeholder = Placeholder::cls(); foreach ( $html_list as $k => $v ) { $size = $placeholder_list[ $k ]; $src = $src_list[ $k ]; $html_list[ $k ] = $__placeholder->replace( $v, $src, $size ); } } if ( $cfg_lazy ) { $this->content = str_replace( $html_list_ori, $html_list, $this->content ); } // iframe lazy load. if ( $cfg_iframe_lazy ) { $html_list = $this->_parse_iframe(); $html_list_ori = $html_list; foreach ( $html_list as $k => $v ) { $snippet = $cfg_trim_noscript ? '' : ''; if ( $cfg_js_delay ) { $v = str_replace( ' src=', ' data-litespeed-src=', $v ); } else { $v = str_replace( ' src=', ' data-src=', $v ); } $v = str_replace( '#isU', $content, $matches, PREG_SET_ORDER ); foreach ( $matches as $match ) { $attrs = Utility::parse_attr( $match[1] ); if ( empty( $attrs['src'] ) ) { continue; } self::debug2( 'found iframe: ' . $attrs['src'] ); if ( ! empty( $attrs['data-no-lazy'] ) || ! empty( $attrs['data-skip-lazy'] ) || ! empty( $attrs['data-lazyloaded'] ) || ! empty( $attrs['data-src'] ) ) { self::debug2( 'bypassed' ); continue; } $hit = ! empty( $attrs['class'] ) ? Utility::str_hit_array( $attrs['class'], $cls_excludes ) : false; if ( $hit ) { self::debug2( 'iframe lazyload cls excludes [hit] ' . $hit ); continue; } if ( apply_filters( 'litespeed_iframe_lazyload_exc', false, $attrs['src'] ) ) { self::debug2( 'bypassed by filter' ); continue; } // to avoid multiple replacement. if ( in_array( $match[0], $html_list, true ) ) { continue; } $html_list[] = $match[0]; } return $html_list; } /** * Replace image src to webp/avif in buffer. * * @since 1.6.2 * @access private * * @param string $content HTML content. * @return string Modified content. */ private function _replace_buffer_img_webp( $content ) { /** * Added custom element & attribute support. * * @since 2.2.2 */ $webp_ele_to_check = $this->conf( Base::O_IMG_OPTM_WEBP_ATTR ); foreach ( $webp_ele_to_check as $v ) { if ( ! $v || false === strpos( $v, '.' ) ) { self::debug2( 'buffer_webp no . attribute ' . $v ); continue; } self::debug2( 'buffer_webp attribute ' . $v ); $v = explode( '.', $v ); $attr = preg_quote( $v[1], '#' ); if ( $v[0] ) { $pattern = '#<' . preg_quote( $v[0], '#' ) . '([^>]+)' . $attr . '=([\'"])(.+)\2#iU'; } else { $pattern = '# ' . $attr . '=([\'"])(.+)\1#iU'; } preg_match_all( $pattern, $content, $matches ); foreach ( $matches[ $v[0] ? 3 : 2 ] as $k2 => $url ) { // Check if is a DATA-URI. if ( false !== strpos( $url, 'data:image' ) ) { continue; } $url2 = $this->replace_webp( $url ); if ( ! $url2 ) { continue; } if ( $v[0] ) { $html_snippet = sprintf( '<' . $v[0] . '%1$s' . $v[1] . '=%2$s', $matches[1][ $k2 ], $matches[2][ $k2 ] . $url2 . $matches[2][ $k2 ] ); } else { $html_snippet = sprintf( ' ' . $v[1] . '=%1$s', $matches[1][ $k2 ] . $url2 . $matches[1][ $k2 ] ); } $content = str_replace( $matches[0][ $k2 ], $html_snippet, $content ); } } // parse srcset. // todo: should apply this to cdn too. if ( ( defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( Base::O_IMG_OPTM_WEBP_REPLACE_SRCSET ) ) && $this->webp_support() ) { $content = Utility::srcset_replace( $content, [ $this, 'replace_webp' ] ); } // Replace background-image. if ( ( defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( Base::O_IMG_OPTM_WEBP ) ) && $this->webp_support() ) { $content = $this->replace_background_webp( $content ); } return $content; } /** * Replace background image in inline styles and JSON blobs. * * @since 4.0 * * @param string $content HTML content. * @return string Modified content. */ public function replace_background_webp( $content ) { self::debug2( 'Start replacing background WebP/AVIF.' ); // Handle Elementor's data-settings JSON encoded background-images. $content = $this->replace_urls_in_json( $content ); preg_match_all( '#url\(([^)]+)\)#iU', $content, $matches ); foreach ( $matches[1] as $k => $url ) { // Check if is a DATA-URI. if ( false !== strpos( $url, 'data:image' ) ) { continue; } /** * Support quotes in src `background-image: url('src')`. * * @since 2.9.3 */ $url = trim( $url, '\'"' ); // Fix Elementor's Slideshow unusual background images like style="background-image: url("https://xxxx.png");" if ( 0 === strpos( $url, '"' ) && '"' === substr( $url, -6 ) ) { $url = substr( $url, 6, -6 ); } $url2 = $this->replace_webp( $url ); if ( ! $url2 ) { continue; } $html_snippet = str_replace( $url, $url2, $matches[0][ $k ] ); $content = str_replace( $matches[0][ $k ], $html_snippet, $content ); } return $content; } /** * Replace images in json data settings attributes. * * @since 6.2 * * @param string $content HTML content to scan and modify. * @return string Modified content with replaced URLs inside JSON attributes. */ public function replace_urls_in_json( $content ) { $pattern = '/data-settings="(.*?)"/i'; $parent_class = $this; preg_match_all( $pattern, $content, $matches, PREG_SET_ORDER ); foreach ( $matches as $match ) { // Check if the string contains HTML entities. $is_encoded = preg_match( '/"|<|>|&|'/', $match[1] ); // Decode HTML entities in the JSON string. $json_string = html_entity_decode( $match[1] ); $json_data = \json_decode( $json_string, true ); if ( JSON_ERROR_NONE === json_last_error() && is_array( $json_data ) ) { $did_webp_replace = false; array_walk_recursive( $json_data, /** * Replace URLs in JSON data recursively. * * @param mixed $item Value (modified in place). * @param string $key Array key. */ function ( &$item, $key ) use ( &$did_webp_replace, $parent_class ) { if ( 'url' === $key ) { $item_image = $parent_class->replace_webp( $item ); if ( $item_image ) { $item = $item_image; $did_webp_replace = true; } } } ); if ( $did_webp_replace ) { // Re-encode the modified array back to a JSON string. $new_json_string = wp_json_encode( $json_data ); // Re-encode the JSON string to HTML entities only if it was originally encoded. if ( $is_encoded ) { $new_json_string = htmlspecialchars( $new_json_string, ENT_QUOTES | 0 ); // ENT_HTML401 for PHP>=5.4. } // Replace the old JSON string in the content with the new, modified JSON string. $content = str_replace( $match[1], $new_json_string, $content ); } } } return $content; } /** * Replace internal image src to webp or avif. * * @since 1.6.2 * @access public * * @param string $url Image URL. * @return string|false Replaced URL or false if not applicable. */ public function replace_webp( $url ) { if ( ! $this->webp_support() ) { self::debug2( 'No next generation format chosen in setting, bypassed' ); return false; } // Parse extension from URL path. $url_path = wp_parse_url( $url, PHP_URL_PATH ); $postfix = $url_path ? strtolower( pathinfo( $url_path, PATHINFO_EXTENSION ) ) : ''; // Only process image formats that can be converted to WebP/AVIF. if ( ! in_array( $postfix, [ 'jpg', 'jpeg', 'png', 'gif' ], true ) ) { self::debug2( 'not convertible format, bypassed' ); return false; } self::debug2( $this->_sys_format . ' replacing: ' . substr( $url, 0, 200 ) . ' [.' . $postfix . ']' ); /** * WebP/AVIF API hook. * NOTE: As $url may contain query strings, check filters which may parse_url before appending format. * * @since 2.9.5 * @see #751737 - API docs for WebP generation */ $ori_check = apply_filters( 'litespeed_media_check_ori', Utility::is_internal_file( $url ), $url ); if ( $ori_check ) { // check if has webp/avif file. $has_next = apply_filters( 'litespeed_media_check_webp', Utility::is_internal_file( $url, $this->_sys_format ), $url ); if ( $has_next ) { $url .= '.' . $this->_sys_format; } else { self::debug2( '-no WebP or AVIF file, bypassed' ); return false; } } else { self::debug2( '-no file, bypassed' ); return false; } self::debug2( '- replaced to: ' . $url ); return $url; } /** * Hook to wp_get_attachment_image_src. * * @since 1.6.2 * @access public * * @param array $img The URL, width, height array. * @return array */ public function webp_attach_img_src( $img ) { self::debug2( 'changing attach src: ' . $img[0] ); $url = $img ? $this->replace_webp( $img[0] ) : false; if ( $url ) { $img[0] = $url; } return $img; } /** * Try to replace img url. * * @since 1.6.2 * @access public * * @param string $url Image URL. * @return string */ public function webp_url( $url ) { $url2 = $url ? $this->replace_webp( $url ) : false; if ( $url2 ) { $url = $url2; } return $url; } /** * Hook to replace WP responsive images. * * @since 1.6.2 * @access public * * @param array $srcs Srcset array. * @return array */ public function webp_srcset( $srcs ) { if ( $srcs ) { foreach ( $srcs as $w => $data ) { $url = $this->replace_webp( $data['url'] ); if ( ! $url ) { continue; } $srcs[ $w ]['url'] = $url; } } return $srcs; } } src/root.cls.php000064400000034447152075713340007632 0ustar00conf(Base::O_CACHE_MOBILE); } /** * Log an error message * * @since 7.0 */ public static function debugErr( $msg, $backtrace_limit = false ) { $msg = '❌ ' . $msg; self::debug($msg, $backtrace_limit); } /** * Log a debug message. * * @since 4.4 * @access public */ public static function debug( $msg, $backtrace_limit = false ) { if (!defined('LSCWP_LOG')) { return; } if (defined('static::LOG_TAG')) { $msg = static::LOG_TAG . ' ' . $msg; } Debug2::debug($msg, $backtrace_limit); } /** * Log an advanced debug message. * * @since 4.4 * @access public */ public static function debug2( $msg, $backtrace_limit = false ) { if (!defined('LSCWP_LOG_MORE')) { return; } if (defined('static::LOG_TAG')) { $msg = static::LOG_TAG . ' ' . $msg; } Debug2::debug2($msg, $backtrace_limit); } /** * Check if there is cache folder for that type * * @since 3.0 */ public function has_cache_folder( $type ) { $subsite_id = is_multisite() && !is_network_admin() ? get_current_blog_id() : ''; if (file_exists(LITESPEED_STATIC_DIR . '/' . $type . '/' . $subsite_id)) { return true; } return false; } /** * Maybe make the cache folder if not existed * * @since 4.4.2 */ protected function _maybe_mk_cache_folder( $type ) { if (!$this->has_cache_folder($type)) { $subsite_id = is_multisite() && !is_network_admin() ? get_current_blog_id() : ''; $path = LITESPEED_STATIC_DIR . '/' . $type . '/' . $subsite_id; mkdir($path, 0755, true); } } /** * Delete file-based cache folder for that type * * @since 3.0 */ public function rm_cache_folder( $type ) { if (!$this->has_cache_folder($type)) { return; } $subsite_id = is_multisite() && !is_network_admin() ? get_current_blog_id() : ''; File::rrmdir(LITESPEED_STATIC_DIR . '/' . $type . '/' . $subsite_id); // Clear All summary data self::save_summary(false, false, true); if ($type == 'ccss' || $type == 'ucss') { Debug2::debug('[CSS] Cleared ' . $type . ' queue'); } elseif ($type == 'avatar') { Debug2::debug('[Avatar] Cleared ' . $type . ' queue'); } elseif ($type == 'css' || $type == 'js') { return; } else { Debug2::debug('[' . strtoupper($type) . '] Cleared ' . $type . ' queue'); } } /** * Build the static filepath * * @since 4.0 */ protected function _build_filepath_prefix( $type ) { $filepath_prefix = '/' . $type . '/'; if (is_multisite()) { $filepath_prefix .= get_current_blog_id() . '/'; } return $filepath_prefix; } /** * Load current queues from data file * * @since 4.1 * @since 4.3 Elevated to root.cls */ public function load_queue( $type ) { $filepath_prefix = $this->_build_filepath_prefix($type); $static_path = LITESPEED_STATIC_DIR . $filepath_prefix . '.litespeed_conf.dat'; $queue = array(); if (file_exists($static_path)) { $queue = \json_decode(file_get_contents($static_path), true) ?: array(); } return $queue; } /** * Save current queues to data file * * @since 4.1 * @since 4.3 Elevated to root.cls */ public function save_queue( $type, $list ) { $filepath_prefix = $this->_build_filepath_prefix($type); $static_path = LITESPEED_STATIC_DIR . $filepath_prefix . '.litespeed_conf.dat'; $data = \json_encode($list); File::save($static_path, $data, true); } /** * Clear all waiting queues * * @since 3.4 * @since 4.3 Elevated to root.cls */ public function clear_q( $type, $silent = false ) { $filepath_prefix = $this->_build_filepath_prefix($type); $static_path = LITESPEED_STATIC_DIR . $filepath_prefix . '.litespeed_conf.dat'; if (file_exists($static_path)) { $silent = false; unlink($static_path); } if (!$silent) { $msg = __('All QUIC.cloud service queues have been cleared.', 'litespeed-cache'); Admin_Display::success($msg); } } /** * Load an instance or create it if not existed * * @since 4.0 */ public static function cls( $cls = false, $unset = false, $data = false ) { if (!$cls) { $cls = self::ori_cls(); } $cls = __NAMESPACE__ . '\\' . $cls; $cls_tag = strtolower($cls); if (!isset(self::$_instances[$cls_tag])) { if ($unset) { return; } self::$_instances[$cls_tag] = new $cls($data); } elseif ($unset) { unset(self::$_instances[$cls_tag]); return; } return self::$_instances[$cls_tag]; } /** * Set one conf or confs */ public function set_conf( $id, $val = null ) { if (is_array($id)) { foreach ($id as $k => $v) { $this->set_conf($k, $v); } return; } self::$_options[$id] = $val; } /** * Set one primary conf or confs */ public function set_primary_conf( $id, $val = null ) { if (is_array($id)) { foreach ($id as $k => $v) { $this->set_primary_conf($k, $v); } return; } self::$_primary_options[$id] = $val; } /** * Set one network conf */ public function set_network_conf( $id, $val = null ) { if (is_array($id)) { foreach ($id as $k => $v) { $this->set_network_conf($k, $v); } return; } self::$_network_options[$id] = $val; } /** * Set one const conf */ public function set_const_conf( $id, $val ) { self::$_const_options[$id] = $val; } /** * Check if is overwritten by const * * @since 3.0 */ public function const_overwritten( $id ) { if (!isset(self::$_const_options[$id]) || self::$_const_options[$id] == self::$_options[$id]) { return null; } return self::$_const_options[$id]; } /** * Check if is overwritten by primary site * * @since 3.2.2 */ public function primary_overwritten( $id ) { if (!isset(self::$_primary_options[$id]) || self::$_primary_options[$id] == self::$_options[$id]) { return null; } // Network admin settings is impossible to be overwritten by primary if (is_network_admin()) { return null; } return self::$_primary_options[$id]; } /** * Check if is overwritten by filter. * * @since 7.7 */ public function filter_overwritten( $id ) { $val_setting = $this->conf($id, true); // if setting not found if( null === $val_setting ){ return null; } $filter_name = 'litespeed_conf_load_option_' . $id; $val_filter = apply_filters($filter_name, $val_setting ); if ($val_setting === $val_filter) { // If the value is the same, return null. return null; } return $val_filter; } /** * Check if is overwritten by code filter * * @deprecated 7.7 Use general filter_overwritten() * @since 7.4 */ public function deprecated_filter_overwritten( $id ) { $cls_admin_display = Admin_Display::$settings_filters; // Check if filter name is set. if(!isset($cls_admin_display[$id]) || !isset($cls_admin_display[$id]['filter']) || is_array($cls_admin_display[$id]['filter']) ){ return null; } $val_setting = $this->conf($id, true); // if setting not found if( null === $val_setting ){ $val_setting = ''; } $val_filter = apply_filters($cls_admin_display[$id]['filter'], $val_setting ); if ($val_setting === $val_filter) { // If the value is the same, return null. return null; } return $val_filter; } /** * Check if is overwritten by $SERVER variable * * @since 7.4 */ public function server_overwritten( $id ) { $cls_admin_display = Admin_Display::$settings_filters; if(!isset($cls_admin_display[$id]['filter'])){ return null; } if(!is_array($cls_admin_display[$id]['filter'])) { $cls_admin_display[$id]['filter'] = array( $cls_admin_display[$id]['filter'] ); } foreach( $cls_admin_display[$id]['filter'] as $variable ){ if(isset($_SERVER[$variable])) { return [ $variable , $_SERVER[$variable] ] ; } } return null; } /** * Get the list of configured options for the blog. * * @since 1.0 */ public function get_options( $ori = false ) { if (!$ori) { return array_merge(self::$_options, self::$_primary_options, self::$_network_options, self::$_const_options); } return self::$_options; } /** * If has a conf or not */ public function has_conf( $id ) { return array_key_exists($id, self::$_options); } /** * If has a primary conf or not */ public function has_primary_conf( $id ) { return array_key_exists($id, self::$_primary_options); } /** * If has a network conf or not */ public function has_network_conf( $id ) { return array_key_exists($id, self::$_network_options); } /** * Get conf */ public function conf( $id, $ori = false ) { if (isset(self::$_options[$id])) { if (!$ori) { $val = $this->const_overwritten($id); if ($val !== null) { defined('LSCWP_LOG') && Debug2::debug('[Conf] 🏛️ const option ' . $id . '=' . var_export($val, true)); return $val; } $val = $this->primary_overwritten($id); // Network Use primary site settings if ($val !== null) { return $val; } $val = $this->filter_overwritten($id); if ($val !== null) { return $val; } } // Network original value will be in _network_options if (!is_network_admin() || !$this->has_network_conf($id)) { return self::$_options[$id]; } } if ($this->has_network_conf($id)) { if (!$ori) { $val = $this->const_overwritten($id); if ($val !== null) { defined('LSCWP_LOG') && Debug2::debug('[Conf] 🏛️ const option ' . $id . '=' . var_export($val, true)); return $val; } } return $this->network_conf($id); } defined('LSCWP_LOG') && Debug2::debug('[Conf] Invalid option ID ' . $id); return null; } /** * Get primary conf */ public function primary_conf( $id ) { return self::$_primary_options[$id]; } /** * Get network conf */ public function network_conf( $id ) { if (!$this->has_network_conf($id)) { return null; } return self::$_network_options[$id]; } /** * Get called class short name */ public static function ori_cls() { $cls = new \ReflectionClass(get_called_class()); $shortname = $cls->getShortName(); $namespace = str_replace(__NAMESPACE__ . '\\', '', $cls->getNamespaceName() . '\\'); if ($namespace) { // the left namespace after dropped LiteSpeed $shortname = $namespace . $shortname; } return $shortname; } /** * Generate conf name for wp_options record * * @since 3.0 */ public static function name( $id ) { $name = strtolower(self::ori_cls()); return 'litespeed.' . $name . '.' . $id; } /** * Dropin with prefix for WP's get_option * * @since 3.0 */ public static function get_option( $id, $default_v = false ) { $v = get_option(self::name($id), $default_v); // Maybe decode array if (is_array($default_v)) { $v = self::_maybe_decode($v); } return $v; } /** * Dropin with prefix for WP's get_site_option * * @since 3.0 */ public static function get_site_option( $id, $default_v = false ) { $v = get_site_option(self::name($id), $default_v); // Maybe decode array if (is_array($default_v)) { $v = self::_maybe_decode($v); } return $v; } /** * Dropin with prefix for WP's add_option * * @since 3.0 */ public static function add_option( $id, $v ) { add_option(self::name($id), self::_maybe_encode($v)); } /** * Dropin with prefix for WP's add_site_option * * @since 3.0 */ public static function add_site_option( $id, $v ) { add_site_option(self::name($id), self::_maybe_encode($v)); } /** * Dropin with prefix for WP's update_option * * @since 3.0 */ public static function update_option( $id, $v ) { update_option(self::name($id), self::_maybe_encode($v)); } /** * Dropin with prefix for WP's update_site_option * * @since 3.0 */ public static function update_site_option( $id, $v ) { update_site_option(self::name($id), self::_maybe_encode($v)); } /** * Decode an array * * @since 4.0 */ protected static function _maybe_decode( $v ) { if (!is_array($v)) { $v2 = \json_decode($v, true); if ($v2 !== null) { $v = $v2; } } return $v; } /** * Encode an array * * @since 4.0 */ private static function _maybe_encode( $v ) { if (is_array($v)) { $v = \json_encode($v) ?: $v; // Non utf-8 encoded value will get failed, then used ori value } return $v; } /** * Dropin with prefix for WP's delete_option * * @since 3.0 */ public static function delete_option( $id ) { delete_option(self::name($id)); } /** * Dropin with prefix for WP's delete_site_option * * @since 3.0 */ public static function delete_site_option( $id ) { delete_site_option(self::name($id)); } /** * Read summary * * @since 3.0 * @access public */ public static function get_summary( $field = false ) { $summary = self::get_option('_summary', array()); if (!is_array($summary)) { $summary = array(); } if (!$field) { return $summary; } if (array_key_exists($field, $summary)) { return $summary[$field]; } return null; } /** * Save summary * * @since 3.0 * @access public */ public static function save_summary( $data = false, $reload = false, $overwrite = false ) { if ($reload || empty(static::cls()->_summary)) { self::reload_summary(); } $existing_summary = static::cls()->_summary; if ($overwrite || !is_array($existing_summary)) { $existing_summary = array(); } $new_summary = array_merge($existing_summary, $data ?: array()); // self::debug2('Save after Reloaded summary', $new_summary); static::cls()->_summary = $new_summary; self::update_option('_summary', $new_summary); } /** * Reload summary * * @since 5.0 */ public static function reload_summary() { static::cls()->_summary = self::get_summary(); // self::debug2( 'Reloaded summary', static::cls()->_summary ); } /** * Get the current instance object. To be inherited. * * @since 3.0 */ public static function get_instance() { return static::cls(); } } src/ucss.cls.php000064400000040543152075713340007616 0ustar00_summary = self::get_summary(); add_filter( 'litespeed_ucss_whitelist', [ $this->cls( 'Data' ), 'load_ucss_whitelist' ] ); } /** * Uniform url tag for ucss usage * * @since 4.7 * * @param string|false $request_url The request URL. * @return string The URL tag. */ public static function get_url_tag( $request_url = false ) { $url_tag = $request_url; if (is_404()) { $url_tag = '404'; } elseif (apply_filters('litespeed_ucss_per_pagetype', false)) { $url_tag = Utility::page_type(); self::debug('litespeed_ucss_per_pagetype filter altered url to ' . $url_tag); } return $url_tag; } /** * Get UCSS path * * @since 4.0 * * @param string $request_url The request URL. * @param bool $dry_run Whether to run in dry mode. * @return string|false The UCSS filename or false. */ public function load( $request_url, $dry_run = false ) { // Check UCSS URI excludes $ucss_exc = apply_filters( 'litespeed_ucss_exc', $this->conf( self::O_OPTM_UCSS_EXC ) ); $hit = $ucss_exc ? Utility::str_hit_array( $request_url, $ucss_exc ) : false; if ( $hit ) { self::debug( 'UCSS bypassed due to UCSS URI Exclude setting: ' . $hit ); Core::comment( 'QUIC.cloud UCSS bypassed by setting' ); return false; } $filepath_prefix = $this->_build_filepath_prefix('ucss'); $url_tag = self::get_url_tag($request_url); $vary = $this->cls('Vary')->finalize_full_varies(); $filename = $this->cls('Data')->load_url_file($url_tag, $vary, 'ucss'); if ($filename) { $static_file = LITESPEED_STATIC_DIR . $filepath_prefix . $filename . '.css'; if (file_exists($static_file)) { self::debug2('existing ucss ' . $static_file); // Check if is error comment inside only $tmp = File::read($static_file); if ( '/*' === substr( $tmp, 0, 2 ) && '*/' === substr( trim( $tmp ), -2 ) ) { self::debug2('existing ucss is error only: ' . $tmp); Core::comment('QUIC.cloud UCSS bypassed due to generation error ❌ ' . $filepath_prefix . $filename . '.css'); return false; } Core::comment('QUIC.cloud UCSS loaded ✅ ' . $filepath_prefix . $filename . '.css' ); return $filename . '.css'; } } if ($dry_run) { return false; } Core::comment('QUIC.cloud UCSS in queue'); $uid = get_current_user_id(); $ua = $this->_get_ua(); // Store it for cron $this->_queue = $this->load_queue('ucss'); if (count($this->_queue) > 500) { self::debug('UCSS Queue is full - 500'); return false; } $queue_k = (strlen($vary) > 32 ? md5($vary) : $vary) . ' ' . $url_tag; $this->_queue[ $queue_k ] = [ 'url' => apply_filters( 'litespeed_ucss_url', $request_url ), 'user_agent' => substr( $ua, 0, 200 ), 'is_mobile' => $this->_separate_mobile(), 'is_webp' => $this->cls( 'Media' )->webp_support() ? 1 : 0, 'uid' => $uid, 'vary' => $vary, 'url_tag' => $url_tag, ]; // Current UA will be used to request $this->save_queue('ucss', $this->_queue); self::debug('Added queue_ucss [url_tag] ' . $url_tag . ' [UA] ' . $ua . ' [vary] ' . $vary . ' [uid] ' . $uid); // Prepare cache tag for later purge Tag::add('UCSS.' . md5($queue_k)); return false; } /** * Get User Agent * * @since 5.3 * * @return string The user agent string. */ private function _get_ua() { return ! empty( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : ''; } /** * Add rows to q * * @since 5.3 * * @param array $url_files Array of URL file data. * @return false|void False if queue is full. */ public function add_to_q( $url_files ) { // Store it for cron $this->_queue = $this->load_queue('ucss'); if (count($this->_queue) > 500) { self::debug('UCSS Queue is full - 500'); return false; } $ua = $this->_get_ua(); foreach ($url_files as $url_file) { $vary = $url_file['vary']; $request_url = $url_file['url']; $is_mobile = $url_file['mobile']; $is_webp = $url_file['webp']; $url_tag = self::get_url_tag($request_url); $queue_k = (strlen($vary) > 32 ? md5($vary) : $vary) . ' ' . $url_tag; $q = [ 'url' => apply_filters( 'litespeed_ucss_url', $request_url ), 'user_agent' => substr( $ua, 0, 200 ), 'is_mobile' => $is_mobile, 'is_webp' => $is_webp, 'uid' => false, 'vary' => $vary, 'url_tag' => $url_tag, ]; // Current UA will be used to request self::debug('Added queue_ucss [url_tag] ' . $url_tag . ' [UA] ' . $ua . ' [vary] ' . $vary . ' [uid] false'); $this->_queue[$queue_k] = $q; } $this->save_queue('ucss', $this->_queue); } /** * Generate UCSS * * @since 4.0 * * @param bool $keep_going Whether to continue processing. * @return mixed The cron handler result. */ public static function cron( $keep_going = false ) { $_instance = self::cls(); return $_instance->_cron_handler( $keep_going ); } /** * Handle UCSS cron * * @since 4.2 * * @param bool $keep_going Whether to continue processing. * @return mixed The redirect result or void. */ private function _cron_handler( $keep_going ) { $this->_queue = $this->load_queue( 'ucss' ); if ( empty( $this->_queue ) ) { return; } // For cron, need to check request interval too if ( ! $keep_going ) { if (!empty($this->_summary['curr_request']) && time() - $this->_summary['curr_request'] < 300 && !$this->conf(self::O_DEBUG)) { self::debug('Last request not done'); return; } } $i = 0; foreach ($this->_queue as $k => $v) { if (!empty($v['_status'])) { continue; } self::debug('cron job [tag] ' . $k . ' [url] ' . $v['url'] . ($v['is_mobile'] ? ' 📱 ' : '') . ' [UA] ' . $v['user_agent']); if (!isset($v['is_webp'])) { $v['is_webp'] = false; } ++$i; $res = $this->_send_req($v['url'], $k, $v['uid'], $v['user_agent'], $v['vary'], $v['url_tag'], $v['is_mobile'], $v['is_webp']); if (!$res) { // Status is wrong, drop this this->_queue $this->_queue = $this->load_queue('ucss'); unset($this->_queue[$k]); $this->save_queue('ucss', $this->_queue); if ( ! $keep_going ) { return; } if ( $i > 3 ) { GUI::print_loading( count( $this->_queue ), 'UCSS' ); return Router::self_redirect( Router::ACTION_UCSS, self::TYPE_GEN ); } continue; } // Exit queue if out of quota or service is hot if ( 'out_of_quota' === $res || 'svc_hot' === $res ) { return; } $this->_queue = $this->load_queue( 'ucss' ); $this->_queue[ $k ]['_status'] = 'requested'; $this->save_queue( 'ucss', $this->_queue ); self::debug( 'Saved to queue [k] ' . $k ); // only request first one if ( ! $keep_going ) { return; } if ($i > 3) { GUI::print_loading(count($this->_queue), 'UCSS'); return Router::self_redirect(Router::ACTION_UCSS, self::TYPE_GEN); } } } /** * Send to QC API to generate UCSS * * @since 2.3 * @access private * * @param string $request_url The request URL. * @param string $queue_k The queue key. * @param int|false $uid The user ID. * @param string $user_agent The user agent. * @param string $vary The vary string. * @param string $url_tag The URL tag. * @param bool $is_mobile Whether is mobile. * @param bool $is_webp Whether supports webp. * @return string|bool|null The result status. */ private function _send_req( $request_url, $queue_k, $uid, $user_agent, $vary, $url_tag, $is_mobile, $is_webp ) { // Check if has credit to push or not $err = false; $allowance = $this->cls('Cloud')->allowance(Cloud::SVC_UCSS, $err); if (!$allowance) { self::debug('❌ No credit: ' . $err); $err && Admin_Display::error(Error::msg($err)); return 'out_of_quota'; } set_time_limit(120); // Update css request status $this->_summary['curr_request'] = time(); self::save_summary(); // Gather guest HTML to send $html = $this->cls('CSS')->prepare_html($request_url, $user_agent, $uid); if (!$html) { return false; } // Parse HTML to gather all CSS content before requesting $css = false; list(, $html) = $this->prepare_css($html, $is_webp, true); // Use this to drop CSS from HTML as we don't need those CSS to generate UCSS $filename = $this->cls('Data')->load_url_file($url_tag, $vary, 'css'); $filepath_prefix = $this->_build_filepath_prefix('css'); $static_file = LITESPEED_STATIC_DIR . $filepath_prefix . $filename . '.css'; self::debug('Checking combined file ' . $static_file); if (file_exists($static_file)) { $css = File::read($static_file); } if (!$css) { self::debug('❌ No combined css'); return false; } $data = [ 'url' => $request_url, 'queue_k' => $queue_k, 'user_agent' => $user_agent, 'is_mobile' => $is_mobile ? 1 : 0, // todo:compatible w/ tablet 'is_webp' => $is_webp ? 1 : 0, 'html' => $html, 'css' => $css, ]; if (!isset($this->_ucss_whitelist)) { $this->_ucss_whitelist = $this->_filter_whitelist(); } $data['whitelist'] = $this->_ucss_whitelist; self::debug('Generating: ', $data); $json = Cloud::post(Cloud::SVC_UCSS, $data, 30); if (!is_array($json)) { return $json; } // Old version compatibility if (empty($json['status'])) { if (!empty($json['ucss'])) { $this->_save_con('ucss', $json['ucss'], $queue_k, $is_mobile, $is_webp); } // Delete the row return false; } // Unknown status, remove this line if ( 'queued' !== $json['status'] ) { return false; } // Save summary data $this->_summary['last_spent'] = time() - $this->_summary['curr_request']; $this->_summary['last_request'] = $this->_summary['curr_request']; $this->_summary['curr_request'] = 0; self::save_summary(); return true; } /** * Save UCSS content * * @since 4.2 * * @param string $type The content type. * @param string $css The CSS content. * @param string $queue_k The queue key. * @param bool $is_mobile Whether is mobile. * @param bool $is_webp Whether supports webp. */ private function _save_con( $type, $css, $queue_k, $is_mobile, $is_webp ) { // Add filters $css = apply_filters('litespeed_' . $type, $css, $queue_k); // Sanitize: CSS must not contain HTML tags $css = wp_strip_all_tags( $css ); self::debug2('con: ', $css); if ( '/*' === substr( $css, 0, 2 ) && '*/' === substr( $css, -2 ) ) { self::debug('❌ empty ' . $type . ' [content] ' . $css); // continue; // Save the error info too } // Write to file $filecon_md5 = md5($css); $filepath_prefix = $this->_build_filepath_prefix($type); $static_file = LITESPEED_STATIC_DIR . $filepath_prefix . $filecon_md5 . '.css'; File::save($static_file, $css, true); $url_tag = $this->_queue[$queue_k]['url_tag']; $vary = $this->_queue[$queue_k]['vary']; self::debug2("Save URL to file [file] $static_file [vary] $vary"); $this->cls('Data')->save_url($url_tag, $vary, $type, $filecon_md5, dirname($static_file), $is_mobile, $is_webp); Purge::add(strtoupper($type) . '.' . md5($queue_k)); } /** * Prepare CSS from HTML for CCSS generation only. UCSS will used combined CSS directly. * Prepare refined HTML for both CCSS and UCSS. * * @since 3.4.3 * * @param string $html The HTML content. * @param bool $is_webp Whether supports webp. * @param bool $dryrun Whether to run in dry mode. * @return array Array of CSS and HTML. */ public function prepare_css( $html, $is_webp = false, $dryrun = false ) { $css = ''; preg_match_all('#]+)/?>|]*)>([^<]+)#isU', $html, $matches, PREG_SET_ORDER); foreach ($matches as $match) { $debug_info = ''; if (strpos($match[0], 'cls('Optimizer')->load_file($attrs['href']); if (!$con) { continue; } } else { $con = ''; } } else { // Inline style $attrs = Utility::parse_attr($match[2]); if (!empty($attrs['media']) && strpos($attrs['media'], 'print') !== false) { continue; } Debug2::debug2('[CSS] Load inline CSS ' . substr($match[3], 0, 100) . '...', $attrs); $con = $match[3]; $debug_info = '__INLINE__'; } $con = Optimizer::minify_css($con); if ($is_webp && $this->cls('Media')->webp_support()) { $con = $this->cls('Media')->replace_background_webp($con); } if ( ! empty( $attrs['media'] ) && 'all' !== $attrs['media'] ) { $con = '@media ' . $attrs['media'] . '{' . $con . "}\n"; } else { $con = $con . "\n"; } $con = '/* ' . $debug_info . ' */' . $con; $css .= $con; $html = str_replace($match[0], '', $html); } return [ $css, $html ]; } /** * Filter the comment content, add quotes to selector from whitelist. Return the json * * @since 3.3 */ private function _filter_whitelist() { $whitelist = []; $list = apply_filters('litespeed_ucss_whitelist', $this->conf(self::O_OPTM_UCSS_SELECTOR_WHITELIST)); foreach ($list as $k => $v) { if (substr($v, 0, 2) === '//') { continue; } $whitelist[] = $v; } return $whitelist; } /** * Notify finished from server * * @since 5.1 */ public function notify() { $post_data = \json_decode( file_get_contents( 'php://input' ), true ); if ( is_null( $post_data ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- This is a callback from QUIC.cloud, verified by extract_msg() $post_data = $_POST; } self::debug('notify() data', $post_data); $this->_queue = $this->load_queue('ucss'); list($post_data) = $this->cls('Cloud')->extract_msg($post_data, 'ucss'); $notified_data = $post_data['data']; if (empty($notified_data) || !is_array($notified_data)) { self::debug('❌ notify exit: no notified data'); return Cloud::err('no notified data'); } // Check if its in queue or not $valid_i = 0; foreach ($notified_data as $v) { if (empty($v['request_url'])) { self::debug('❌ notify bypass: no request_url', $v); continue; } if (empty($v['queue_k'])) { self::debug('❌ notify bypass: no queue_k', $v); continue; } if (empty($this->_queue[$v['queue_k']])) { self::debug('❌ notify bypass: no this queue [q_k]' . $v['queue_k']); continue; } // Save data if (!empty($v['data_ucss'])) { $is_mobile = $this->_queue[$v['queue_k']]['is_mobile']; $is_webp = $this->_queue[$v['queue_k']]['is_webp']; $this->_save_con('ucss', $v['data_ucss'], $v['queue_k'], $is_mobile, $is_webp); ++$valid_i; } unset($this->_queue[$v['queue_k']]); self::debug('notify data handled, unset queue [q_k] ' . $v['queue_k']); } $this->save_queue('ucss', $this->_queue); self::debug('notified'); return Cloud::ok( [ 'count' => $valid_i ] ); } /** * Handle all request actions from main cls * * @since 2.3 * @access public */ public function handler() { $type = Router::verify_type(); switch ($type) { case self::TYPE_GEN: self::cron(true); break; case self::TYPE_CLEAR_Q: $this->clear_q('ucss'); break; default: break; } Admin::redirect(); } } src/localization.cls.php000064400000010033152075713340011320 0ustar00conf( self::O_OPTM_LOCALIZE ) ) { exit( 'Not supported' ); } $match = false; $domains = $this->conf( self::O_OPTM_LOCALIZE_DOMAINS ); foreach ( $domains as $v ) { if ( ! $v || 0 === strpos( $v, '#' ) ) { continue; } $type = 'js'; $domain = $v; // Try to parse space split value if ( strpos( $v, ' ' ) ) { $v = explode( ' ', $v ); if ( ! empty( $v[1] ) ) { $type = strtolower( $v[0] ); $domain = $v[1]; } } if ( 0 !== strpos( $domain, 'https://' ) ) { continue; } if ( 'js' !== $type ) { continue; } if ( $url !== $domain ) { continue; } $match = true; break; } if ( ! $match ) { exit( 'Not supported2' ); } header( 'Content-Type: application/javascript' ); // Generate $this->_maybe_mk_cache_folder( 'localres' ); $file = $this->_realpath( $url ); self::debug( 'localize [url] ' . $url ); $response = wp_safe_remote_get( $url, [ 'timeout' => 180, 'stream' => true, 'filename' => $file, ] ); // Parse response data if ( is_wp_error( $response ) ) { $error_message = $response->get_error_message(); if ( file_exists( $file ) ) { wp_delete_file( $file ); } self::debug( 'failed to get: ' . $error_message ); wp_safe_redirect( $url ); exit(); } $url = $this->_rewrite( $url ); wp_safe_redirect( $url ); exit(); } /** * Get the final URL of local avatar * * @since 4.5 * * @param string $url Original external URL. * @return string Rewritten local URL. */ private function _rewrite( $url ) { return LITESPEED_STATIC_URL . '/localres/' . $this->_filepath( $url ); } /** * Generate realpath of the cache file * * @since 4.5 * @access private * * @param string $url Original external URL. * @return string Absolute file path. */ private function _realpath( $url ) { return LITESPEED_STATIC_DIR . '/localres/' . $this->_filepath( $url ); } /** * Get filepath * * @since 4.5 * * @param string $url Original external URL. * @return string Relative file path. */ private function _filepath( $url ) { $filename = md5( $url ) . '.js'; if ( is_multisite() ) { $filename = get_current_blog_id() . '/' . $filename; } return $filename; } /** * Localize JS/Fonts * * @since 3.3 * @access public * * @param string $content Page HTML content. * @return string Modified content with localized URLs. */ public function finalize( $content ) { if ( is_admin() ) { return $content; } if ( ! $this->conf( self::O_OPTM_LOCALIZE ) ) { return $content; } $domains = $this->conf( self::O_OPTM_LOCALIZE_DOMAINS ); if ( ! $domains ) { return $content; } foreach ( $domains as $v ) { if ( ! $v || 0 === strpos( $v, '#' ) ) { continue; } $type = 'js'; $domain = $v; // Try to parse space split value if ( strpos( $v, ' ' ) ) { $v = explode( ' ', $v ); if ( ! empty( $v[1] ) ) { $type = strtolower( $v[0] ); $domain = $v[1]; } } if ( 0 !== strpos( $domain, 'https://' ) ) { continue; } if ( 'js' !== $type ) { continue; } $content = str_replace( $domain, LITESPEED_STATIC_URL . '/localres/' . base64_encode( $domain ), $content ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode } return $content; } } src/css.cls.php000064400000043430152075713340007427 0ustar00_summary = self::get_summary(); add_filter( 'litespeed_ccss_whitelist', [ $this->cls( 'Data' ), 'load_ccss_whitelist' ] ); } /** * HTML lazyload CSS. * * @since 4.0 * @return string */ public function prepare_html_lazy() { return ''; } /** * Output critical CSS. * * @since 1.3 * @access public * @return string|null */ public function prepare_ccss() { // Get critical css for current page // Note: need to consider mobile $rules = $this->_ccss(); if ( ! $rules ) { return null; } $error_tag = ''; if ( substr( $rules, 0, 2 ) === '/*' && substr( $rules, -2 ) === '*/' ) { Core::comment( 'QUIC.cloud CCSS bypassed due to generation error ❌' ); $error_tag = ' data-error="failed to generate"'; } // Append default critical css $rules .= $this->conf( self::O_OPTM_CCSS_CON ); return ''; } /** * Generate CCSS url tag. * * @since 4.0 * @param string $request_url Current request URL. * @return string */ private function _gen_ccss_file_tag( $request_url ) { if ( is_404() ) { return '404'; } if ( $this->conf( self::O_OPTM_CCSS_PER_URL ) ) { return $request_url; } $sep_uri = $this->conf( self::O_OPTM_CCSS_SEP_URI ); $hit = false; if ( $sep_uri ) { $hit = Utility::str_hit_array( $request_url, $sep_uri ); } if ( $sep_uri && $hit ) { Debug2::debug( '[CCSS] Separate CCSS due to separate URI setting: ' . $hit ); return $request_url; } $pt = Utility::page_type(); $sep_pt = $this->conf( self::O_OPTM_CCSS_SEP_POSTTYPE ); if ( in_array( $pt, $sep_pt, true ) ) { Debug2::debug( '[CCSS] Separate CCSS due to posttype setting: ' . $pt ); return $request_url; } // Per posttype return $pt; } /** * The critical css content of the current page. * * @since 2.3 * @return string|null */ private function _ccss() { global $wp; // get current request url $permalink_structure = get_option( 'permalink_structure' ); if ( ! empty( $permalink_structure ) ) { $request_url = trailingslashit( home_url( $wp->request ) ); } else { $qs_add = $wp->query_string ? '?' . (string) $wp->query_string : '' ; $request_url = home_url( $wp->request ) . $qs_add; } $filepath_prefix = $this->_build_filepath_prefix( 'ccss' ); $url_tag = $this->_gen_ccss_file_tag( $request_url ); $vary = $this->cls( 'Vary' )->finalize_full_varies(); $filename = $this->cls( 'Data' )->load_url_file( $url_tag, $vary, 'ccss' ); if ( $filename ) { $static_file = LITESPEED_STATIC_DIR . $filepath_prefix . $filename . '.css'; if ( file_exists( $static_file ) ) { Debug2::debug2( '[CSS] existing ccss ' . $static_file ); Core::comment( 'QUIC.cloud CCSS loaded ✅ ' . $filepath_prefix . $filename . '.css' ); return File::read( $static_file ); } } $uid = get_current_user_id(); $ua = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : ''; // Store it to prepare for cron Core::comment( 'QUIC.cloud CCSS in queue' ); $this->_queue = $this->load_queue( 'ccss' ); if ( count( $this->_queue ) > 500 ) { self::debug( 'CCSS Queue is full - 500' ); return null; } $queue_k = ( strlen( $vary ) > 32 ? md5( $vary ) : $vary ) . ' ' . $url_tag; $this->_queue[ $queue_k ] = [ 'url' => apply_filters( 'litespeed_ccss_url', $request_url ), 'user_agent' => substr( $ua, 0, 200 ), 'is_mobile' => $this->_separate_mobile(), 'is_webp' => $this->cls( 'Media' )->webp_support() ? 1 : 0, 'uid' => $uid, 'vary' => $vary, 'url_tag' => $url_tag, ]; // Current UA will be used to request $this->save_queue( 'ccss', $this->_queue ); self::debug( 'Added queue_ccss [url_tag] ' . $url_tag . ' [UA] ' . $ua . ' [vary] ' . $vary . ' [uid] ' . $uid ); // Prepare cache tag for later purge Tag::add( 'CCSS.' . md5( $queue_k ) ); return null; } /** * Cron ccss generation. * * @since 2.3 * @access private * * @param bool $should_continue Continue processing multiple items. * @return mixed */ public static function cron_ccss( $should_continue = false ) { $_instance = self::cls(); return $_instance->_cron_handler( 'ccss', $should_continue ); } /** * Handle UCSS/CCSS cron. * * @since 4.2 * * @param string $type Job type: 'ccss' or 'ucss'. * @param bool $should_continue Continue processing multiple items. * @return void */ private function _cron_handler( $type, $should_continue ) { $this->_queue = $this->load_queue( $type ); if ( empty( $this->_queue ) ) { return; } $type_tag = strtoupper( $type ); // For cron, need to check request interval too if ( ! $should_continue ) { if ( ! empty( $this->_summary[ 'curr_request_' . $type ] ) && time() - (int) $this->_summary[ 'curr_request_' . $type ] < 300 && ! $this->conf( self::O_DEBUG ) ) { Debug2::debug( '[' . $type_tag . '] Last request not done' ); return; } } $i = 0; foreach ( $this->_queue as $k => $v ) { if ( ! empty( $v['_status'] ) ) { continue; } Debug2::debug( '[' . $type_tag . '] cron job [tag] ' . $k . ' [url] ' . $v['url'] . ( $v['is_mobile'] ? ' 📱 ' : '' ) . ' [UA] ' . $v['user_agent'] ); if ( 'ccss' === $type && empty( $v['url_tag'] ) ) { unset( $this->_queue[ $k ] ); $this->save_queue( $type, $this->_queue ); Debug2::debug( '[CCSS] wrong queue_ccss format' ); continue; } if ( ! isset( $v['is_webp'] ) ) { $v['is_webp'] = false; } ++$i; $res = $this->_send_req( $v['url'], $k, $v['uid'], $v['user_agent'], $v['vary'], $v['url_tag'], $type, $v['is_mobile'], $v['is_webp'] ); if ( ! $res ) { // Status is wrong, drop this this->_queue unset( $this->_queue[ $k ] ); $this->save_queue( $type, $this->_queue ); if ( ! $should_continue ) { return; } if ( $i > 3 ) { GUI::print_loading( count( $this->_queue ), $type_tag ); return Router::self_redirect( Router::ACTION_CSS, self::TYPE_GEN_CCSS ); } continue; } // Exit queue if out of quota or service is hot if ( 'out_of_quota' === $res || 'svc_hot' === $res ) { return; } $this->_queue[ $k ]['_status'] = 'requested'; $this->save_queue( $type, $this->_queue ); // only request first one if ( ! $should_continue ) { return; } if ( $i > 3 ) { GUI::print_loading( count( $this->_queue ), $type_tag ); return Router::self_redirect( Router::ACTION_CSS, self::TYPE_GEN_CCSS ); } } } /** * Send to QC API to generate CCSS/UCSS. * * @since 2.3 * @access private * * @param string $request_url Request URL. * @param string $queue_k Queue key. * @param int $uid WP User ID. * @param string $user_agent User agent string. * @param string $vary Vary string. * @param string $url_tag URL tag. * @param string $type Type: 'ccss' or 'ucss'. * @param bool $is_mobile Is mobile. * @param bool $is_webp Has webp support. * @return bool|string True on success, 'out_of_quota' / 'svc_hot' on special cases, false on failure. */ private function _send_req( $request_url, $queue_k, $uid, $user_agent, $vary, $url_tag, $type, $is_mobile, $is_webp ) { // Check if has credit to push or not $err = false; $allowance = $this->cls( 'Cloud' )->allowance( Cloud::SVC_CCSS, $err ); if ( ! $allowance ) { Debug2::debug( '[CCSS] ❌ No credit: ' . $err ); $err && Admin_Display::error( Error::msg( $err ) ); return 'out_of_quota'; } set_time_limit( 120 ); // Update css request status $this->_summary[ 'curr_request_' . $type ] = time(); self::save_summary(); // Gather guest HTML to send $html = $this->prepare_html( $request_url, $user_agent, $uid ); if ( ! $html ) { return false; } // Parse HTML to gather all CSS content before requesting list( $css, $html ) = $this->prepare_css( $html, $is_webp ); if ( ! $css ) { $type_tag = strtoupper( $type ); Debug2::debug( '[' . $type_tag . '] ❌ No combined css' ); return false; } // Generate critical css $data = [ 'url' => $request_url, 'queue_k' => $queue_k, 'user_agent' => $user_agent, 'is_mobile' => $is_mobile ? 1 : 0, // todo:compatible w/ tablet 'is_webp' => $is_webp ? 1 : 0, 'html' => $html, 'css' => $css, ]; if ( ! isset( $this->_ccss_whitelist ) ) { $this->_ccss_whitelist = $this->_filter_whitelist(); } $data['whitelist'] = $this->_ccss_whitelist; self::debug( 'Generating: ', $data ); $json = Cloud::post( Cloud::SVC_CCSS, $data, 30 ); if ( ! is_array( $json ) ) { return $json; } // Old version compatibility if ( empty( $json['status'] ) ) { if ( ! empty( $json[ $type ] ) ) { $this->_save_con( $type, $json[ $type ], $queue_k, $is_mobile, $is_webp ); } // Delete the row return false; } // Unknown status, remove this line if ( 'queued' !== $json['status'] ) { return false; } // Save summary data $this->_summary[ 'last_spent_' . $type ] = time() - (int) $this->_summary[ 'curr_request_' . $type ]; $this->_summary[ 'last_request_' . $type ] = $this->_summary[ 'curr_request_' . $type ]; $this->_summary[ 'curr_request_' . $type ] = 0; self::save_summary(); return true; } /** * Save CCSS/UCSS content. * * @since 4.2 * * @param string $type Type: 'ccss' or 'ucss'. * @param string $css CSS content. * @param string $queue_k Queue key. * @param bool $mobile Is mobile. * @param bool $webp Has webp support. * @return void */ private function _save_con( $type, $css, $queue_k, $mobile, $webp ) { // Add filters $css = apply_filters( 'litespeed_' . $type, $css, $queue_k ); // Sanitize: CSS must not contain HTML tags $css = wp_strip_all_tags( $css ); Debug2::debug2( '[CSS] con: ' . $css ); if ( substr( $css, 0, 2 ) === '/*' && substr( $css, -2 ) === '*/' ) { self::debug( '❌ empty ' . $type . ' [content] ' . $css ); // continue; // Save the error info too } // Write to file $filecon_md5 = md5( $css ); $filepath_prefix = $this->_build_filepath_prefix( $type ); $static_file = LITESPEED_STATIC_DIR . $filepath_prefix . $filecon_md5 . '.css'; File::save( $static_file, $css, true ); $url_tag = $this->_queue[ $queue_k ]['url_tag']; $vary = $this->_queue[ $queue_k ]['vary']; Debug2::debug2( "[CSS] Save URL to file [file] $static_file [vary] $vary" ); $this->cls( 'Data' )->save_url( $url_tag, $vary, $type, $filecon_md5, dirname( $static_file ), $mobile, $webp ); Purge::add( strtoupper( $type ) . '.' . md5( $queue_k ) ); } /** * Play for fun. * * @since 3.4.3 * * @param string $request_url URL to test. * @return void */ public function test_url( $request_url ) { $user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : ''; $html = $this->prepare_html( $request_url, $user_agent ); list( $css, $html ) = $this->prepare_css( $html, true, true ); $data = [ 'url' => $request_url, 'ccss_type' => 'test', 'user_agent' => $user_agent, 'is_mobile' => 0, 'html' => $html, 'css' => $css, 'type' => 'CCSS', ]; $json = Cloud::post( Cloud::SVC_CCSS, $data, 180 ); Debug2::debug2( '[CSS][test_url] response', $json ); } /** * Prepare HTML from URL. * * @since 3.4.3 * * @param string $request_url URL to fetch. * @param string $user_agent User agent to use. * @param int|bool $uid Optional user ID for simulation. * @return string|false */ public function prepare_html( $request_url, $user_agent, $uid = false ) { $html = $this->cls( 'Crawler' )->self_curl( add_query_arg( 'LSCWP_CTRL', 'before_optm', $request_url ), $user_agent, $uid ); Debug2::debug2( '[CSS] self_curl result....', $html ); if ( ! $html ) { return false; } $html = $this->cls( 'Optimizer' )->html_min( $html, true ); // Drop $html = preg_replace( '##isU', '', $html ); return $html; } /** * Prepare CSS from HTML for CCSS generation only. UCSS will use combined CSS directly. * Prepare refined HTML for both CCSS and UCSS. * * @since 3.4.3 * * @param string $html HTML content. * @param bool $is_webp Convert backgrounds to WebP when supported. * @param bool $dryrun If true, do not fetch external CSS files. * @return array{0:string,1:string} [combined CSS, refined HTML] */ public function prepare_css( $html, $is_webp = false, $dryrun = false ) { $css = ''; preg_match_all( '#]+)/?>|]*)>([^<]+)#isU', $html, $matches, PREG_SET_ORDER ); foreach ( $matches as $match ) { $debug_info = ''; if ( strpos( $match[0], 'cls( 'Optimizer' )->load_file( $attrs['href'] ); if ( ! $con ) { continue; } } else { $con = ''; } } else { // Inline style $attrs = Utility::parse_attr( $match[2] ); if ( ! empty( $attrs['media'] ) && false !== strpos( $attrs['media'], 'print' ) ) { continue; } Debug2::debug2( '[CSS] Load inline CSS ' . substr( $match[3], 0, 100 ) . '...', $attrs ); $con = $match[3]; $debug_info = '__INLINE__'; } $con = Optimizer::minify_css( $con ); if ( $is_webp && $this->cls( 'Media' )->webp_support() ) { $con = $this->cls( 'Media' )->replace_background_webp( $con ); } if ( ! empty( $attrs['media'] ) && 'all' !== $attrs['media'] ) { $con = '@media ' . $attrs['media'] . '{' . $con . "}\n"; } else { $con = $con . "\n"; } $con = '/* ' . $debug_info . ' */' . $con; $css .= $con; $html = str_replace( $match[0], '', $html ); } return [ $css, $html ]; } /** * Filter the comment content, add quotes to selector from whitelist. Return the json. * * @since 7.1 * @return array */ private function _filter_whitelist() { $whitelist = []; $list = apply_filters( 'litespeed_ccss_whitelist', $this->conf( self::O_OPTM_CCSS_SELECTOR_WHITELIST ) ); foreach ( $list as $v ) { if ( substr( $v, 0, 2 ) === '//' ) { continue; } $whitelist[] = $v; } return $whitelist; } /** * Notify finished from server. * * @since 7.1 * @return array */ public function notify() { // phpcs:ignore WordPress.Security.NonceVerification.Missing $post_data = \json_decode( file_get_contents( 'php://input' ), true ); if ( is_null( $post_data ) ) { // Fallback for form-encoded payloads // phpcs:ignore WordPress.Security.NonceVerification.Missing $post_data = $_POST; } self::debug( 'notify() data', $post_data ); $this->_queue = $this->load_queue( 'ccss' ); list( $post_data ) = $this->cls( 'Cloud' )->extract_msg( $post_data, 'ccss' ); $notified_data = $post_data['data']; if ( empty( $notified_data ) || ! is_array( $notified_data ) ) { self::debug( '❌ notify exit: no notified data' ); return Cloud::err( 'no notified data' ); } // Check if its in queue or not $valid_i = 0; foreach ( $notified_data as $v ) { if ( empty( $v['request_url'] ) ) { self::debug( '❌ notify bypass: no request_url', $v ); continue; } if ( empty( $v['queue_k'] ) ) { self::debug( '❌ notify bypass: no queue_k', $v ); continue; } if ( empty( $this->_queue[ $v['queue_k'] ] ) ) { self::debug( '❌ notify bypass: no this queue [q_k]' . $v['queue_k'] ); continue; } // Save data if ( ! empty( $v['data_ccss'] ) ) { $is_mobile = $this->_queue[ $v['queue_k'] ]['is_mobile']; $is_webp = $this->_queue[ $v['queue_k'] ]['is_webp']; $this->_save_con( 'ccss', $v['data_ccss'], $v['queue_k'], $is_mobile, $is_webp ); ++$valid_i; } unset( $this->_queue[ $v['queue_k'] ] ); self::debug( 'notify data handled, unset queue [q_k] ' . $v['queue_k'] ); } $this->save_queue( 'ccss', $this->_queue ); self::debug( 'notified' ); return Cloud::ok( [ 'count' => $valid_i ] ); } /** * Handle all request actions from main cls. * * @since 2.3 * @access public * @return void */ public function handler() { $type = Router::verify_type(); switch ( $type ) { case self::TYPE_GEN_CCSS: self::cron_ccss( true ); break; case self::TYPE_CLEAR_Q_CCSS: $this->clear_q( 'ccss' ); break; default: break; } Admin::redirect(); } } src/task.cls.php000064400000016063152075713340007603 0ustar00 cron hook registration. * * @var array */ private static $_triggers = [ Base::O_IMG_OPTM_CRON => [ 'name' => 'litespeed_task_imgoptm_pull', 'hook' => 'LiteSpeed\Img_Optm::start_async_cron', ], // always fetch immediately Base::O_OPTM_CSS_ASYNC => [ 'name' => 'litespeed_task_ccss', 'hook' => 'LiteSpeed\CSS::cron_ccss', ], Base::O_OPTM_UCSS => [ 'name' => 'litespeed_task_ucss', 'hook' => 'LiteSpeed\UCSS::cron', ], Base::O_MEDIA_VPI_CRON => [ 'name' => 'litespeed_task_vpi', 'hook' => 'LiteSpeed\VPI::cron', ], Base::O_MEDIA_PLACEHOLDER_RESP_ASYNC => [ 'name' => 'litespeed_task_lqip', 'hook' => 'LiteSpeed\Placeholder::cron', ], Base::O_DISCUSS_AVATAR_CRON => [ 'name' => 'litespeed_task_avatar', 'hook' => 'LiteSpeed\Avatar::cron', ], Base::O_IMG_OPTM_AUTO => [ 'name' => 'litespeed_task_imgoptm_req', 'hook' => 'LiteSpeed\Img_Optm::cron_auto_request', ], Base::O_GUEST => [ 'name' => 'litespeed_task_guest_sync', 'hook' => 'LiteSpeed\Guest::cron', ], // Daily sync Guest Mode IP/UA lists Base::O_CRAWLER => [ 'name' => 'litespeed_task_crawler', 'hook' => 'LiteSpeed\Crawler::start_async_cron', ], // Set crawler to last one to use above results ]; /** * Options allowed to run for guest optimization. * * @var array */ private static $_guest_options = [ Base::O_OPTM_CSS_ASYNC, Base::O_OPTM_UCSS, Base::O_MEDIA_VPI ]; /** * Schedule id for crawler. * * @var string */ const FILTER_CRAWLER = 'litespeed_crawl_filter'; /** * Schedule id for general tasks. * * @var string */ const FILTER = 'litespeed_filter'; /** * Keep all tasks in cron. * * @since 3.0 * @access public * @return void */ public function init() { self::debug2( 'Init' ); add_filter( 'cron_schedules', [ $this, 'lscache_cron_filter' ] ); $guest_optm = $this->conf( Base::O_GUEST ) && $this->conf( Base::O_GUEST_OPTM ); foreach ( self::$_triggers as $id => $trigger ) { if ( Base::O_IMG_OPTM_CRON === $id ) { if ( ! Img_Optm::need_pull() ) { continue; } } elseif ( ! $this->conf( $id ) ) { if ( ! $guest_optm || ! in_array( $id, self::$_guest_options, true ) ) { continue; } } // Special check for crawler. if ( Base::O_CRAWLER === $id ) { if ( ! Router::can_crawl() ) { continue; } add_filter( 'cron_schedules', [ $this, 'lscache_cron_filter_crawler' ] ); // phpcs:ignore WordPress.WP.CronInterval.ChangeDetected } if ( ! wp_next_scheduled( $trigger['name'] ) ) { self::debug( 'Cron hook register [name] ' . $trigger['name'] ); // Determine schedule: crawler uses its own, guest uses daily, others use 15min if ( Base::O_CRAWLER === $id ) { $schedule = self::FILTER_CRAWLER; } elseif ( Base::O_GUEST === $id ) { $schedule = 'daily'; } else { $schedule = self::FILTER; } wp_schedule_event( time(), $schedule, $trigger['name'] ); } add_action( $trigger['name'], $trigger['hook'] ); } } /** * Handle all async noabort requests. * * @since 5.5 * @return void */ public static function async_litespeed_handler() { $hash_data = self::get_option( 'async_call-hash', [] ); if ( ! $hash_data || ! is_array( $hash_data ) || empty( $hash_data['hash'] ) || empty( $hash_data['ts'] ) ) { self::debug( 'async_litespeed_handler no hash data', $hash_data ); return; } $nonce = isset( $_GET['nonce'] ) ? sanitize_text_field( wp_unslash( $_GET['nonce'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( 120 < time() - (int) $hash_data['ts'] || '' === $nonce || $nonce !== $hash_data['hash'] ) { self::debug( 'async_litespeed_handler nonce mismatch' ); return; } self::delete_option( 'async_call-hash' ); $type = Router::verify_type(); self::debug( 'type=' . $type ); // Don't lock up other requests while processing. session_write_close(); switch ( $type ) { case 'crawler': Crawler::async_handler(); break; case 'crawler_force': Crawler::async_handler( true ); break; case 'imgoptm': Img_Optm::async_handler(); break; case 'imgoptm_force': Img_Optm::async_handler( true ); break; default: break; } } /** * Async caller wrapper func. * * @since 5.5 * * @param string $type Async operation type. * @return void */ public static function async_call( $type ) { $hash = Str::rrand( 32 ); self::update_option( 'async_call-hash', [ 'hash' => $hash, 'ts' => time(), ] ); $args = [ 'timeout' => 0.01, 'blocking' => false, 'sslverify' => false, // 'cookies' => $_COOKIE, ]; $qs = [ 'action' => 'async_litespeed', 'nonce' => $hash, Router::TYPE => $type, ]; $url = add_query_arg( $qs, admin_url( 'admin-ajax.php' ) ); self::debug( 'async call to ' . $url ); wp_safe_remote_post( esc_url_raw( $url ), $args ); } /** * Clean all potential existing crons. * * @since 3.0 * @access public * @return void */ public static function destroy() { Utility::compatibility(); array_map( 'wp_clear_scheduled_hook', array_column( self::$_triggers, 'name' ) ); } /** * Try to clean the crons if disabled. * * @since 3.0 * @access public * * @param string $id Option id of cron trigger. * @return void */ public function try_clean( $id ) { if ( $id && ! empty( self::$_triggers[ $id ] ) ) { if ( ! $this->conf( $id ) || ( Base::O_CRAWLER === $id && ! Router::can_crawl() ) ) { self::debug( 'Cron clear [id] ' . $id . ' [hook] ' . self::$_triggers[ $id ]['name'] ); wp_clear_scheduled_hook( self::$_triggers[ $id ]['name'] ); } return; } self::debug( '❌ Unknown cron [id] ' . $id ); } /** * Register cron interval for general tasks. * * @since 1.6.1 * @access public * * @param array $schedules Existing schedules. * @return array */ public function lscache_cron_filter( $schedules ) { if ( ! array_key_exists( self::FILTER, $schedules ) ) { $schedules[ self::FILTER ] = [ 'interval' => 900, 'display' => __( 'Every 15 Minutes', 'litespeed-cache' ), ]; } return $schedules; } /** * Register cron interval for crawler. * * @since 1.1.0 * @access public * * @param array $schedules Existing schedules. * @return array */ public function lscache_cron_filter_crawler( $schedules ) { $crawler_run_interval = defined( 'LITESPEED_CRAWLER_RUN_INTERVAL' ) ? (int) constant( 'LITESPEED_CRAWLER_RUN_INTERVAL' ) : 600; if ( ! array_key_exists( self::FILTER_CRAWLER, $schedules ) ) { $schedules[ self::FILTER_CRAWLER ] = [ 'interval' => $crawler_run_interval, 'display' => __( 'LiteSpeed Crawler Cron', 'litespeed-cache' ), ]; } return $schedules; } } src/cdn.cls.php000064400000037654152075713340007416 0ustar00 */ private $_cfg_cdn_mapping = []; /** * List of URL substrings/regex used to exclude items from CDN. * * @var string[] */ private $_cfg_cdn_exclude; /** * Hosts used by CDN mappings for quick membership checks. * * @var string[] */ private $cdn_mapping_hosts = []; /** * Initialize CDN integration and register filters if enabled. * * @since 1.2.3 * @return void */ public function init() { self::debug2( 'init' ); if ( defined( self::BYPASS ) ) { self::debug2( 'CDN bypass' ); return; } if ( ! Router::can_cdn() ) { if ( ! defined( self::BYPASS ) ) { define( self::BYPASS, true ); } return; } $this->_cfg_cdn = $this->conf( Base::O_CDN ); if ( ! $this->_cfg_cdn ) { if ( ! defined( self::BYPASS ) ) { define( self::BYPASS, true ); } return; } $this->_cfg_url_ori = $this->conf( Base::O_CDN_ORI ); // Parse cdn mapping data to array( 'filetype' => 'url' ) $mapping_to_check = [ Base::CDN_MAPPING_INC_IMG, Base::CDN_MAPPING_INC_CSS, Base::CDN_MAPPING_INC_JS ]; foreach ( $this->conf( Base::O_CDN_MAPPING ) as $v ) { if ( ! $v[ Base::CDN_MAPPING_URL ] ) { continue; } $this_url = $v[ Base::CDN_MAPPING_URL ]; $this_host = wp_parse_url( $this_url, PHP_URL_HOST ); // Check img/css/js foreach ( $mapping_to_check as $to_check ) { if ( $v[ $to_check ] ) { self::debug2( 'mapping ' . $to_check . ' -> ' . $this_url ); // If filetype to url is one to many, make url be an array $this->_append_cdn_mapping( $to_check, $this_url ); if ( ! in_array( $this_host, $this->cdn_mapping_hosts, true ) ) { $this->cdn_mapping_hosts[] = $this_host; } } } // Check file types if ( $v[ Base::CDN_MAPPING_FILETYPE ] ) { foreach ( $v[ Base::CDN_MAPPING_FILETYPE ] as $v2 ) { $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_FILETYPE ] = true; // If filetype to url is one to many, make url be an array $this->_append_cdn_mapping( $v2, $this_url ); if ( ! in_array( $this_host, $this->cdn_mapping_hosts, true ) ) { $this->cdn_mapping_hosts[] = $this_host; } } self::debug2( 'mapping ' . implode( ',', $v[ Base::CDN_MAPPING_FILETYPE ] ) . ' -> ' . $this_url ); } } if ( ! $this->_cfg_url_ori || ! $this->_cfg_cdn_mapping ) { if ( ! defined( self::BYPASS ) ) { define( self::BYPASS, true ); } return; } $this->_cfg_ori_dir = $this->conf( Base::O_CDN_ORI_DIR ); // In case user customized upload path if ( defined( 'UPLOADS' ) ) { $this->_cfg_ori_dir[] = UPLOADS; } // Check if need preg_replace $this->_cfg_url_ori = Utility::wildcard2regex( $this->_cfg_url_ori ); $this->_cfg_cdn_exclude = $this->conf( Base::O_CDN_EXC ); if ( ! empty( $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_INC_IMG ] ) ) { // Hook to srcset if ( function_exists( 'wp_calculate_image_srcset' ) ) { add_filter( 'wp_calculate_image_srcset', [ $this, 'srcset' ], 999 ); } // Hook to mime icon add_filter( 'wp_get_attachment_image_src', [ $this, 'attach_img_src' ], 999 ); add_filter( 'wp_get_attachment_url', [ $this, 'url_img' ], 999 ); } if ( ! empty( $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_INC_CSS ] ) ) { add_filter( 'style_loader_src', [ $this, 'url_css' ], 999 ); } if ( ! empty( $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_INC_JS ] ) ) { add_filter( 'script_loader_src', [ $this, 'url_js' ], 999 ); } add_filter( 'litespeed_buffer_finalize', [ $this, 'finalize' ], 30 ); } /** * Associate all filetypes with CDN URL. * * @since 2.0 * @access private * * @param string $filetype Mapping key (e.g., extension or mapping constant). * @param string $url CDN base URL to use for this mapping. * @return void */ private function _append_cdn_mapping( $filetype, $url ) { // If filetype to url is one to many, make url be an array if ( empty( $this->_cfg_cdn_mapping[ $filetype ] ) ) { $this->_cfg_cdn_mapping[ $filetype ] = $url; } elseif ( is_array( $this->_cfg_cdn_mapping[ $filetype ] ) ) { // Append url to filetype $this->_cfg_cdn_mapping[ $filetype ][] = $url; } else { // Convert _cfg_cdn_mapping from string to array $this->_cfg_cdn_mapping[ $filetype ] = [ $this->_cfg_cdn_mapping[ $filetype ], $url ]; } } /** * Whether the given type is included in CDN mappings. * * @since 1.6.2.1 * * @param string $type 'css' or 'js'. * @return bool True if included in CDN. */ public function inc_type( $type ) { if ( 'css' === $type && ! empty( $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_INC_CSS ] ) ) { return true; } if ( 'js' === $type && ! empty( $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_INC_JS ] ) ) { return true; } return false; } /** * Run CDN processing on finalized buffer. * NOTE: After cache finalized, cannot change cache control. * * @since 1.2.3 * @access public * * @param string $content The HTML/content buffer. * @return string The processed content. */ public function finalize( $content ) { $this->content = $content; $this->_finalize(); return $this->content; } /** * Replace eligible URLs with CDN URLs in the working buffer. * * @since 1.2.3 * @access private * @return void */ private function _finalize() { if ( defined( self::BYPASS ) ) { return; } self::debug( 'CDN _finalize' ); // Start replacing img src if ( ! empty( $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_INC_IMG ] ) ) { $this->_replace_img(); $this->_replace_inline_css(); } if ( ! empty( $this->_cfg_cdn_mapping[ Base::CDN_MAPPING_FILETYPE ] ) ) { $this->_replace_file_types(); } } /** * Parse all file types and replace according to configured attributes. * * @since 1.2.3 * @access private * @return void */ private function _replace_file_types() { $ele_to_check = $this->conf( Base::O_CDN_ATTR ); foreach ( $ele_to_check as $v ) { if ( ! $v || false === strpos( $v, '.' ) ) { self::debug2( 'replace setting bypassed: no . attribute ' . $v ); continue; } self::debug2( 'replace attribute ' . $v ); $v = explode( '.', $v ); $attr = preg_quote( $v[1], '#' ); if ( $v[0] ) { $pattern = '#<' . preg_quote( $v[0], '#' ) . '([^>]+)' . $attr . '=([\'"])(.+)\g{2}#iU'; } else { $pattern = '# ' . $attr . '=([\'"])(.+)\g{1}#iU'; } preg_match_all( $pattern, $this->content, $matches ); if (empty($matches[$v[0] ? 3 : 2])) { continue; } foreach ($matches[$v[0] ? 3 : 2] as $k2 => $url) { // self::debug2( 'check ' . $url ); $postfix = '.' . pathinfo((string) wp_parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION); if (!array_key_exists($postfix, $this->_cfg_cdn_mapping)) { // self::debug2( 'non-existed postfix ' . $postfix ); continue; } self::debug2( 'matched file_type ' . $postfix . ' : ' . $url ); $url2 = $this->rewrite( $url, Base::CDN_MAPPING_FILETYPE, $postfix ); if ( ! $url2 ) { continue; } $attr_str = str_replace( $url, $url2, $matches[0][ $k2 ] ); $this->content = str_replace( $matches[0][ $k2 ], $attr_str, $this->content ); } } } /** * Parse all images and replace their src attributes. * * @since 1.2.3 * @access private * @return void */ private function _replace_img() { preg_match_all( '#]+?)src=([\'"\\\]*)([^\'"\s\\\>]+)([\'"\\\]*)([^>]*)>#i', $this->content, $matches ); foreach ( $matches[3] as $k => $url ) { // Check if is a DATA-URI if ( false !== strpos( $url, 'data:image' ) ) { continue; } $url2 = $this->rewrite( $url, Base::CDN_MAPPING_INC_IMG ); if ( ! $url2 ) { continue; } $html_snippet = sprintf( '', $matches[1][ $k ], $matches[2][ $k ] . $url2 . $matches[4][ $k ], $matches[5][ $k ] ); $this->content = str_replace( $matches[0][ $k ], $html_snippet, $this->content ); } } /** * Parse and replace all inline styles containing url(). * * @since 1.2.3 * @access private * @return void */ private function _replace_inline_css() { self::debug2( '_replace_inline_css', $this->_cfg_cdn_mapping ); /** * Excludes `\` from URL matching * * @see #959152 - WordPress LSCache CDN Mapping causing malformed URLS * @see #685485 * @since 3.0 */ preg_match_all( '/url\((?![\'"]?data)[\'"]?(.+?)[\'"]?\)/i', $this->content, $matches ); foreach ( $matches[1] as $k => $url ) { $url = str_replace( [ ' ', '\t', '\n', '\r', '\0', '\x0B', '"', "'", '"', ''' ], '', $url ); // Parse file postfix $parsed_url = wp_parse_url( $url, PHP_URL_PATH ); if ( ! $parsed_url ) { continue; } $postfix = '.' . pathinfo( $parsed_url, PATHINFO_EXTENSION ); if ( array_key_exists( $postfix, $this->_cfg_cdn_mapping ) ) { self::debug2( 'matched file_type ' . $postfix . ' : ' . $url ); $url2 = $this->rewrite( $url, Base::CDN_MAPPING_FILETYPE, $postfix ); if ( ! $url2 ) { continue; } } elseif ( in_array( $postfix, [ 'jpg', 'jpeg', 'png', 'gif', 'svg', 'webp', 'avif' ], true ) ) { $url2 = $this->rewrite( $url, Base::CDN_MAPPING_INC_IMG ); if ( ! $url2 ) { continue; } } else { continue; } $attr = str_replace( $matches[1][ $k ], $url2, $matches[0][ $k ] ); $this->content = str_replace( $matches[0][ $k ], $attr, $this->content ); } self::debug2( '_replace_inline_css done' ); } /** * Filter: wp_get_attachment_image_src. * * @since 1.2.3 * @since 1.7 Removed static from function. * @access public * * @param array{0:string,1:int,2:int} $img The URL of the attachment image src, the width, the height. * @return array{0:string,1:int,2:int} */ public function attach_img_src( $img ) { if ( $img ) { $url = $this->rewrite( $img[0], Base::CDN_MAPPING_INC_IMG ); if ( $url ) { $img[0] = $url; } } return $img; } /** * Try to rewrite one image URL with CDN. * * @since 1.7 * @access public * * @param string $url Original URL. * @return string URL after rewriting, or original if not applicable. */ public function url_img( $url ) { if ( $url ) { $url2 = $this->rewrite( $url, Base::CDN_MAPPING_INC_IMG ); if ( $url2 ) { $url = $url2; } } return $url; } /** * Try to rewrite one CSS URL with CDN. * * @since 1.7 * @access public * * @param string $url Original URL. * @return string URL after rewriting, or original if not applicable. */ public function url_css( $url ) { if ( $url ) { $url2 = $this->rewrite( $url, Base::CDN_MAPPING_INC_CSS ); if ( $url2 ) { $url = $url2; } } return $url; } /** * Try to rewrite one JS URL with CDN. * * @since 1.7 * @access public * * @param string $url Original URL. * @return string URL after rewriting, or original if not applicable. */ public function url_js( $url ) { if ( $url ) { $url2 = $this->rewrite( $url, Base::CDN_MAPPING_INC_JS ); if ( $url2 ) { $url = $url2; } } return $url; } /** * Filter responsive image sources for CDN. * * @since 1.2.3 * @since 1.7 Removed static from function. * @access public * * @param array $srcs Srcset array. * @return array */ public function srcset( $srcs ) { if ( $srcs ) { foreach ( $srcs as $w => $data ) { $url = $this->rewrite( $data['url'], Base::CDN_MAPPING_INC_IMG ); if ( ! $url ) { continue; } $srcs[ $w ]['url'] = $url; } } return $srcs; } /** * Replace an URL with mapped CDN URL, if applicable. * * @since 1.2.3 * @access public * * @param string $url Target URL. * @param string $mapping_kind Mapping kind (e.g., Base::CDN_MAPPING_INC_IMG or Base::CDN_MAPPING_FILETYPE). * @param string|false $postfix File extension (with dot) when mapping by file type. * @return string|false Replaced URL on success, false when not applicable. */ public function rewrite( $url, $mapping_kind, $postfix = false ) { self::debug2( 'rewrite ' . $url ); $url_parsed = wp_parse_url( $url ); if ( empty( $url_parsed['path'] ) ) { self::debug2( '-rewrite bypassed: no path' ); return false; } // Only images under wp-content/wp-includes can be replaced $is_internal_folder = Utility::str_hit_array( $url_parsed['path'], $this->_cfg_ori_dir ); if ( ! $is_internal_folder ) { self::debug2( '-rewrite failed: path not match: ' . LSCWP_CONTENT_FOLDER ); return false; } // Check if is external url if ( ! empty( $url_parsed['host'] ) ) { if ( ! Utility::internal( $url_parsed['host'] ) && ! $this->_is_ori_url( $url ) ) { self::debug2( '-rewrite failed: host not internal' ); return false; } } $exclude = Utility::str_hit_array( $url, $this->_cfg_cdn_exclude ); if ( $exclude ) { self::debug2( '-abort excludes ' . $exclude ); return false; } // Fill full url before replacement if ( empty( $url_parsed['host'] ) ) { $url = Utility::uri2url( $url ); self::debug2( '-fill before rewritten: ' . $url ); $url_parsed = wp_parse_url( $url ); } $scheme = ! empty( $url_parsed['scheme'] ) ? $url_parsed['scheme'] . ':' : ''; // Find the mapping url to be replaced to if ( empty( $this->_cfg_cdn_mapping[ $mapping_kind ] ) ) { return false; } if ( Base::CDN_MAPPING_FILETYPE !== $mapping_kind ) { $final_url = $this->_cfg_cdn_mapping[ $mapping_kind ]; } else { // select from file type $final_url = $this->_cfg_cdn_mapping[ $postfix ]; if ( ! $final_url ) { return false; } } // If filetype to url is one to many, need to random one if ( is_array( $final_url ) ) { $final_url = $final_url[ array_rand( $final_url ) ]; } // Now lets replace CDN url foreach ( $this->_cfg_url_ori as $v ) { if ( false !== strpos( $v, '*' ) ) { $url = preg_replace( '#' . $scheme . $v . '#iU', $final_url, $url ); } else { $url = str_replace( $scheme . $v, $final_url, $url ); } } self::debug2( '-rewritten: ' . $url ); return $url; } /** * Check if the given URL matches any configured "original" URLs for CDN. * * @since 2.1 * @access private * * @param string $url URL to test. * @return bool True if URL is one of the originals. */ private function _is_ori_url( $url ) { $url_parsed = wp_parse_url( $url ); $scheme = ! empty( $url_parsed['scheme'] ) ? $url_parsed['scheme'] . ':' : ''; foreach ( $this->_cfg_url_ori as $v ) { $needle = $scheme . $v; if ( false !== strpos( $v, '*' ) ) { if ( preg_match( '#' . $needle . '#iU', $url ) ) { return true; } } elseif ( 0 === strpos( $url, $needle ) ) { return true; } } return false; } /** * Check if the host is one of the CDN mapping hosts. * * @since 1.2.3 * * @param string $host Hostname to check. * @return bool False when bypassed, otherwise true if internal CDN host. */ public static function internal( $host ) { if ( defined( self::BYPASS ) ) { return false; } $instance = self::cls(); return in_array( $host, $instance->cdn_mapping_hosts, true ); // todo: can add $this->_is_ori_url() check in future } } src/control.cls.php000064400000060545152075713340010325 0ustar00 */ private $_response_header_ttls = []; /** * Init cache control. * * @since 1.6.2 * @return void */ public function init() { /** * Add vary filter for Role Excludes. * * @since 1.6.2 */ add_filter( 'litespeed_vary', [ $this, 'vary_add_role_exclude' ] ); // 301 redirect hook. add_filter( 'wp_redirect', [ $this, 'check_redirect' ], 10, 2 ); // Load response header conf. $this->_response_header_ttls = $this->conf( Base::O_CACHE_TTL_STATUS ); foreach ( $this->_response_header_ttls as $k => $v ) { $v = explode( ' ', $v ); if ( empty( $v[0] ) || empty( $v[1] ) ) { continue; } $this->_response_header_ttls[ $v[0] ] = $v[1]; } if ( $this->conf( Base::O_PURGE_STALE ) ) { $this->set_stale(); } } /** * Exclude role from optimization filter. * * @since 1.6.2 * @access public * * @param array $vary Existing vary map. * @return array */ public function vary_add_role_exclude( $vary ) { if ( $this->in_cache_exc_roles() ) { $vary['role_exclude_cache'] = 1; } return $vary; } /** * Check if one user role is in exclude cache group settings. * * @since 1.6.2 * @since 3.0 Moved here from conf.cls * @access public * * @param string|null $role The user role. * @return string|false Comma-separated roles if set, otherwise false. */ public function in_cache_exc_roles( $role = null ) { // Get user role. if ( null === $role ) { $role = Router::get_role(); } if ( ! $role ) { return false; } $roles = explode( ',', $role ); $found = array_intersect( $roles, $this->conf( Base::O_CACHE_EXC_ROLES ) ); return $found ? implode( ',', $found ) : false; } /** * 1. Initialize cacheable status for `wp` hook * 2. Hook error page tags for cacheable pages * * @since 1.1.3 * @access public * @return void */ public function init_cacheable() { // Hook `wp` to mark default cacheable status. // NOTE: Any process that does NOT run into `wp` hook will not get cacheable by default. add_action( 'wp', [ $this, 'set_cacheable' ], 5 ); // Hook WP REST to be cacheable. if ( $this->conf( Base::O_CACHE_REST ) ) { add_action( 'rest_api_init', [ $this, 'set_cacheable' ], 5 ); } // AJAX cache. $ajax_cache = $this->conf( Base::O_CACHE_AJAX_TTL ); foreach ( $ajax_cache as $v ) { $v = explode( ' ', $v ); if ( empty( $v[0] ) || empty( $v[1] ) ) { continue; } add_action( 'wp_ajax_nopriv_' . $v[0], function () use ( $v ) { self::set_custom_ttl( $v[1] ); self::force_cacheable( 'ajax Cache setting for action ' . $v[0] ); }, 4 ); } // Check error page. add_filter( 'status_header', [ $this, 'check_error_codes' ], 10, 2 ); } /** * Check if the page returns any error code. * * @since 1.0.13.1 * @access public * * @param string $status_header Status header. * @param int $code HTTP status code. * @return string Original status header. */ public function check_error_codes( $status_header, $code ) { if ( array_key_exists( $code, $this->_response_header_ttls ) ) { if ( self::is_cacheable() && ! $this->_response_header_ttls[ $code ] ) { self::set_nocache( '[Ctrl] TTL is set to no cache [status_header] ' . $code ); } // Set TTL. self::set_custom_ttl( $this->_response_header_ttls[ $code ] ); } elseif ( self::is_cacheable() ) { $first = substr( $code, 0, 1 ); if ( '4' === $first || '5' === $first ) { self::set_nocache( '[Ctrl] 4xx/5xx default to no cache [status_header] ' . $code ); } } // Set cache tag. if ( in_array( $code, Tag::$error_code_tags, true ) ) { Tag::add( Tag::TYPE_HTTP . $code ); } // Give the default status_header back. return $status_header; } /** * Set no vary setting. * * @access public * @since 1.1.3 * @return void */ public static function set_no_vary() { if ( self::is_no_vary() ) { return; } self::$_control |= self::BM_NO_VARY; self::debug( 'X Cache_control -> no-vary', 3 ); } /** * Get no vary setting. * * @access public * @since 1.1.3 * @return bool */ public static function is_no_vary() { return self::$_control & self::BM_NO_VARY; } /** * Set stale. * * @access public * @since 1.1.3 * @return void */ public function set_stale() { if ( self::is_stale() ) { return; } self::$_control |= self::BM_STALE; self::debug( 'X Cache_control -> stale' ); } /** * Get stale. * * @access public * @since 1.1.3 * @return bool */ public static function is_stale() { return self::$_control & self::BM_STALE; } /** * Set cache control to shared private. * * @access public * @since 1.1.3 * * @param string|false $reason The reason to mark shared, or false. * @return void */ public static function set_shared( $reason = false ) { if ( self::is_shared() ) { return; } self::$_control |= self::BM_SHARED; self::set_private(); if ( ! is_string( $reason ) ) { $reason = false; } if ( $reason ) { $reason = "( $reason )"; } self::debug( 'X Cache_control -> shared ' . $reason ); } /** * Check if is shared private. * * @access public * @since 1.1.3 * @return bool */ public static function is_shared() { return (bool) ( self::$_control & self::BM_SHARED ) && self::is_private(); } /** * Set cache control to forced public. * * @access public * @since 1.7.1 * * @param string|false $reason Reason text or false. * @return void */ public static function set_public_forced( $reason = false ) { if ( self::is_public_forced() ) { return; } self::$_control |= self::BM_PUBLIC_FORCED; if ( ! is_string( $reason ) ) { $reason = false; } if ( $reason ) { $reason = "( $reason )"; } self::debug( 'X Cache_control -> public forced ' . $reason ); } /** * Check if is public forced. * * @access public * @since 1.7.1 * @return bool */ public static function is_public_forced() { return self::$_control & self::BM_PUBLIC_FORCED; } /** * Set cache control to private. * * @access public * @since 1.1.3 * * @param string|false $reason The reason to set private. * @return void */ public static function set_private( $reason = false ) { if ( self::is_private() ) { return; } self::$_control |= self::BM_PRIVATE; if ( ! is_string( $reason ) ) { $reason = false; } if ( $reason ) { $reason = "( $reason )"; } self::debug( 'X Cache_control -> private ' . $reason ); } /** * Check if is private. * * @access public * @since 1.1.3 * @return bool */ public static function is_private() { // if ( defined( 'LITESPEED_GUEST' ) && LITESPEED_GUEST ) { // return false; // } return (bool) ( self::$_control & self::BM_PRIVATE ) && ! self::is_public_forced(); } /** * Initialize cacheable status in `wp` hook, if not call this, by default it will be non-cacheable. * * @access public * @since 1.1.3 * * @param string|false $reason Reason text or false. * @return void */ public function set_cacheable( $reason = false ) { self::$_control |= self::BM_CACHEABLE; if ( ! is_string( $reason ) ) { $reason = false; } if ( $reason ) { $reason = ' [reason] ' . $reason; } self::debug( 'Cache_control init on' . $reason ); } /** * This will disable non-cacheable BM. * * @access public * @since 2.2 * * @param string|false $reason Reason text or false. * @return void */ public static function force_cacheable( $reason = false ) { self::$_control |= self::BM_FORCED_CACHEABLE; if ( ! is_string( $reason ) ) { $reason = false; } if ( $reason ) { $reason = ' [reason] ' . $reason; } self::debug( 'Forced cacheable' . $reason ); } /** * Switch to nocacheable status. * * @access public * @since 1.1.3 * * @param string|false $reason The reason to no cache. * @return void */ public static function set_nocache( $reason = false ) { self::$_control |= self::BM_NOTCACHEABLE; if ( ! is_string( $reason ) ) { $reason = false; } if ( $reason ) { $reason = "( $reason )"; } self::debug( 'X Cache_control -> no Cache ' . $reason, 5 ); } /** * Check current notcacheable bit set. * * @access public * @since 1.1.3 * @return bool True if notcacheable bit is set, otherwise false. */ public static function isset_notcacheable() { return self::$_control & self::BM_NOTCACHEABLE; } /** * Check current force cacheable bit set. * * @access public * @since 2.2 * @return bool */ public static function is_forced_cacheable() { return self::$_control & self::BM_FORCED_CACHEABLE; } /** * Check current cacheable status. * * @access public * @since 1.1.3 * @return bool True if is still cacheable, otherwise false. */ public static function is_cacheable() { if ( defined( 'LSCACHE_NO_CACHE' ) && LSCACHE_NO_CACHE ) { self::debug( 'LSCACHE_NO_CACHE constant defined' ); return false; } // Guest mode always cacheable // if ( defined( 'LITESPEED_GUEST' ) && LITESPEED_GUEST ) { // return true; // } // If it's forced public cacheable. if ( self::is_public_forced() ) { return true; } // If it's forced cacheable. if ( self::is_forced_cacheable() ) { return true; } return ! self::isset_notcacheable() && ( self::$_control & self::BM_CACHEABLE ); } /** * Set a custom TTL to use with the request if needed. * * @access public * @since 1.1.3 * * @param int|string $ttl An integer or numeric string to use as the TTL. * @param string|false $reason Optional reason text. * @return void */ public static function set_custom_ttl( $ttl, $reason = false ) { if ( is_numeric( $ttl ) ) { self::$_custom_ttl = (int) $ttl; self::debug( 'X Cache_control TTL -> ' . $ttl . ( $reason ? ' [reason] ' . $ttl : '' ) ); } } /** * Generate final TTL. * * @access public * @since 1.1.3 * @return int */ public function get_ttl() { if ( 0 !== self::$_custom_ttl ) { return (int) self::$_custom_ttl; } // Check if is in timed url list or not. $timed_urls = Utility::wildcard2regex( $this->conf( Base::O_PURGE_TIMED_URLS ) ); $timed_urls_time = $this->conf( Base::O_PURGE_TIMED_URLS_TIME ); if ( $timed_urls && $timed_urls_time ) { $current_url = Tag::build_uri_tag( true ); // Use time limit ttl. $scheduled_time = strtotime( $timed_urls_time ); $ttl = $scheduled_time - current_time('timestamp'); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp if ( $ttl < 0 ) { $ttl += 86400; // add one day } foreach ( $timed_urls as $v ) { if ( false !== strpos( $v, '*' ) ) { if ( preg_match( '#' . $v . '#iU', $current_url ) ) { self::debug( 'X Cache_control TTL is limited to ' . $ttl . ' due to scheduled purge regex ' . $v ); return $ttl; } } elseif ( $v === $current_url ) { self::debug( 'X Cache_control TTL is limited to ' . $ttl . ' due to scheduled purge rule ' . $v ); return $ttl; } } } // Private cache uses private ttl setting. if ( self::is_private() ) { return (int) $this->conf( Base::O_CACHE_TTL_PRIV ); } if ( is_front_page() ) { return (int) $this->conf( Base::O_CACHE_TTL_FRONTPAGE ); } $feed_ttl = (int) $this->conf( Base::O_CACHE_TTL_FEED ); if ( is_feed() && $feed_ttl > 0 ) { return $feed_ttl; } if ( $this->cls( 'REST' )->is_rest() || $this->cls( 'REST' )->is_internal_rest() ) { return (int) $this->conf( Base::O_CACHE_TTL_REST ); } return (int) $this->conf( Base::O_CACHE_TTL_PUB ); } /** * Check if need to set no cache status for redirection or not. * * @access public * @since 1.1.3 * * @param string $location Redirect location. * @param int $status HTTP status. * @return string Redirect location. */ public function check_redirect( $location, $status ) { $script_uri = ''; if ( !empty( $_SERVER['SCRIPT_URI'] ) ) { $script_uri = sanitize_text_field( wp_unslash( $_SERVER['SCRIPT_URI'] ) ); } elseif ( !empty( $_SERVER['REQUEST_URI'] ) ) { $home = trailingslashit( home_url() ); $script_uri = $home . ltrim( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ), '/' ); } if ( '' !== $script_uri ) { self::debug( '301 from ' . $script_uri ); self::debug( '301 to ' . $location ); $to_check = [ PHP_URL_SCHEME, PHP_URL_HOST, PHP_URL_PATH, PHP_URL_QUERY ]; $is_same_redirect = true; $query_string = ! empty( $_SERVER['QUERY_STRING'] ) ? sanitize_text_field( wp_unslash( $_SERVER['QUERY_STRING'] ) ) : ''; foreach ( $to_check as $v ) { $url_parsed = PHP_URL_QUERY === $v ? $query_string : wp_parse_url( $script_uri, $v ); $target = wp_parse_url( $location, $v ); self::debug( 'Compare [from] ' . $url_parsed . ' [to] ' . $target ); if ( PHP_URL_QUERY === $v ) { $url_parsed = $url_parsed ? urldecode( $url_parsed ) : ''; $target = $target ? urldecode( $target ) : ''; if ( '&' === substr( $url_parsed, -1 ) ) { $url_parsed = substr( $url_parsed, 0, -1 ); } } if ( $url_parsed !== $target ) { $is_same_redirect = false; self::debug( '301 different redirection' ); break; } } if ( $is_same_redirect ) { self::set_nocache( '301 to same url' ); } } return $location; } /** * Sets up the Cache Control header. * * @since 1.1.3 * @access public * @return string empty string if empty, otherwise the cache control header. */ public function output() { $esi_hdr = ''; if ( ESI::has_esi() ) { $esi_hdr = ',esi=on'; } $hdr = self::X_HEADER . ': '; // phpcs:ignore WordPress.NamingConventions.ValidHookName.NotLowercase if ( defined( 'DONOTCACHEPAGE' ) && apply_filters( 'litespeed_const_DONOTCACHEPAGE', DONOTCACHEPAGE ) ) { self::debug( '❌ forced no cache [reason] DONOTCACHEPAGE const' ); $hdr .= 'no-cache' . $esi_hdr; return $hdr; } // Guest mode directly return cacheable result // if ( defined( 'LITESPEED_GUEST' ) && LITESPEED_GUEST ) { // If is POST, no cache // if ( defined( 'LSCACHE_NO_CACHE' ) && LSCACHE_NO_CACHE ) { // self::debug( "[Ctrl] ❌ forced no cache [reason] LSCACHE_NO_CACHE const" ); // $hdr .= 'no-cache'; // } // else if( $_SERVER[ 'REQUEST_METHOD' ] !== 'GET' ) { // self::debug( "[Ctrl] ❌ forced no cache [reason] req not GET" ); // $hdr .= 'no-cache'; // } // else { // $hdr .= 'public'; // $hdr .= ',max-age=' . $this->get_ttl(); // } // $hdr .= $esi_hdr; // return $hdr; // } // Fix cli `uninstall --deactivate` fatal err if (!self::is_cacheable()) { $hdr .= 'no-cache' . $esi_hdr; return $hdr; } if ( self::is_shared() ) { $hdr .= 'shared,private'; } elseif ( self::is_private() ) { $hdr .= 'private'; } else { $hdr .= 'public'; } if ( self::is_no_vary() ) { $hdr .= ',no-vary'; } $hdr .= ',max-age=' . $this->get_ttl() . $esi_hdr; return $hdr; } /** * Generate all `control` tags before output. * * @access public * @since 1.1.3 * @return void */ public function finalize() { // if ( defined( 'LITESPEED_GUEST' ) && LITESPEED_GUEST ) { // return; // } if ( is_preview() ) { self::set_nocache( 'preview page' ); return; } // Check if has metabox non-cacheable setting or not. if ( file_exists( LSCWP_DIR . 'src/metabox.cls.php' ) && $this->cls( 'Metabox' )->setting( 'litespeed_no_cache' ) ) { self::set_nocache( 'per post metabox setting' ); return; } // Check if URI is forced public cache. $excludes = $this->conf( Base::O_CACHE_FORCE_PUB_URI ); $req_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : ''; $hit = Utility::str_hit_array( $req_uri, $excludes, true ); if ( $hit ) { list( $result, $this_ttl ) = $hit; self::set_public_forced( 'Setting: ' . $result ); self::debug( 'Forced public cacheable due to setting: ' . $result ); if ( $this_ttl ) { self::set_custom_ttl( $this_ttl ); } } if ( self::is_public_forced() ) { return; } // Check if URI is forced cache. $excludes = $this->conf( Base::O_CACHE_FORCE_URI ); $hit = Utility::str_hit_array( $req_uri, $excludes, true ); if ( $hit ) { list( $result, $this_ttl ) = $hit; self::force_cacheable(); self::debug( 'Forced cacheable due to setting: ' . $result ); if ( $this_ttl ) { self::set_custom_ttl( $this_ttl ); } } // if is not cacheable, terminate check. // Even no need to run 3rd party hook. if ( ! self::is_cacheable() ) { self::debug( 'not cacheable before ctrl finalize' ); return; } // Apply 3rd party filter. // NOTE: Hook always needs to run asap because some 3rd party set is_mobile in this hook. do_action( 'litespeed_control_finalize', defined( 'LSCACHE_IS_ESI' ) ? LSCACHE_IS_ESI : false ); // Pass ESI block id. // if is not cacheable, terminate check. if ( ! self::is_cacheable() ) { self::debug( 'not cacheable after api_control' ); return; } // Check litespeed setting to set cacheable status. if ( ! $this->_setting_cacheable() ) { self::set_nocache(); return; } // If user has password cookie, do not cache (moved from vary). global $post; if ( ! empty( $post->post_password ) && isset( $_COOKIE[ 'wp-postpass_' . COOKIEHASH ] ) ) { self::set_nocache( 'pswd cookie' ); return; } // The following check to the end is ONLY for mobile. $is_mobile_conf = apply_filters( 'litespeed_is_mobile', false ); if ( ! $this->conf( Base::O_CACHE_MOBILE ) ) { if ( $is_mobile_conf ) { self::set_nocache( 'mobile' ); } return; } $env_vary = isset( $_SERVER['LSCACHE_VARY_VALUE'] ) ? sanitize_text_field( wp_unslash( $_SERVER['LSCACHE_VARY_VALUE'] ) ) : ''; if ( !$env_vary && isset( $_SERVER['HTTP_X_LSCACHE_VARY_VALUE'] ) ) { $env_vary = sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_LSCACHE_VARY_VALUE'] ) ); } if ( $env_vary && false !== strpos( $env_vary, 'ismobile' ) ) { if ( ! wp_is_mobile() && ! $is_mobile_conf ) { self::set_nocache( 'is not mobile' ); // todo: no need to uncache, it will correct vary value in vary finalize anyways. return; } } elseif ( wp_is_mobile() || $is_mobile_conf ) { self::set_nocache( 'is mobile' ); return; } } /** * Check if is mobile for filter `litespeed_is_mobile` in API. * * @since 3.0 * @access public * @return bool */ public static function is_mobile() { return wp_is_mobile(); } /** * Get request method w/ compatibility to X-Http-Method-Override. * * @since 6.2 * @return string */ private function _get_req_method() { if ( isset( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ) ) { $override = sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ) ); self::debug( 'X-Http-Method-Override -> ' . $override ); if ( ! defined( 'LITESPEED_X_HTTP_METHOD_OVERRIDE' ) ) { define( 'LITESPEED_X_HTTP_METHOD_OVERRIDE', true ); } return $override; } if ( isset( $_SERVER['REQUEST_METHOD'] ) ) { return sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ); } return 'unknown'; } /** * Check if a page is cacheable based on litespeed setting. * * @since 1.0.0 * @access private * @return bool True if cacheable, false otherwise. */ private function _setting_cacheable() { // logged_in users already excluded, no hook added. // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! empty( $_REQUEST[ Router::ACTION ] ) ) { return $this->_no_cache_for( 'Query String Action' ); } $method = $this->_get_req_method(); if ( defined( 'LITESPEED_X_HTTP_METHOD_OVERRIDE' ) && LITESPEED_X_HTTP_METHOD_OVERRIDE && 'HEAD' === $method ) { return $this->_no_cache_for( 'HEAD method from override' ); } if ( 'GET' !== $method && 'HEAD' !== $method ) { return $this->_no_cache_for( 'Not GET method: ' . $method ); } if ( is_feed() && 0 === $this->conf( Base::O_CACHE_TTL_FEED ) ) { return $this->_no_cache_for( 'feed' ); } if ( is_trackback() ) { return $this->_no_cache_for( 'trackback' ); } if ( is_search() ) { return $this->_no_cache_for( 'search' ); } // Check private cache URI setting. $excludes = $this->conf( Base::O_CACHE_PRIV_URI ); $req_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : ''; $result = Utility::str_hit_array( $req_uri, $excludes ); if ( $result ) { self::set_private( 'Admin cfg Private Cached URI: ' . $result ); } if ( ! self::is_forced_cacheable() ) { // Check if URI is excluded from cache. $excludes = $this->cls( 'Data' )->load_cache_nocacheable( $this->conf( Base::O_CACHE_EXC ) ); $result = Utility::str_hit_array( $req_uri, $excludes ); if ( $result ) { return $this->_no_cache_for( 'Admin configured URI Do not cache: ' . $result ); } // Check QS excluded setting. $excludes = $this->conf( Base::O_CACHE_EXC_QS ); $qs_hit = $this->_is_qs_excluded( $excludes ); if ( ! empty( $excludes ) && $qs_hit ) { return $this->_no_cache_for( 'Admin configured QS Do not cache: ' . $qs_hit ); } $excludes = $this->conf( Base::O_CACHE_EXC_CAT ); if ( ! empty( $excludes ) && has_category( $excludes ) ) { return $this->_no_cache_for( 'Admin configured Category Do not cache.' ); } $excludes = $this->conf( Base::O_CACHE_EXC_TAG ); if ( ! empty( $excludes ) && has_tag( $excludes ) ) { return $this->_no_cache_for( 'Admin configured Tag Do not cache.' ); } $excludes = $this->conf( Base::O_CACHE_EXC_COOKIES ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- names only, compared as keys. if ( ! empty( $excludes ) && ! empty( $_COOKIE ) ) { $cookie_hit = array_intersect( array_keys( $_COOKIE ), $excludes ); if ( $cookie_hit ) { return $this->_no_cache_for( 'Admin configured Cookie Do not cache.' ); } } $excludes = $this->conf( Base::O_CACHE_EXC_USERAGENTS ); if ( ! empty( $excludes ) && isset( $_SERVER['HTTP_USER_AGENT'] ) ) { $nummatches = preg_match( Utility::arr2regex( $excludes ), sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) ); if ( $nummatches ) { return $this->_no_cache_for( 'Admin configured User Agent Do not cache.' ); } } // Check if is exclude roles ( Need to set Vary too ). $result = $this->in_cache_exc_roles(); if ( $result ) { return $this->_no_cache_for( 'Role Excludes setting ' . $result ); } } return true; } /** * Write a debug message for if a page is not cacheable. * * @since 1.0.0 * @access private * * @param string $reason An explanation for why the page is not cacheable. * @return bool Always false. */ private function _no_cache_for( $reason ) { self::debug( 'X Cache_control off - ' . $reason ); return false; } /** * Check if current request has qs excluded setting. * * @since 1.3 * @access private * * @param array $excludes QS excludes setting. * @return bool|string False if not excluded, otherwise the hit qs list. */ private function _is_qs_excluded( $excludes ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! empty( $_GET ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $keys = array_keys( $_GET ); $intersect = array_intersect( $keys, $excludes ); if ( $intersect ) { return implode( ',', $intersect ); } } return false; } } src/cloud-auth.trait.php000064400000022610152075713340011243 0ustar00_summary['sk_b64'] ) ) { $keypair = sodium_crypto_sign_keypair(); $pk = base64_encode( sodium_crypto_sign_publickey( $keypair ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode $sk = base64_encode( sodium_crypto_sign_secretkey( $keypair ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode $this->_summary['pk_b64'] = $pk; $this->_summary['sk_b64'] = $sk; $this->save_summary(); // ATM `qc_activated` = null return true; } return false; } /** * Init QC setup * * @since 7.0 */ public function init_qc() { $this->init_qc_prepare(); $ref = $this->_get_ref_url(); // WPAPI REST echo dryrun $echobox = self::post( self::API_REST_ECHO, false, 60 ); if ( false === $echobox ) { self::debugErr( 'REST Echo Failed!' ); $msg = __( "QUIC.cloud's access to your WP REST API seems to be blocked.", 'litespeed-cache' ); Admin_Display::error( $msg ); wp_safe_redirect( $ref ); exit; } self::debug( 'echo succeeded' ); // Load separate thread echoed data from storage if ( empty( $echobox['wpapi_ts'] ) || empty( $echobox['wpapi_signature_b64'] ) ) { Admin_Display::error( __( 'Failed to get echo data from WPAPI', 'litespeed-cache' ) ); wp_safe_redirect( $ref ); exit; } $data = [ 'wp_pk_b64' => $this->_summary['pk_b64'], 'wpapi_ts' => $echobox['wpapi_ts'], 'wpapi_signature_b64' => $echobox['wpapi_signature_b64'], ]; $server_ip = $this->conf( self::O_SERVER_IP ); if ( $server_ip ) { $data['server_ip'] = $server_ip; } // Activation redirect $param = [ 'site_url' => site_url(), 'ver' => Core::VER, 'data' => $data, 'ref' => $ref, ]; wp_safe_redirect( $this->_cloud_server_dash . '/' . self::SVC_U_ACTIVATE . '?data=' . rawurlencode( Utility::arr2str( $param ) ) ); exit; } /** * Decide the ref * * @param string|false $ref Ref slug. * @return string */ private function _get_ref_url( $ref = false ) { $link = 'admin.php?page=litespeed'; if ( 'cdn' === $ref ) { $link = 'admin.php?page=litespeed-cdn'; } if ( 'online' === $ref ) { $link = 'admin.php?page=litespeed-general'; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended $ref_get = ! empty( $_GET['ref'] ) ? sanitize_text_field( wp_unslash( $_GET['ref'] ) ) : ''; if ( $ref_get && 'cdn' === $ref_get ) { $link = 'admin.php?page=litespeed-cdn'; } if ( $ref_get && 'online' === $ref_get ) { $link = 'admin.php?page=litespeed-general'; } return get_admin_url( null, $link ); } /** * Init QC setup (CLI) * * @since 7.0 */ public function init_qc_cli() { $this->init_qc_prepare(); $server_ip = $this->conf( self::O_SERVER_IP ); if ( ! $server_ip ) { self::debugErr( 'Server IP needs to be set first!' ); $msg = sprintf( __( 'You need to set the %1$s first. Please use the command %2$s to set.', 'litespeed-cache' ), '`' . __( 'Server IP', 'litespeed-cache' ) . '`', '`wp litespeed-option set server_ip __your_ip_value__`' ); Admin_Display::error( $msg ); return; } // WPAPI REST echo dryrun $echobox = self::post( self::API_REST_ECHO, false, 60 ); if ( false === $echobox ) { self::debugErr( 'REST Echo Failed!' ); $msg = __( "QUIC.cloud's access to your WP REST API seems to be blocked.", 'litespeed-cache' ); Admin_Display::error( $msg ); return; } self::debug( 'echo succeeded' ); // Load separate thread echoed data from storage if ( empty( $echobox['wpapi_ts'] ) || empty( $echobox['wpapi_signature_b64'] ) ) { self::debug( 'Resp: ', $echobox ); Admin_Display::error( __( 'Failed to get echo data from WPAPI', 'litespeed-cache' ) ); return; } $data = [ 'wp_pk_b64' => $this->_summary['pk_b64'], 'wpapi_ts' => $echobox['wpapi_ts'], 'wpapi_signature_b64' => $echobox['wpapi_signature_b64'], 'server_ip' => $server_ip, ]; $res = $this->post( self::SVC_D_ACTIVATE, $data ); return $res; } /** * Init QC CDN setup (CLI) * * @since 7.0 * * @param string $method Method. * @param string|bool $cert Cert path. * @param string|bool $key Key path. * @param string|bool $cf_token Cloudflare token. */ public function init_qc_cdn_cli( $method, $cert = false, $key = false, $cf_token = false ) { if ( ! $this->activated() ) { Admin_Display::error( __( 'You need to activate QC first.', 'litespeed-cache' ) ); return; } $server_ip = $this->conf( self::O_SERVER_IP ); if ( ! $server_ip ) { self::debugErr( 'Server IP needs to be set first!' ); $msg = sprintf( __( 'You need to set the %1$s first. Please use the command %2$s to set.', 'litespeed-cache' ), '`' . __( 'Server IP', 'litespeed-cache' ) . '`', '`wp litespeed-option set server_ip __your_ip_value__`' ); Admin_Display::error( $msg ); return; } if ( $cert ) { if ( ! file_exists( $cert ) || ! file_exists( $key ) ) { Admin_Display::error( __( 'Cert or key file does not exist.', 'litespeed-cache' ) ); return; } } $data = [ 'method' => $method, 'server_ip' => $server_ip, ]; if ( $cert ) { $data['cert'] = File::read( $cert ); $data['key'] = File::read( $key ); } if ( $cf_token ) { $data['cf_token'] = $cf_token; } $res = $this->post( self::SVC_D_ENABLE_CDN, $data ); return $res; } /** * Link to QC setup * * @since 7.0 */ public function link_qc() { if ( ! $this->activated() ) { Admin_Display::error( __( 'You need to activate QC first.', 'litespeed-cache' ) ); return; } $data = [ 'wp_ts' => time(), ]; $data['wp_signature_b64'] = $this->_sign_b64( $data['wp_ts'] ); // Activation redirect $param = [ 'site_url' => site_url(), 'ver' => Core::VER, 'data' => $data, 'ref' => $this->_get_ref_url(), ]; wp_safe_redirect( $this->_cloud_server_dash . '/' . self::SVC_U_LINK . '?data=' . rawurlencode( Utility::arr2str( $param ) ) ); exit; } /** * Show QC Account CDN status * * @since 7.0 */ public function cdn_status_cli() { if ( ! $this->activated() ) { Admin_Display::error( __( 'You need to activate QC first.', 'litespeed-cache' ) ); return; } $data = []; $res = $this->post( self::SVC_D_STATUS_CDN_CLI, $data ); return $res; } /** * Link to QC Account for CLI * * @since 7.0 * * @param string $email Account email. * @param string $key API key. */ public function link_qc_cli( $email, $key ) { if ( ! $this->activated() ) { Admin_Display::error( __( 'You need to activate QC first.', 'litespeed-cache' ) ); return; } $data = [ 'qc_acct_email' => $email, 'qc_acct_apikey'=> $key, ]; $res = $this->post( self::SVC_D_LINK, $data ); return $res; } /** * API link parsed call to QC * * @since 7.0 * * @param string $action2 Action slug. */ public function api_link_call( $action2 ) { if ( ! $this->activated() ) { Admin_Display::error( __( 'You need to activate QC first.', 'litespeed-cache' ) ); return; } $data = [ 'action2' => $action2, ]; $res = $this->post( self::SVC_D_API, $data ); self::debug( 'API link call result: ', $res ); } /** * Enable QC CDN * * @since 7.0 */ public function enable_cdn() { if ( ! $this->activated() ) { Admin_Display::error( __( 'You need to activate QC first.', 'litespeed-cache' ) ); return; } $data = [ 'wp_ts' => time(), ]; $data['wp_signature_b64'] = $this->_sign_b64( $data['wp_ts'] ); // Activation redirect $param = [ 'site_url' => site_url(), 'ver' => Core::VER, 'data' => $data, 'ref' => $this->_get_ref_url(), ]; wp_safe_redirect( $this->_cloud_server_dash . '/' . self::SVC_U_ENABLE_CDN . '?data=' . rawurlencode( Utility::arr2str( $param ) ) ); exit; } /** * Reset QC setup * * @since 7.0 */ public function reset_qc() { unset( $this->_summary['pk_b64'] ); unset( $this->_summary['sk_b64'] ); unset( $this->_summary['qc_activated'] ); if ( ! empty( $this->_summary['partner'] ) ) { unset( $this->_summary['partner'] ); } $this->save_summary(); self::debug( 'Clear local QC activation.' ); $this->clear_cloud(); Admin_Display::success( sprintf( __( 'Reset %s activation successfully.', 'litespeed-cache' ), 'QUIC.cloud' ) ); wp_safe_redirect( $this->_get_ref_url() ); exit; } /** * Check if activated QUIC.cloud service or not * * @since 7.0 * @access public */ public function activated() { return ! empty( $this->_summary['sk_b64'] ) && ! empty( $this->_summary['qc_activated'] ); } /** * Show my.qc quick link to the domain page * * @return string */ public function qc_link() { $data = [ 'site_url' => site_url(), 'ver' => LSCWP_V, 'ref' => $this->_get_ref_url(), ]; return $this->_cloud_server_dash . '/u/wp3/manage?data=' . rawurlencode( Utility::arr2str( $data ) ); // . (!empty($this->_summary['is_linked']) ? '?wplogin=1' : ''); } } src/gui.cls.php000064400000111113152075713340007415 0ustar00 [ days, litespeed_only ], ... ] * * @var array */ private $_promo_list = [ 'new_version' => [ 7, false ], 'score' => [ 14, false ], // 'slack' => [ 3, false ], ]; /** Path to guest JavaScript file. */ const LIB_GUEST_JS = 'assets/js/guest.min.js'; /** Path to guest document.referrer JavaScript file. */ const LIB_GUEST_DOCREF_JS = 'assets/js/guest.docref.min.js'; /** Path to guest vary endpoint. */ const PHP_GUEST = 'guest.vary.php'; /** Dismiss type: WHM. */ const TYPE_DISMISS_WHM = 'whm'; /** Dismiss type: ExpiresDefault. */ const TYPE_DISMISS_EXPIRESDEFAULT = 'ExpiresDefault'; /** Dismiss type: Promo. */ const TYPE_DISMISS_PROMO = 'promo'; /** Dismiss type: PIN. */ const TYPE_DISMISS_PIN = 'pin'; /** WHM message option name. */ const WHM_MSG = 'lscwp_whm_install'; /** WHM message option value. */ const WHM_MSG_VAL = 'whm_install'; /** * Summary options cache. * * @var array Summary/options cache. */ protected $_summary; /** * Instance. * * @since 1.3 */ public function __construct() { $this->_summary = self::get_summary(); } /** * Frontend init. * * @since 3.0 */ public function init() { self::debug2( 'init' ); if ( is_admin_bar_showing() && current_user_can( 'manage_options' ) ) { add_action( 'wp_enqueue_scripts', [ $this, 'frontend_enqueue_style' ] ); add_action( 'admin_bar_menu', [ $this, 'frontend_shortcut' ], 95 ); } /** * Turn on instant click. * * @since 1.8.2 */ if ( $this->conf( self::O_UTIL_INSTANT_CLICK ) ) { add_action( 'wp_enqueue_scripts', [ $this, 'frontend_enqueue_style_public' ] ); } // NOTE: this needs to be before optimizer to avoid wrapper being removed. add_filter( 'litespeed_buffer_finalize', [ $this, 'finalize' ], 8 ); } /** * Print a loading message when redirecting CCSS/UCSS page to avoid blank page confusion. * * @param int $counter Files left in queue. * @param string $type Queue type label. * @return void */ public static function print_loading( $counter, $type ) { echo '
'; echo " "; printf( /* translators: 1: number, 2: text */ esc_html__( '%1$s %2$s files left in queue', 'litespeed-cache' ), esc_html( number_format_i18n( $counter ) ), esc_html( $type ) ); echo '

' . esc_html__( 'Cancel', 'litespeed-cache' ) . '

'; echo '
'; } /** * Display the tab list. * * @since 7.3 * * @param array $tabs Key => Label pairs. * @return void */ public static function display_tab_list( $tabs ) { $i = 1; foreach ( $tabs as $k => $val ) { $accesskey = $i <= 9 ? $i : ''; printf( '%3$s', esc_attr( $k ), esc_attr( $accesskey ), esc_html( $val ) ); ++$i; } } /** * Render a pie chart SVG string. * * @since 1.6.6 * * @param int $percent Percentage 0-100. * @param int $width Width/height in pixels. * @param bool $finished_tick Show a tick when 100%. * @param bool $without_percentage Hide the % label. * @param string|bool $append_cls Extra CSS class. * @return string SVG markup. */ public static function pie( $percent, $width = 50, $finished_tick = false, $without_percentage = false, $append_cls = false ) { $label = $without_percentage ? $percent : ( $percent . '%' ); $percentage = '' . esc_html( $label ) . ''; if ( 100 === $percent && $finished_tick ) { $percentage = ''; } $svg = sprintf( " %4\$s ", esc_attr( $append_cls ), $width, $percent, $percentage ); return $svg; } /** * Allowed SVG tags/attributes for kses. * * @since 7.3 * * @return array> Allowed tags/attributes. */ public static function allowed_svg_tags() { return [ 'svg' => [ 'width' => true, 'height' => true, 'viewbox' => true, // Note: SVG standard uses 'viewBox', but wp_kses normalizes to lowercase. 'xmlns' => true, 'class' => true, 'id' => true, ], 'circle' => [ 'cx' => true, 'cy' => true, 'r' => true, 'fill' => true, 'stroke' => true, 'class' => true, 'stroke-width' => true, 'stroke-dasharray' => true, ], 'path' => [ 'd' => true, 'fill' => true, 'stroke' => true, ], 'text' => [ 'x' => true, 'y' => true, 'dx' => true, 'dy' => true, 'font-size' => true, 'font-family' => true, 'font-weight' => true, 'fill' => true, 'stroke' => true, 'stroke-width' => true, 'text-anchor' => true, 'class' => true, 'id' => true, ], 'g' => [ 'transform' => true, 'fill' => true, 'stroke' => true, 'stroke-width' => true, 'class' => true, 'id' => true, ], 'button' => [ 'type' => true, 'data-balloon-break' => true, 'data-balloon-pos' => true, 'aria-label' => true, 'class' => true, ], ]; } /** * Display a tiny pie with a tooltip. * * @since 3.0 * * @param int $percent Percentage 0-100. * @param int $width Width/height in pixels. * @param string $tooltip Tooltip text. * @param string $tooltip_pos Tooltip position (e.g., 'up'). * @param string|bool $append_cls Extra CSS class. * @return string HTML/SVG. */ public static function pie_tiny( $percent, $width = 50, $tooltip = '', $tooltip_pos = 'up', $append_cls = false ) { // formula C = 2πR. $dasharray = 2 * 3.1416 * 9 * ( $percent / 100 ); return sprintf( " ", esc_attr( $tooltip_pos ), esc_attr( $tooltip ), esc_attr( $append_cls ), $width, esc_attr( $dasharray ) ); } /** * Get CSS class name for PageSpeed score. * * Scale: * 90-100 (fast) * 50-89 (average) * 0-49 (slow) * * @since 2.9 * @access public * * @param int $score Score 0-100. * @return string Class name: success|warning|danger. */ public function get_cls_of_pagescore( $score ) { if ( $score >= 90 ) { return 'success'; } if ( $score >= 50 ) { return 'warning'; } return 'danger'; } /** * Handle dismiss actions for banners and notices. * * @since 1.0 * @access public * @return void */ public static function dismiss() { $_instance = self::cls(); switch ( Router::verify_type() ) { case self::TYPE_DISMISS_WHM: self::dismiss_whm(); break; case self::TYPE_DISMISS_EXPIRESDEFAULT: self::update_option( Admin_Display::DB_DISMISS_MSG, Admin_Display::RULECONFLICT_DISMISSED ); break; case self::TYPE_DISMISS_PIN: Admin_Display::dismiss_pin(); break; case self::TYPE_DISMISS_PROMO: if ( empty( $_GET['promo_tag'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended break; } $promo_tag = sanitize_key( wp_unslash( $_GET['promo_tag'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( empty( $_instance->_promo_list[ $promo_tag ] ) ) { break; } defined( 'LSCWP_LOG' ) && self::debug( 'Dismiss promo ' . $promo_tag ); // Forever dismiss. if ( ! empty( $_GET['done'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $_instance->_summary[ $promo_tag ] = 'done'; } elseif ( ! empty( $_GET['later'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended // Delay the banner to half year later. $_instance->_summary[ $promo_tag ] = time() + ( 86400 * 180 ); } else { // Update welcome banner to 30 days after. $_instance->_summary[ $promo_tag ] = time() + ( 86400 * 30 ); } self::save_summary(); break; default: break; } if ( Router::is_ajax() ) { // All dismiss actions are considered as ajax call, so just exit. exit( wp_json_encode( [ 'success' => 1 ] ) ); } // Plain click link, redirect to referral url. Admin::redirect(); } /** * Check if has rule conflict notice. * * @since 1.1.5 * @access public * * @return bool True if message should be shown. */ public static function has_msg_ruleconflict() { $db_dismiss_msg = self::get_option( Admin_Display::DB_DISMISS_MSG ); if ( ! $db_dismiss_msg ) { self::update_option( Admin_Display::DB_DISMISS_MSG, -1 ); } return Admin_Display::RULECONFLICT_ON === $db_dismiss_msg; } /** * Check if has WHM notice. * * @since 1.1.1 * @access public * * @return bool True if message should be shown. */ public static function has_whm_msg() { $val = self::get_option( self::WHM_MSG ); if ( ! $val ) { self::dismiss_whm(); return false; } return self::WHM_MSG_VAL === $val; } /** * Delete WHM message tag. * * @since 1.1.1 * @access public * @return void */ public static function dismiss_whm() { self::update_option( self::WHM_MSG, -1 ); } /** * Whether current request is a LiteSpeed admin page. * * @since 2.9 * * @return bool True if LiteSpeed page. */ private function _is_litespeed_page() { if ( ! empty( $_GET['page'] ) && // phpcs:ignore WordPress.Security.NonceVerification.Recommended in_array( (string) $_GET['page'], // phpcs:ignore WordPress.Security.NonceVerification.Recommended [ 'litespeed-settings', 'litespeed-dash', Admin::PAGE_EDIT_HTACCESS, 'litespeed-optimization', 'litespeed-crawler', 'litespeed-import', 'litespeed-report', ], true ) ) { return true; } return false; } /** * Display promo banner (or check-only mode to know which promo would display). * * @since 2.1 * @access public * * @param bool $check_only If true, only return the promo tag that would be shown. * @return false|string False if none, or the promo tag string. */ public function show_promo( $check_only = false ) { $is_litespeed_page = $this->_is_litespeed_page(); // Bypass showing info banner if disabled all in debug. if ( defined( 'LITESPEED_DISABLE_ALL' ) && LITESPEED_DISABLE_ALL ) { return false; } if ( file_exists( ABSPATH . '.litespeed_no_banner' ) ) { defined( 'LSCWP_LOG' ) && self::debug( 'Bypass banners due to silence file' ); return false; } foreach ( $this->_promo_list as $promo_tag => $v ) { list( $delay_days, $litespeed_page_only ) = $v; if ( $litespeed_page_only && ! $is_litespeed_page ) { continue; } // First time check. if ( empty( $this->_summary[ $promo_tag ] ) ) { $this->_summary[ $promo_tag ] = time() + 86400 * $delay_days; self::save_summary(); continue; } $promo_timestamp = $this->_summary[ $promo_tag ]; // Was ticked as done. if ( 'done' === $promo_timestamp ) { continue; } // Not reach the dateline yet. if ( time() < $promo_timestamp ) { continue; } // Try to load, if can pass, will set $this->_promo_true = true. $this->_promo_true = false; include LSCWP_DIR . "tpl/banner/$promo_tag.php"; // If not defined, means it didn't pass the display workflow in tpl. if ( ! $this->_promo_true ) { continue; } if ( $check_only ) { return $promo_tag; } defined( 'LSCWP_LOG' ) && self::debug( 'Show promo ' . $promo_tag ); // Only contain one. break; } return false; } /** * Load frontend public script. * * @since 1.8.2 * @access public * @return void */ public function frontend_enqueue_style_public() { wp_enqueue_script( Core::PLUGIN_NAME, LSWCP_PLUGIN_URL . 'assets/js/instant_click.min.js', [], Core::VER, [ 'strategy' => 'defer', 'in_footer' => true, ] ); } /** * Load frontend stylesheet. * * @since 1.3 * @access public * @return void */ public function frontend_enqueue_style() { wp_enqueue_style( Core::PLUGIN_NAME, LSWCP_PLUGIN_URL . 'assets/css/litespeed.css', [], Core::VER, 'all' ); } /** * Load frontend menu shortcut items in the admin bar. * * @since 1.3 * @since 7.6 Add VPI clear. * @access public * @return void */ public function frontend_shortcut() { global $wp_admin_bar; $wp_admin_bar->add_menu( [ 'id' => 'litespeed-menu', 'title' => '', 'href' => get_admin_url( null, 'admin.php?page=litespeed' ), 'meta' => [ 'tabindex' => 0, 'class' => 'litespeed-top-toolbar', ], ] ); $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-single', 'title' => esc_html__( 'Purge this page', 'litespeed-cache' ) . ' - LSCache', 'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_FRONT, false, true ), 'meta' => [ 'tabindex' => '0' ], ] ); if ( $this->has_cache_folder( 'ucss' ) ) { $possible_url_tag = UCSS::get_url_tag(); $append_arr = []; if ( $possible_url_tag ) { $append_arr['url_tag'] = $possible_url_tag; } $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-single-ucss', 'title' => esc_html__( 'Purge this page', 'litespeed-cache' ) . ' - UCSS', 'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_UCSS, false, true, $append_arr ), 'meta' => [ 'tabindex' => '0' ], ] ); } $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-single-action', 'title' => esc_html__( 'Mark this page as ', 'litespeed-cache' ), 'meta' => [ 'tabindex' => '0' ], ] ); $current_uri = isset( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : ''; if ( $current_uri ) { $append_arr = [ Conf::TYPE_SET . '[' . self::O_CACHE_FORCE_URI . '][]' => $current_uri . '$', 'redirect' => $current_uri, ]; $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-single-action', 'id' => 'litespeed-single-forced_cache', 'title' => esc_html__( 'Forced cacheable', 'litespeed-cache' ), 'href' => Utility::build_url( Router::ACTION_CONF, Conf::TYPE_SET, false, true, $append_arr ), ] ); $append_arr = [ Conf::TYPE_SET . '[' . self::O_CACHE_EXC . '][]' => $current_uri . '$', 'redirect' => $current_uri, ]; $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-single-action', 'id' => 'litespeed-single-noncache', 'title' => esc_html__( 'Non cacheable', 'litespeed-cache' ), 'href' => Utility::build_url( Router::ACTION_CONF, Conf::TYPE_SET, false, true, $append_arr ), ] ); $append_arr = [ Conf::TYPE_SET . '[' . self::O_CACHE_PRIV_URI . '][]' => $current_uri . '$', 'redirect' => $current_uri, ]; $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-single-action', 'id' => 'litespeed-single-private', 'title' => esc_html__( 'Private cache', 'litespeed-cache' ), 'href' => Utility::build_url( Router::ACTION_CONF, Conf::TYPE_SET, false, true, $append_arr ), ] ); $append_arr = [ Conf::TYPE_SET . '[' . self::O_OPTM_EXC . '][]' => $current_uri . '$', 'redirect' => $current_uri, ]; $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-single-action', 'id' => 'litespeed-single-nonoptimize', 'title' => esc_html__( 'No optimization', 'litespeed-cache' ), 'href' => Utility::build_url( Router::ACTION_CONF, Conf::TYPE_SET, false, true, $append_arr ), ] ); } $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-single-action', 'id' => 'litespeed-single-more', 'title' => esc_html__( 'More settings', 'litespeed-cache' ), 'href' => get_admin_url( null, 'admin.php?page=litespeed-cache' ), ] ); $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-all', 'title' => esc_html__( 'Purge All', 'litespeed-cache' ), 'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL, false, '_ori' ), 'meta' => [ 'tabindex' => '0' ], ] ); $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-all-lscache', 'title' => esc_html__( 'Purge All', 'litespeed-cache' ) . ' - ' . esc_html__( 'LSCache', 'litespeed-cache' ), 'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_LSCACHE, false, '_ori' ), 'meta' => [ 'tabindex' => '0' ], ] ); $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-cssjs', 'title' => esc_html__( 'Purge All', 'litespeed-cache' ) . ' - ' . esc_html__( 'CSS/JS Cache', 'litespeed-cache' ), 'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_CSSJS, false, '_ori' ), 'meta' => [ 'tabindex' => '0' ], ] ); if ( $this->conf( self::O_CDN_CLOUDFLARE ) ) { $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-cloudflare', 'title' => esc_html__( 'Purge All', 'litespeed-cache' ) . ' - ' . esc_html__( 'Cloudflare', 'litespeed-cache' ), 'href' => Utility::build_url( Router::ACTION_CDN_CLOUDFLARE, CDN\Cloudflare::TYPE_PURGE_ALL ), 'meta' => [ 'tabindex' => '0' ], ] ); } if ( defined( 'LSCWP_OBJECT_CACHE' ) ) { $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-object', 'title' => esc_html__( 'Purge All', 'litespeed-cache' ) . ' - ' . esc_html__( 'Object Cache', 'litespeed-cache' ), 'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_OBJECT, false, '_ori' ), 'meta' => [ 'tabindex' => '0' ], ] ); } if ( Router::opcache_enabled() ) { $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-opcache', 'title' => esc_html__( 'Purge All', 'litespeed-cache' ) . ' - ' . esc_html__( 'Opcode Cache', 'litespeed-cache' ), 'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_OPCACHE, false, '_ori' ), 'meta' => [ 'tabindex' => '0' ], ] ); } if ( $this->has_cache_folder( 'ccss' ) ) { $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-ccss', 'title' => esc_html__( 'Purge All', 'litespeed-cache' ) . ' - CCSS', 'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_CCSS, false, '_ori' ), 'meta' => [ 'tabindex' => '0' ], ] ); } if ( $this->has_cache_folder( 'ucss' ) ) { $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-ucss', 'title' => esc_html__( 'Purge All', 'litespeed-cache' ) . ' - UCSS', 'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_UCSS, false, '_ori' ), ] ); } if ( $this->has_cache_folder( 'localres' ) ) { $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-localres', 'title' => esc_html__( 'Purge All', 'litespeed-cache' ) . ' - ' . esc_html__( 'Localized Resources', 'litespeed-cache' ), 'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_LOCALRES, false, '_ori' ), 'meta' => [ 'tabindex' => '0' ], ] ); } if ( $this->has_cache_folder( 'lqip' ) ) { $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-placeholder', 'title' => esc_html__( 'Purge All', 'litespeed-cache' ) . ' - ' . esc_html__( 'LQIP Cache', 'litespeed-cache' ), 'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_LQIP, false, '_ori' ), 'meta' => [ 'tabindex' => '0' ], ] ); } if ( $this->has_cache_folder( 'vpi' ) ) { $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-vpi', 'title' => __( 'Purge All', 'litespeed-cache' ) . ' - VPI', 'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_VPI, false, '_ori' ), 'meta' => [ 'tabindex' => '0' ], ] ); } if ( $this->has_cache_folder( 'avatar' ) ) { $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-avatar', 'title' => esc_html__( 'Purge All', 'litespeed-cache' ) . ' - ' . esc_html__( 'Gravatar Cache', 'litespeed-cache' ), 'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_AVATAR, false, '_ori' ), 'meta' => [ 'tabindex' => '0' ], ] ); } do_action( 'litespeed_frontend_shortcut' ); } /** * Hooked to wp_before_admin_bar_render. * Adds links to the admin bar so users can quickly manage/purge. * * @since 1.7.2 Moved from admin_display.cls to gui.cls; Renamed from `add_quick_purge` to `backend_shortcut`. * @access public * @global \WP_Admin_Bar $wp_admin_bar * @return void */ public function backend_shortcut() { global $wp_admin_bar; if ( defined( 'LITESPEED_DISABLE_ALL' ) && LITESPEED_DISABLE_ALL ) { $wp_admin_bar->add_menu( [ 'id' => 'litespeed-menu', 'title' => '', 'href' => 'admin.php?page=litespeed-toolbox#settings-debug', 'meta' => [ 'tabindex' => 0, 'class' => 'litespeed-top-toolbar', ], ] ); $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-enable_all', 'title' => esc_html__( 'Enable All Features', 'litespeed-cache' ), 'href' => 'admin.php?page=litespeed-toolbox#settings-debug', 'meta' => [ 'tabindex' => '0' ], ] ); return; } $wp_admin_bar->add_menu( [ 'id' => 'litespeed-menu', 'title' => '', 'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_LSCACHE ), 'meta' => [ 'tabindex' => 0, 'class' => 'litespeed-top-toolbar', ], ] ); $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-bar-manage', 'title' => esc_html__( 'Manage', 'litespeed-cache' ), 'href' => 'admin.php?page=litespeed', 'meta' => [ 'tabindex' => '0' ], ] ); $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-bar-setting', 'title' => esc_html__( 'Settings', 'litespeed-cache' ), 'href' => 'admin.php?page=litespeed-cache', 'meta' => [ 'tabindex' => '0' ], ] ); if ( ! is_network_admin() ) { $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-bar-imgoptm', 'title' => esc_html__( 'Image Optimization', 'litespeed-cache' ), 'href' => 'admin.php?page=litespeed-img_optm', 'meta' => [ 'tabindex' => '0' ], ] ); } $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-all', 'title' => esc_html__( 'Purge All', 'litespeed-cache' ), 'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL ), 'meta' => [ 'tabindex' => '0' ], ] ); $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-all-lscache', 'title' => esc_html__( 'Purge All', 'litespeed-cache' ) . ' - ' . esc_html__( 'LSCache', 'litespeed-cache' ), 'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_LSCACHE ), 'meta' => [ 'tabindex' => '0' ], ] ); $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-cssjs', 'title' => esc_html__( 'Purge All', 'litespeed-cache' ) . ' - ' . esc_html__( 'CSS/JS Cache', 'litespeed-cache' ), 'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_CSSJS ), 'meta' => [ 'tabindex' => '0' ], ] ); if ( $this->conf( self::O_CDN_CLOUDFLARE ) ) { $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-cloudflare', 'title' => esc_html__( 'Purge All', 'litespeed-cache' ) . ' - ' . esc_html__( 'Cloudflare', 'litespeed-cache' ), 'href' => Utility::build_url( Router::ACTION_CDN_CLOUDFLARE, CDN\Cloudflare::TYPE_PURGE_ALL ), 'meta' => [ 'tabindex' => '0' ], ] ); } if ( defined( 'LSCWP_OBJECT_CACHE' ) ) { $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-object', 'title' => esc_html__( 'Purge All', 'litespeed-cache' ) . ' - ' . esc_html__( 'Object Cache', 'litespeed-cache' ), 'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_OBJECT ), 'meta' => [ 'tabindex' => '0' ], ] ); } if ( Router::opcache_enabled() ) { $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-opcache', 'title' => esc_html__( 'Purge All', 'litespeed-cache' ) . ' - ' . esc_html__( 'Opcode Cache', 'litespeed-cache' ), 'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_OPCACHE ), 'meta' => [ 'tabindex' => '0' ], ] ); } if ( $this->has_cache_folder( 'ccss' ) ) { $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-ccss', 'title' => esc_html__( 'Purge All', 'litespeed-cache' ) . ' - CCSS', 'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_CCSS ), 'meta' => [ 'tabindex' => '0' ], ] ); } if ( $this->has_cache_folder( 'ucss' ) ) { $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-ucss', 'title' => esc_html__( 'Purge All', 'litespeed-cache' ) . ' - UCSS', 'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_UCSS ), ] ); } if ( $this->has_cache_folder( 'localres' ) ) { $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-localres', 'title' => esc_html__( 'Purge All', 'litespeed-cache' ) . ' - ' . esc_html__( 'Localized Resources', 'litespeed-cache' ), 'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_LOCALRES ), 'meta' => [ 'tabindex' => '0' ], ] ); } if ( $this->has_cache_folder( 'lqip' ) ) { $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-placeholder', 'title' => esc_html__( 'Purge All', 'litespeed-cache' ) . ' - ' . esc_html__( 'LQIP Cache', 'litespeed-cache' ), 'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_LQIP ), 'meta' => [ 'tabindex' => '0' ], ] ); } if ( $this->has_cache_folder( 'vpi' ) ) { $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-vpi', 'title' => __( 'Purge All', 'litespeed-cache' ) . ' - VPI', 'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_VPI ), 'meta' => [ 'tabindex' => '0' ], ] ); } if ( $this->has_cache_folder( 'avatar' ) ) { $wp_admin_bar->add_menu( [ 'parent' => 'litespeed-menu', 'id' => 'litespeed-purge-avatar', 'title' => esc_html__( 'Purge All', 'litespeed-cache' ) . ' - ' . esc_html__( 'Gravatar Cache', 'litespeed-cache' ), 'href' => Utility::build_url( Router::ACTION_PURGE, Purge::TYPE_PURGE_ALL_AVATAR ), 'meta' => [ 'tabindex' => '0' ], ] ); } do_action( 'litespeed_backend_shortcut' ); } /** * Clear unfinished data link/button. * * @since 2.4.2 * @access public * * @param int $unfinished_num Number of unfinished images. * @return string HTML for action button. */ public static function img_optm_clean_up( $unfinished_num ) { return sprintf( ' %3$s', esc_url( Utility::build_url( Router::ACTION_IMG_OPTM, Img_Optm::TYPE_CLEAN ) ), esc_attr__( 'Remove all previous unfinished image optimization requests.', 'litespeed-cache' ), esc_html__( 'Clean Up Unfinished Data', 'litespeed-cache' ) . ( $unfinished_num ? ': ' . Admin_Display::print_plural( $unfinished_num, 'image' ) : '' ) ); } /** * Generate install link. * * @since 2.4.2 * @access public * * @param string $title Plugin title. * @param string $name Slug. * @param string $v Version (unused, kept for BC). * @return string HTML link. */ public static function plugin_install_link( $title, $name, $v ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable $url = wp_nonce_url( self_admin_url( 'update.php?action=install-plugin&plugin=' . $name ), 'install-plugin_' . $name ); $action = sprintf( '%5$s', esc_url( $url ), esc_attr( $name ), esc_attr( $title ), esc_attr( sprintf( __( 'Install %s', 'litespeed-cache' ), $title ) ), esc_html__( 'Install Now', 'litespeed-cache' ) ); return $action; } /** * Generate upgrade link. * * @since 2.4.2 * @access public * * @param string $title Plugin title. * @param string $name Slug. * @param string $v Version string. * @return string HTML message with links. */ public static function plugin_upgrade_link( $title, $name, $v ) { $details_url = self_admin_url( 'plugin-install.php?tab=plugin-information&plugin=' . $name . '§ion=changelog&TB_iframe=true&width=600&height=800' ); $file = $name . '/' . $name . '.php'; $msg = sprintf( /* translators: 1: details URL, 2: class/aria, 3: version, 4: update URL, 5: class/aria */ __('View version %3$s details or update now.', 'litespeed-cache'), esc_url( $details_url ), sprintf( 'class="thickbox open-plugin-details-modal" aria-label="%s"', esc_attr( sprintf( /* translators: 1: plugin title, 2: version */ __( 'View %1$s version %2$s details', 'litespeed-cache' ), $title, $v ) ) ), esc_html( $v ), esc_url( wp_nonce_url( self_admin_url( 'update.php?action=upgrade-plugin&plugin=' ) . $file, 'upgrade-plugin_' . $file ) ), sprintf( 'class="update-link" aria-label="%s"', esc_attr( sprintf( /* translators: %s: plugin title */ __( 'Update %s now', 'litespeed-cache' ), $title ) ) ) ); return $msg; } /** * Finalize buffer by GUI class. * * @since 1.6 * @access public * * @param string $buffer HTML buffer. * @return string Filtered buffer. */ public function finalize( $buffer ) { $buffer = $this->_clean_wrapper( $buffer ); // Maybe restore doc.ref. if ( $this->conf( Base::O_GUEST ) && false !== strpos( $buffer, '' ) && defined( 'LITESPEED_IS_HTML' ) ) { $buffer = $this->_enqueue_guest_docref_js( $buffer ); } if ( defined( 'LITESPEED_GUEST' ) && LITESPEED_GUEST && false !== strpos( $buffer, '' ) && defined( 'LITESPEED_IS_HTML' ) ) { $buffer = $this->_enqueue_guest_js( $buffer ); } return $buffer; } /** * Append guest restore doc.ref JS for organic traffic count. * * @since 4.4.6 * * @param string $buffer HTML buffer. * @return string Buffer with inline script injected. */ private function _enqueue_guest_docref_js( $buffer ) { $js_con = File::read( LSCWP_DIR . self::LIB_GUEST_DOCREF_JS ); $buffer = preg_replace( '//', '', $buffer, 1 ); return $buffer; } /** * Append guest JS to update vary. * * @since 4.0 * * @param string $buffer HTML buffer. * @return string Buffer with inline script injected. */ private function _enqueue_guest_js( $buffer ) { $js_con = File::read( LSCWP_DIR . self::LIB_GUEST_JS ); // Build path for guest endpoint using wp_parse_url for compatibility. $guest_update_path = wp_parse_url( LSWCP_PLUGIN_URL . self::PHP_GUEST, PHP_URL_PATH ); $js_con = str_replace( 'litespeed_url', esc_url( $guest_update_path ), $js_con ); $buffer = preg_replace( '/<\/body>/', '', $buffer, 1 ); return $buffer; } /** * Clean wrapper from buffer. * * @since 1.4 * @since 1.6 Converted to private with adding prefix _. * @access private * * @param string $buffer HTML buffer. * @return string Cleaned buffer. */ private function _clean_wrapper( $buffer ) { if ( self::$_clean_counter < 1 ) { self::debug2( 'bypassed by no counter' ); return $buffer; } self::debug2( 'start cleaning counter ' . self::$_clean_counter ); for ( $i = 1; $i <= self::$_clean_counter; $i++ ) { // If miss beginning. $start = strpos( $buffer, self::clean_wrapper_begin( $i ) ); if ( false === $start ) { $buffer = str_replace( self::clean_wrapper_end( $i ), '', $buffer ); self::debug2( "lost beginning wrapper $i" ); continue; } // If miss end. $end_wrapper = self::clean_wrapper_end( $i ); $end = strpos( $buffer, $end_wrapper ); if ( false === $end ) { $buffer = str_replace( self::clean_wrapper_begin( $i ), '', $buffer ); self::debug2( "lost ending wrapper $i" ); continue; } // Now replace wrapped content. $buffer = substr_replace( $buffer, '', $start, $end - $start + strlen( $end_wrapper ) ); self::debug2( "cleaned wrapper $i" ); } return $buffer; } /** * Display a to-be-removed HTML wrapper (begin tag). * * @since 1.4 * @access public * * @param int|false $counter Optional explicit wrapper id; auto-increment if false. * @return string Wrapper begin HTML comment. */ public static function clean_wrapper_begin( $counter = false ) { if ( false === $counter ) { ++self::$_clean_counter; $counter = self::$_clean_counter; self::debug( 'clean wrapper ' . $counter . ' begin' ); } return ''; } /** * Display a to-be-removed HTML wrapper (end tag). * * @since 1.4 * @access public * * @param int|false $counter Optional explicit wrapper id; use latest if false. * @return string Wrapper end HTML comment. */ public static function clean_wrapper_end( $counter = false ) { if ( false === $counter ) { $counter = self::$_clean_counter; self::debug( 'clean wrapper ' . $counter . ' end' ); } return ''; } } src/rest.cls.php000064400000022125152075713340007612 0ustar00 'POST', 'callback' => [ $this, 'toggle_crawler_state' ], 'permission_callback' => function () { return current_user_can( 'manage_network_options' ) || current_user_can( 'manage_options' ); }, ] ); register_rest_route( 'litespeed/v1', '/tool/check_ip', [ 'methods' => 'GET', 'callback' => [ $this, 'check_ip' ], 'permission_callback' => function () { return current_user_can( 'manage_network_options' ) || current_user_can( 'manage_options' ); }, ] ); register_rest_route( 'litespeed/v1', '/guest/sync', [ 'methods' => 'GET', 'callback' => [ $this, 'guest_sync' ], 'permission_callback' => function () { return current_user_can( 'manage_network_options' ) || current_user_can( 'manage_options' ); }, ] ); // IP callback validate register_rest_route( 'litespeed/v3', '/ip_validate', [ 'methods' => 'POST', 'callback' => [ $this, 'ip_validate' ], 'permission_callback' => [ $this, 'is_from_cloud' ], ] ); // 1.2. WP REST Dryrun Callback register_rest_route( 'litespeed/v3', '/wp_rest_echo', [ 'methods' => 'POST', 'callback' => [ $this, 'wp_rest_echo' ], 'permission_callback' => [ $this, 'is_from_cloud' ], ] ); register_rest_route( 'litespeed/v3', '/ping', [ 'methods' => 'POST', 'callback' => [ $this, 'ping' ], 'permission_callback' => [ $this, 'is_from_cloud' ], ] ); // CDN setup callback notification register_rest_route( 'litespeed/v3', '/cdn_status', [ 'methods' => 'POST', 'callback' => [ $this, 'cdn_status' ], 'permission_callback' => [ $this, 'is_from_cloud' ], ] ); // Image optm notify_img // Need validation register_rest_route( 'litespeed/v1', '/notify_img', [ 'methods' => 'POST', 'callback' => [ $this, 'notify_img' ], 'permission_callback' => [ $this, 'is_from_cloud' ], ] ); register_rest_route( 'litespeed/v1', '/notify_ccss', [ 'methods' => 'POST', 'callback' => [ $this, 'notify_ccss' ], 'permission_callback' => [ $this, 'is_from_cloud' ], ] ); register_rest_route( 'litespeed/v1', '/notify_ucss', [ 'methods' => 'POST', 'callback' => [ $this, 'notify_ucss' ], 'permission_callback' => [ $this, 'is_from_cloud' ], ] ); register_rest_route( 'litespeed/v1', '/notify_vpi', [ 'methods' => 'POST', 'callback' => [ $this, 'notify_vpi' ], 'permission_callback' => [ $this, 'is_from_cloud' ], ] ); register_rest_route( 'litespeed/v3', '/err_domains', [ 'methods' => 'POST', 'callback' => [ $this, 'err_domains' ], 'permission_callback' => [ $this, 'is_from_cloud' ], ] ); // Image optm check_img // Need validation register_rest_route( 'litespeed/v1', '/check_img', [ 'methods' => 'POST', 'callback' => [ $this, 'check_img' ], 'permission_callback' => [ $this, 'is_from_cloud' ], ] ); } /** * Call to freeze or melt the crawler clicked * * @since 4.3 */ public function toggle_crawler_state() { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- REST API nonce verified by WordPress $crawler_id = isset( $_POST['crawler_id'] ) ? sanitize_text_field( wp_unslash( $_POST['crawler_id'] ) ) : ''; if ( '' !== $crawler_id ) { return $this->cls( 'Crawler' )->toggle_activeness( $crawler_id ) ? 1 : 0; } } /** * Check if the request is from cloud nodes. * * @since 4.2 * @since 4.4.7 Token/API key validation makes IP validation redundant. * @return bool */ public function is_from_cloud() { return $this->cls( 'Cloud' )->is_from_cloud(); } /** * Ping pong. * * @since 3.0.4 * @return mixed */ public function ping() { return $this->cls( 'Cloud' )->ping(); } /** * Launch IP check. * * @since 3.0 * @return mixed */ public function check_ip() { return Tool::cls()->check_ip(); } /** * Sync Guest Mode IP/UA lists. * * @since 7.7 * @return array */ public function guest_sync() { return Guest::cls()->sync_lists(); } /** * Validate IPs from cloud. * * @since 3.0 * @return mixed */ public function ip_validate() { return $this->cls( 'Cloud' )->ip_validate(); } /** * REST echo helper. * * @since 3.0 * @return mixed */ public function wp_rest_echo() { return $this->cls( 'Cloud' )->wp_rest_echo(); } /** * Endpoint to notify plugin of CDN status updates. * * @since 7.0 * @return mixed */ public function cdn_status() { return $this->cls( 'Cloud' )->update_cdn_status(); } /** * Image optimization notification. * * @since 3.0 * @return mixed */ public function notify_img() { return Img_Optm::cls()->notify_img(); } /** * Critical CSS notification. * * @since 7.1 * @return mixed */ public function notify_ccss() { self::debug( 'notify_ccss' ); return CSS::cls()->notify(); } /** * Unique CSS notification. * * @since 5.2 * @return mixed */ public function notify_ucss() { self::debug( 'notify_ucss' ); return UCSS::cls()->notify(); } /** * Viewport Images notification. * * @since 4.7 * @return mixed */ public function notify_vpi() { self::debug( 'notify_vpi' ); return VPI::cls()->notify(); } /** * Error domain report from cloud. * * @since 4.7 * @return mixed */ public function err_domains() { self::debug( 'err_domains' ); return $this->cls( 'Cloud' )->rest_err_domains(); } /** * Launch image check. * * @since 3.0 * @return mixed */ public function check_img() { return Img_Optm::cls()->check_img(); } /** * Return a standardized error payload. * * @since 5.7.0.1 * @param string|int $code Error code. * @return array */ public static function err( $code ) { return [ '_res' => 'err', '_msg' => $code, ]; } /** * Set internal REST tag to ON. * * @since 2.9.4 * @param mixed $not_used Passthrough value from the filter. * @return mixed */ public function set_internal_rest_on( $not_used = null ) { $this->_internal_rest_status = true; Debug2::debug2( '[REST] ✅ Internal REST ON [filter] rest_request_before_callbacks' ); return $not_used; } /** * Set internal REST tag to OFF. * * @since 2.9.4 * @param mixed $not_used Passthrough value from the filter. * @return mixed */ public function set_internal_rest_off( $not_used = null ) { $this->_internal_rest_status = false; Debug2::debug2( '[REST] ❎ Internal REST OFF [filter] rest_request_after_callbacks' ); return $not_used; } /** * Whether current request is an internal REST call. * * @since 2.9.4 * @return bool */ public function is_internal_rest() { return $this->_internal_rest_status; } /** * Check whether a URL or current page is a REST request. * * @since 2.9.3 * @since 2.9.4 Moved here from Utility, dropped static. * @param string|false $url URL to check; when false checks current request. * @return bool */ public function is_rest( $url = false ) { // For WP 4.4.0- compatibility. if ( ! function_exists( 'rest_get_url_prefix' ) ) { return ( defined( 'REST_REQUEST' ) && REST_REQUEST ); } $prefix = rest_get_url_prefix(); // Case #1: After WP_REST_Request initialization. if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { return true; } // Case #2: Support "plain" permalink settings. // phpcs:ignore WordPress.Security.NonceVerification.Recommended $route = isset( $_GET['rest_route'] ) ? sanitize_text_field( wp_unslash( $_GET['rest_route'] ) ) : ''; if ( $route && 0 === strpos( trim( $route, '\\/' ), $prefix, 0 ) ) { return true; } if ( !$url ) { return false; } // Case #3: URL path begins with wp-json/ (REST prefix) – safe for subfolder installs. $rest_url = wp_parse_url( site_url( $prefix ) ); $current_url = wp_parse_url( $url ); if ( false !== $current_url && ! empty( $current_url['path'] ) && false !== $rest_url && ! empty( $rest_url['path'] ) ) { return 0 === strpos( $current_url['path'], $rest_url['path'] ); } return false; } } src/placeholder.cls.php000064400000043666152075713340011134 0ustar00 */ private $_placeholder_resp_dict = []; /** * Keys currently queued within this request. * * @var array */ private $_ph_queue = []; /** * Stats & request summary for throttling. * * @var array */ protected $_summary; /** * Init * * @since 3.0 */ public function __construct() { $this->_conf_placeholder_resp = defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( self::O_MEDIA_PLACEHOLDER_RESP ); $this->_conf_placeholder_resp_svg = $this->conf( self::O_MEDIA_PLACEHOLDER_RESP_SVG ); $this->_conf_lqip = ! defined( 'LITESPEED_GUEST_OPTM' ) && $this->conf( self::O_MEDIA_LQIP ); $this->_conf_lqip_qual = $this->conf( self::O_MEDIA_LQIP_QUAL ); $this->_conf_lqip_min_w = $this->conf( self::O_MEDIA_LQIP_MIN_W ); $this->_conf_lqip_min_h = $this->conf( self::O_MEDIA_LQIP_MIN_H ); $this->_conf_placeholder_resp_async = $this->conf( self::O_MEDIA_PLACEHOLDER_RESP_ASYNC ); $this->_conf_placeholder_resp_color = $this->conf( self::O_MEDIA_PLACEHOLDER_RESP_COLOR ); $this->_conf_ph_default = $this->conf(self::O_MEDIA_LAZY_PLACEHOLDER) ? $this->conf(self::O_MEDIA_LAZY_PLACEHOLDER) : LITESPEED_PLACEHOLDER; $this->_summary = self::get_summary(); } /** * Init Placeholder. */ public function init() { Debug2::debug2( '[LQIP] init' ); add_action( 'litespeed_after_admin_init', [ $this, 'after_admin_init' ] ); } /** * Display column in Media. * * @since 3.0 * @access public */ public function after_admin_init() { if ( $this->_conf_lqip ) { add_filter( 'manage_media_columns', [ $this, 'media_row_title' ] ); add_filter( 'manage_media_custom_column', [ $this, 'media_row_actions' ], 10, 2 ); add_action( 'litespeed_media_row_lqip', [ $this, 'media_row_con' ] ); } } /** * Media Admin Menu -> LQIP column header. * * @since 3.0 * @param array $posts_columns Columns. * @return array */ public function media_row_title( $posts_columns ) { $posts_columns['lqip'] = __( 'LQIP', 'litespeed-cache' ); return $posts_columns; } /** * Media Admin Menu -> LQIP Column renderer trigger. * * @since 3.0 * @param string $column_name Column name. * @param int $post_id Attachment ID. * @return void */ public function media_row_actions( $column_name, $post_id ) { if ( 'lqip' !== $column_name ) { return; } do_action( 'litespeed_media_row_lqip', $post_id ); } /** * Display LQIP column. * * @since 3.0 * @param int $post_id Attachment ID. * @return void */ public function media_row_con( $post_id ) { $meta_value = wp_get_attachment_metadata( $post_id ); if ( empty( $meta_value['file'] ) ) { return; } $total_files = 0; // List all sizes. $all_sizes = [ $meta_value['file'] ]; $size_path = pathinfo( $meta_value['file'], PATHINFO_DIRNAME ) . '/'; if ( ! empty( $meta_value['sizes'] ) && is_array( $meta_value['sizes'] ) ) { foreach ( $meta_value['sizes'] as $v ) { if ( ! empty( $v['file'] ) ) { $all_sizes[] = $size_path . $v['file']; } } } foreach ( $all_sizes as $short_path ) { $lqip_folder = LITESPEED_STATIC_DIR . '/lqip/' . $short_path; if ( is_dir( $lqip_folder ) ) { Debug2::debug( '[LQIP] Found folder: ' . $short_path ); // List all files. foreach ( scandir( $lqip_folder ) as $v ) { if ( '.' === $v || '..' === $v ) { continue; } if ( 0 === $total_files ) { $file = Str::trim_quotes( File::read( $lqip_folder . '/' . $v ) ); echo '
' .
							esc_attr( sprintf( __( 'LQIP image preview for size %s', 'litespeed-cache' ), $v ) ) .
							'
'; } echo ''; ++$total_files; } } } if ( 0 === $total_files ) { echo '—'; } } /** * Replace image HTML with placeholder-based lazy version. * * @since 3.0 * @param string $html Original HTML. * @param string $src Image source URL. * @param string $size Requested size (e.g. "300x200"). * @return string Modified HTML. */ public function replace( $html, $src, $size ) { // Check if need to enable responsive placeholder or not. $ph_candidate = $this->_placeholder( $src, $size ); $this_placeholder = $ph_candidate ? $ph_candidate : $this->_conf_ph_default; $additional_attr = ''; if ( $this->_conf_lqip && $this_placeholder !== $this->_conf_ph_default ) { Debug2::debug2( '[LQIP] Use resp LQIP [size] ' . $size ); $additional_attr = ' data-placeholder-resp="' . esc_attr( Str::trim_quotes( $size ) ) . '"'; } $snippet = ( defined( 'LITESPEED_GUEST_OPTM' ) || $this->conf( self::O_OPTM_NOSCRIPT_RM ) ) ? '' : ''; $html = preg_replace( [ '/\s+src=/i', '/\s+srcset=/i', '/\s+sizes=/i', ], [ ' data-src=', ' data-srcset=', ' data-sizes=', ], $html ); $html = preg_replace( '/_conf_placeholder_resp ) { return false; } // If use local generator. if ( ! $this->_conf_lqip || ! $this->_lqip_size_check( $size ) ) { return $this->_generate_placeholder_locally( $size ); } Debug2::debug2( '[LQIP] Resp LQIP process [src] ' . $src . ' [size] ' . $size ); $arr_key = $size . ' ' . $src; // Check if its already in dict or not. if ( ! empty( $this->_placeholder_resp_dict[ $arr_key ] ) ) { Debug2::debug2( '[LQIP] already in dict' ); return $this->_placeholder_resp_dict[ $arr_key ]; } // Need to generate the responsive placeholder. $placeholder_realpath = $this->_placeholder_realpath( $src, $size ); // todo: give offload API. if ( file_exists( $placeholder_realpath ) ) { Debug2::debug2( '[LQIP] file exists' ); $this->_placeholder_resp_dict[ $arr_key ] = File::read( $placeholder_realpath ); return $this->_placeholder_resp_dict[ $arr_key ]; } // Prevent repeated requests in same request. if ( in_array( $arr_key, $this->_ph_queue, true ) ) { Debug2::debug2( '[LQIP] file bypass generating due to in queue' ); return $this->_generate_placeholder_locally( $size ); } $hit = Utility::str_hit_array( $src, $this->conf( self::O_MEDIA_LQIP_EXC ) ); if ( $hit ) { Debug2::debug2( '[LQIP] file bypass generating due to exclude setting [hit] ' . $hit ); return $this->_generate_placeholder_locally( $size ); } $this->_ph_queue[] = $arr_key; // Send request to generate placeholder. if ( ! $this->_conf_placeholder_resp_async ) { // If requested recently, bypass. if ( $this->_summary && ! empty( $this->_summary['curr_request'] ) && ( time() - (int) $this->_summary['curr_request'] ) < 300 ) { Debug2::debug2( '[LQIP] file bypass generating due to interval limit' ); return false; } // Generate immediately. $this->_placeholder_resp_dict[ $arr_key ] = $this->_generate_placeholder( $arr_key ); return $this->_placeholder_resp_dict[ $arr_key ]; } // Prepare default svg placeholder as tmp placeholder. $tmp_placeholder = $this->_generate_placeholder_locally( $size ); // Store it to prepare for cron. $queue = $this->load_queue( 'lqip' ); if ( in_array( $arr_key, $queue, true ) ) { Debug2::debug2( '[LQIP] already in queue' ); return $tmp_placeholder; } if ( count( $queue ) > 500 ) { Debug2::debug2( '[LQIP] queue is full' ); return $tmp_placeholder; } $queue[] = $arr_key; $this->save_queue( 'lqip', $queue ); Debug2::debug( '[LQIP] Added placeholder queue' ); return $tmp_placeholder; } /** * Generate realpath of placeholder file. * * @since 2.5.1 * @access private * @param string $src Image source URL. * @param string $size Size string "WIDTHxHEIGHT". * @return string Absolute file path. */ private function _placeholder_realpath( $src, $size ) { // Use LQIP Cloud generator, each image placeholder will be separately stored. // Compatibility with WebP and AVIF. $src = Utility::drop_webp( $src ); $filepath_prefix = $this->_build_filepath_prefix( 'lqip' ); // External images will use cache folder directly. $domain = wp_parse_url( $src, PHP_URL_HOST ); if ( $domain && ! Utility::internal( $domain ) ) { // todo: need to improve `util:internal()` to include `CDN::internal()` $md5 = md5($src); return LITESPEED_STATIC_DIR . $filepath_prefix . 'remote/' . substr( $md5, 0, 1 ) . '/' . substr( $md5, 1, 1 ) . '/' . $md5 . '.' . $size; } // Drop domain. $short_path = Utility::att_short_path( $src ); return LITESPEED_STATIC_DIR . $filepath_prefix . $short_path . '/' . $size; } /** * Cron placeholder generation. * * @since 2.5.1 * @param bool $do_continue If true, process full queue in one run. * @return void */ public static function cron( $do_continue = false ) { $_instance = self::cls(); $queue = $_instance->load_queue( 'lqip' ); if ( empty( $queue ) ) { return; } // For cron, need to check request interval too. if ( ! $do_continue ) { if ( ! empty( $_instance->_summary['curr_request'] ) && ( time() - (int) $_instance->_summary['curr_request'] ) < 300 ) { Debug2::debug( '[LQIP] Last request not done' ); return; } } foreach ( $queue as $v ) { Debug2::debug( '[LQIP] cron job [size] ' . $v ); $res = $_instance->_generate_placeholder( $v, true ); // Exit queue if out of quota. if ( 'out_of_quota' === $res ) { return; } // Only request first one unless continuing. if ( ! $do_continue ) { return; } } } /** * Generate placeholder locally (SVG). * * @since 3.0 * @access private * @param string $size Size string "WIDTHxHEIGHT". * @return string Data URL for SVG placeholder. */ private function _generate_placeholder_locally( $size ) { Debug2::debug2( '[LQIP] _generate_placeholder local [size] ' . $size ); $size = explode( 'x', $size ); $svg = str_replace( [ '{width}', '{height}', '{color}' ], [ (int) $size[0], (int) $size[1], $this->_conf_placeholder_resp_color ], $this->_conf_placeholder_resp_svg ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode return 'data:image/svg+xml;base64,' . base64_encode( $svg ); } /** * Send to LiteSpeed API to generate placeholder (and persist). * * @since 2.5.1 * @access private * @param string $raw_size_and_src Concatenated "SIZE SRC". * @param bool $from_cron If true, called from cron context. * @return string Data URL placeholder. */ private function _generate_placeholder( $raw_size_and_src, $from_cron = false ) { // Parse containing size and src info. $size_and_src = explode( ' ', $raw_size_and_src, 2 ); $size = $size_and_src[0]; if ( empty( $size_and_src[1] ) ) { $this->_popup_and_save( $raw_size_and_src ); Debug2::debug( '[LQIP] ❌ No src [raw] ' . $raw_size_and_src ); return $this->_generate_placeholder_locally( $size ); } $src = $size_and_src[1]; $file = $this->_placeholder_realpath( $src, $size ); // Local generate SVG to serve (repeated here to clear queue if settings changed). if ( ! $this->_conf_lqip || ! $this->_lqip_size_check( $size ) ) { $data = $this->_generate_placeholder_locally( $size ); } else { $err = false; $allowance = Cloud::cls()->allowance( Cloud::SVC_LQIP, $err ); if ( ! $allowance ) { Debug2::debug( '[LQIP] ❌ No credit: ' . $err ); $err && Admin_Display::error( Error::msg( $err ) ); if ( $from_cron ) { return 'out_of_quota'; } return $this->_generate_placeholder_locally( $size ); } // Generate LQIP. list( $width, $height ) = explode( 'x', $size ); $req_data = [ 'width' => (int) $width, 'height' => (int) $height, 'url' => Utility::drop_webp( $src ), 'quality' => (int) $this->_conf_lqip_qual, ]; // Check if the image is 404 first. if ( File::is_404( $req_data['url'] ) ) { $this->_popup_and_save( $raw_size_and_src, true ); $this->_append_exc( $src ); Debug2::debug( '[LQIP] 404 before request [src] ' . $req_data['url'] ); return $this->_generate_placeholder_locally( $size ); } // Update request status. $this->_summary['curr_request'] = time(); self::save_summary(); $json = Cloud::post( Cloud::SVC_LQIP, $req_data, 120 ); if ( ! is_array( $json ) ) { return $this->_generate_placeholder_locally( $size ); } if ( empty( $json['lqip'] ) || 0 !== strpos( $json['lqip'], 'data:image/svg+xml' ) ) { // Image error, pop up the current queue. $this->_popup_and_save( $raw_size_and_src, true ); $this->_append_exc( $src ); Debug2::debug( '[LQIP] wrong response format', $json ); return $this->_generate_placeholder_locally( $size ); } $data = $json['lqip']; Debug2::debug( '[LQIP] _generate_placeholder LQIP' ); } // Write to file. File::save( $file, $data, true ); // Save summary data. $this->_summary['last_spent'] = time() - (int) $this->_summary['curr_request']; $this->_summary['last_request'] = $this->_summary['curr_request']; $this->_summary['curr_request'] = 0; self::save_summary(); $this->_popup_and_save( $raw_size_and_src ); Debug2::debug( '[LQIP] saved LQIP ' . $file ); return $data; } /** * Check if the size is valid to send LQIP request or not. * * @since 3.0 * @param string $size Size string "WIDTHxHEIGHT". * @return bool True if meets minimums. */ private function _lqip_size_check( $size ) { $size = explode( 'x', $size ); if ( ( (int) $size[0] >= (int) $this->_conf_lqip_min_w ) || ( (int) $size[1] >= (int) $this->_conf_lqip_min_h ) ) { return true; } Debug2::debug2( '[LQIP] Size too small' ); return false; } /** * Add to LQIP exclude list. * * @since 3.4 * @param string $src Image URL. * @return void */ private function _append_exc( $src ) { $val = $this->conf( self::O_MEDIA_LQIP_EXC ); $val[] = $src; $this->cls( 'Conf' )->update( self::O_MEDIA_LQIP_EXC, $val ); Debug2::debug( '[LQIP] Appended to LQIP Excludes [URL] ' . $src ); } /** * Pop up the current request from queue and save. * * @since 3.0 * @param string $raw_size_and_src Concatenated "SIZE SRC". * @param bool $append_to_exc If true, also add to exclusion list. * @return void */ private function _popup_and_save( $raw_size_and_src, $append_to_exc = false ) { $queue = $this->load_queue( 'lqip' ); if ( ! empty( $queue ) && in_array( $raw_size_and_src, $queue, true ) ) { $idx = array_search( $raw_size_and_src, $queue, true ); if ( false !== $idx ) { unset( $queue[ $idx ] ); } } if ( $append_to_exc ) { $size_and_src = explode( ' ', $raw_size_and_src, 2 ); if (isset( $size_and_src[1] ) && $size_and_src[1]) { $this_src = $size_and_src[1]; // Append to lqip exc setting first. $this->_append_exc( $this_src ); // Check if other queues contain this src or not. if ( $queue ) { foreach ( $queue as $k => $raw_item ) { $parsed = explode( ' ', $raw_item, 2 ); if ( empty( $parsed[1] ) ) { continue; } if ( $parsed[1] === $this_src ) { unset( $queue[ $k ] ); } } } } } $this->save_queue( 'lqip', $queue ); } /** * Handle all request actions from main cls. * * @since 2.5.1 * @access public * @return void */ public function handler() { $type = Router::verify_type(); switch ( $type ) { case self::TYPE_GENERATE: self::cron( true ); break; case self::TYPE_CLEAR_Q: $this->clear_q( 'lqip' ); break; default: break; } Admin::redirect(); } } src/file.cls.php000064400000025107152075713340007557 0ustar00seek(PHP_INT_MAX); return $file->key() + 1; } /** * Read data from file * * @since 1.1.0 * @param string $filename * @param int $start_line * @param int $lines */ public static function read( $filename, $start_line = null, $lines = null ) { if (!file_exists($filename)) { return ''; } if (!is_readable($filename)) { return false; } if ($start_line !== null) { $res = array(); $file = new \SplFileObject($filename); $file->seek($start_line); if ($lines === null) { while (!$file->eof()) { $res[] = rtrim($file->current(), "\n"); $file->next(); } } else { for ($i = 0; $i < $lines; $i++) { if ($file->eof()) { break; } $res[] = rtrim($file->current(), "\n"); $file->next(); } } unset($file); return $res; } $content = file_get_contents($filename); $content = self::remove_zero_space($content); return $content; } /** * Append data to file * * @since 1.1.5 * @access public * @param string $filename * @param string $data * @param boolean $mkdir * @param boolean $silence Used to avoid WP's functions are used */ public static function append( $filename, $data, $mkdir = false, $silence = true ) { return self::save($filename, $data, $mkdir, true, $silence); } /** * Save data to file * * @since 1.1.0 * @param string $filename * @param string $data * @param boolean $mkdir * @param boolean $append If the content needs to be appended * @param boolean $silence Used to avoid WP's functions are used */ public static function save( $filename, $data, $mkdir = false, $append = false, $silence = true ) { if (is_null($filename)) { return $silence ? false : __('Filename is empty!', 'litespeed-cache'); } $error = false; $folder = dirname($filename); // mkdir if folder does not exist if (!file_exists($folder)) { if (!$mkdir) { return $silence ? false : sprintf(__('Folder does not exist: %s', 'litespeed-cache'), $folder); } set_error_handler('litespeed_exception_handler'); try { mkdir($folder, 0755, true); // Create robots.txt file to forbid search engine indexes if (!file_exists(LITESPEED_STATIC_DIR . '/robots.txt')) { file_put_contents(LITESPEED_STATIC_DIR . '/robots.txt', "User-agent: *\nDisallow: /\n"); } } catch (\ErrorException $ex) { return $silence ? false : sprintf(__('Can not create folder: %1$s. Error: %2$s', 'litespeed-cache'), $folder, $ex->getMessage()); } restore_error_handler(); } if (!file_exists($filename)) { if (!is_writable($folder)) { return $silence ? false : sprintf(__('Folder is not writable: %s.', 'litespeed-cache'), $folder); } set_error_handler('litespeed_exception_handler'); try { touch($filename); } catch (\ErrorException $ex) { return $silence ? false : sprintf(__('File %s is not writable.', 'litespeed-cache'), $filename); } restore_error_handler(); } elseif (!is_writable($filename)) { return $silence ? false : sprintf(__('File %s is not writable.', 'litespeed-cache'), $filename); } $data = self::remove_zero_space($data); $ret = file_put_contents($filename, $data, $append ? FILE_APPEND : LOCK_EX); if ($ret === false) { return $silence ? false : sprintf(__('Failed to write to %s.', 'litespeed-cache'), $filename); } return true; } /** * Remove Unicode zero-width space <200b><200c> * * @since 2.1.2 * @since 2.9 changed to public */ public static function remove_zero_space( $content ) { if (is_array($content)) { $content = array_map(__CLASS__ . '::remove_zero_space', $content); return $content; } // Remove UTF-8 BOM if present if (substr($content, 0, 3) === "\xEF\xBB\xBF") { $content = substr($content, 3); } $content = str_replace("\xe2\x80\x8b", '', $content); $content = str_replace("\xe2\x80\x8c", '', $content); $content = str_replace("\xe2\x80\x8d", '', $content); return $content; } /** * Appends an array of strings into a file (.htaccess ), placing it between * BEGIN and END markers. * * Replaces existing marked info. Retains surrounding * data. Creates file if none exists. * * @param string $filename Filename to alter. * @param string $marker The marker to alter. * @param array|string|false $insertion The new content to insert. * @param bool $prepend Prepend insertion if not exist. * @return bool True on write success, false on failure. */ public static function insert_with_markers( $filename, $insertion = false, $marker = false, $prepend = false ) { if (!$marker) { $marker = self::MARKER; } if (!$insertion) { $insertion = array(); } return self::_insert_with_markers($filename, $marker, $insertion, $prepend); // todo: capture exceptions } /** * Return wrapped block data with marker * * @param string $insertion * @param string $marker * @return string The block data */ public static function wrap_marker_data( $insertion, $marker = false ) { if (!$marker) { $marker = self::MARKER; } $start_marker = "# BEGIN {$marker}"; $end_marker = "# END {$marker}"; $new_data = implode("\n", array_merge(array( $start_marker ), $insertion, array( $end_marker ))); return $new_data; } /** * Touch block data from file, return with marker * * @param string $filename * @param string $marker * @return string The current block data */ public static function touch_marker_data( $filename, $marker = false ) { if (!$marker) { $marker = self::MARKER; } $result = self::_extract_from_markers($filename, $marker); if (!$result) { return false; } $start_marker = "# BEGIN {$marker}"; $end_marker = "# END {$marker}"; $new_data = implode("\n", array_merge(array( $start_marker ), $result, array( $end_marker ))); return $new_data; } /** * Extracts strings from between the BEGIN and END markers in the .htaccess file. * * @param string $filename * @param string $marker * @return array An array of strings from a file (.htaccess ) from between BEGIN and END markers. */ public static function extract_from_markers( $filename, $marker = false ) { if (!$marker) { $marker = self::MARKER; } return self::_extract_from_markers($filename, $marker); } /** * Extracts strings from between the BEGIN and END markers in the .htaccess file. * * @param string $filename * @param string $marker * @return array An array of strings from a file (.htaccess ) from between BEGIN and END markers. */ private static function _extract_from_markers( $filename, $marker ) { $result = array(); if (!file_exists($filename)) { return $result; } if ($markerdata = explode("\n", implode('', file($filename)))) { $state = false; foreach ($markerdata as $markerline) { if (strpos($markerline, '# END ' . $marker) !== false) { $state = false; } if ($state) { $result[] = $markerline; } if (strpos($markerline, '# BEGIN ' . $marker) !== false) { $state = true; } } } return array_map('trim', $result); } /** * Inserts an array of strings into a file (.htaccess ), placing it between BEGIN and END markers. * * Replaces existing marked info. Retains surrounding data. Creates file if none exists. * * NOTE: will throw error if failed * * @since 3.0- * @since 3.0 Throw errors if failed * @access private */ private static function _insert_with_markers( $filename, $marker, $insertion, $prepend = false ) { if (!file_exists($filename)) { if (!is_writable(dirname($filename))) { Error::t('W', dirname($filename)); } set_error_handler('litespeed_exception_handler'); try { touch($filename); } catch (\ErrorException $ex) { Error::t('W', $filename); } restore_error_handler(); } elseif (!is_writable($filename)) { Error::t('W', $filename); } if (!is_array($insertion)) { $insertion = explode("\n", $insertion); } $start_marker = "# BEGIN {$marker}"; $end_marker = "# END {$marker}"; $fp = fopen($filename, 'r+'); if (!$fp) { Error::t('W', $filename); } // Attempt to get a lock. If the filesystem supports locking, this will block until the lock is acquired. flock($fp, LOCK_EX); $lines = array(); while (!feof($fp)) { $lines[] = rtrim(fgets($fp), "\r\n"); } // Split out the existing file into the preceding lines, and those that appear after the marker $pre_lines = $post_lines = $existing_lines = array(); $found_marker = $found_end_marker = false; foreach ($lines as $line) { if (!$found_marker && false !== strpos($line, $start_marker)) { $found_marker = true; continue; } elseif (!$found_end_marker && false !== strpos($line, $end_marker)) { $found_end_marker = true; continue; } if (!$found_marker) { $pre_lines[] = $line; } elseif ($found_marker && $found_end_marker) { $post_lines[] = $line; } else { $existing_lines[] = $line; } } // Check to see if there was a change if ($existing_lines === $insertion) { flock($fp, LOCK_UN); fclose($fp); return true; } // Check if need to prepend data if not exist if ($prepend && !$post_lines) { // Generate the new file data $new_file_data = implode("\n", array_merge(array( $start_marker ), $insertion, array( $end_marker ), $pre_lines)); } else { // Generate the new file data $new_file_data = implode("\n", array_merge($pre_lines, array( $start_marker ), $insertion, array( $end_marker ), $post_lines)); } // Write to the start of the file, and truncate it to that length fseek($fp, 0); $bytes = fwrite($fp, $new_file_data); if ($bytes) { ftruncate($fp, ftell($fp)); } fflush($fp); flock($fp, LOCK_UN); fclose($fp); return (bool) $bytes; } } src/debug2.cls.php000064400000044632152075713340010014 0ustar00_maybe_init_folder(); self::$log_path = $this->path( 'debug' ); $ua = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : ''; if ( '' !== $ua && 0 === strpos( $ua, 'lscache_' ) ) { self::$log_path = $this->path( 'crawler' ); } ! defined( 'LSCWP_LOG_TAG' ) && define( 'LSCWP_LOG_TAG', get_current_blog_id() ); if ( $this->conf( Base::O_DEBUG_LEVEL ) ) { ! defined( 'LSCWP_LOG_MORE' ) && define( 'LSCWP_LOG_MORE', true ); } defined( 'LSCWP_DEBUG_EXC_STRINGS' ) || define( 'LSCWP_DEBUG_EXC_STRINGS', $this->conf( Base::O_DEBUG_EXC_STRINGS ) ); } /** * Disable all functionalities temporarily (toggle). * * @since 7.4 * @access public * * @param int $time How long (in seconds) to disable LSC functions. */ public static function tmp_disable( $time = 86400 ) { $conf = Conf::cls(); $disabled = self::cls()->conf( Base::DEBUG_TMP_DISABLE ); if ( 0 === $disabled ) { $conf->update_confs( [ Base::DEBUG_TMP_DISABLE => time() + (int) $time ] ); self::debug2( 'LiteSpeed Cache temporary disabled.' ); return; } $conf->update_confs( [ Base::DEBUG_TMP_DISABLE => 0 ] ); self::debug2( 'LiteSpeed Cache reactivated.' ); } /** * Is the temporary disable active? If expired, re-enable. * * @since 7.4 * @access public * * @return bool */ public static function is_tmp_disable() { $disabled_time = self::cls()->conf( Base::DEBUG_TMP_DISABLE ); if ( 0 === $disabled_time ) { return false; } if ( time() < (int) $disabled_time ) { return true; } Conf::cls()->update_confs( [ Base::DEBUG_TMP_DISABLE => 0 ] ); return false; } /** * Ensure log directory exists and move legacy logs into it. * * @since 6.5 * @access private */ private function _maybe_init_folder() { if ( file_exists( self::$log_path_prefix . 'index.php' ) ) { return; } File::save( self::$log_path_prefix . 'index.php', 'path( $log ); if ( file_exists( $old_path ) && ! file_exists( $new_path ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.rename_rename -- Moving legacy log files during migration rename( $old_path, $new_path ); } } } /** * Get absolute path for a log type. * * @since 6.5 * @param string $type Log type (debug|purge|crawler). * @return string */ public function path( $type ) { return self::$log_path_prefix . self::FilePath( $type ); } /** * Get fixed filename for a log type. * * @since 6.5 * @param string $type Log type (debug|debug.purge|crawler). * @return string */ public static function FilePath( $type ) { if ( 'debug.purge' === $type ) { $type = 'purge'; } $key = defined( 'AUTH_KEY' ) ? AUTH_KEY : md5( __FILE__ ); $rand = substr( md5( substr( $key, -16 ) ), -16 ); return $type . $rand . '.log'; } /** * Write end-of-request markers and response timing. * * @since 4.7 * @access public * @return void */ public static function ended() { $headers = headers_list(); foreach ( $headers as $key => $header ) { if ( 0 === stripos( $header, 'Set-Cookie' ) ) { unset( $headers[ $key ] ); } } self::debug( 'Response headers', $headers ); $elapsed_time = number_format( ( microtime( true ) - LSCWP_TS_0 ) * 1000, 2 ); self::debug( "End response\n--------------------------------------------------Duration: " . $elapsed_time . " ms------------------------------\n" ); } /** * Run beta test upgrade. Accepts a direct ZIP URL or attempts to derive one. * * @since 2.9.5 * @access public * * @param string|false $zip ZIP URL or false to read from request. * @return void */ public function beta_test( $zip = false ) { if ( ! $zip ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( empty( $_REQUEST[ self::BETA_TEST_URL ] ) ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended $zip = sanitize_text_field( wp_unslash( $_REQUEST[ self::BETA_TEST_URL ] ) ); if ( self::BETA_TEST_URL_WP !== $zip ) { if ( 'latest' === $zip ) { $zip = self::BETA_TEST_URL_WP; } else { // Generate zip url $zip = $this->_package_zip( $zip ); } } } if ( ! $zip ) { self::debug( '[Debug2] ❌ No ZIP file' ); return; } self::debug( '[Debug2] ZIP file ' . $zip ); $update_plugins = get_site_transient( 'update_plugins' ); if ( ! is_object( $update_plugins ) ) { $update_plugins = new \stdClass(); } $plugin_info = new \stdClass(); $plugin_info->new_version = Core::VER; $plugin_info->slug = Core::PLUGIN_NAME; $plugin_info->plugin = Core::PLUGIN_FILE; $plugin_info->package = $zip; $plugin_info->url = 'https://wordpress.org/plugins/litespeed-cache/'; $update_plugins->response[ Core::PLUGIN_FILE ] = $plugin_info; set_site_transient( 'update_plugins', $update_plugins ); Activation::cls()->upgrade(); } /** * Resolve a GitHub commit-ish into a downloadable ZIP URL via QC API. * * @since 2.9.5 * @access private * * @param string $commit Commit hash/branch/tag. * @return string|false */ private function _package_zip( $commit ) { $data = [ 'commit' => $commit, ]; $res = Cloud::get( Cloud::API_BETA_TEST, $data ); if ( empty( $res['zip'] ) ) { return false; } return $res['zip']; } /** * Write purge headers into a dedicated purge log. * * @since 2.7 * @access public * * @param string $purge_header The Purge header value. * @return void */ public static function log_purge( $purge_header ) { if ( ! defined( 'LSCWP_LOG' ) && ! defined( 'LSCWP_LOG_BYPASS_NOTADMIN' ) ) { return; } $purge_file = self::cls()->path( 'purge' ); self::cls()->_init_request( $purge_file ); $msg = $purge_header . self::_backtrace_info( 6 ); File::append( $purge_file, self::format_message( $msg ) ); } /** * Initialize logging for current request if enabled. * * @since 1.1.0 * @access public * @return void */ public function init() { if ( defined( 'LSCWP_LOG' ) ) { return; } $debug = $this->conf( Base::O_DEBUG ); if ( Base::VAL_ON2 === $debug ) { if ( ! $this->cls( 'Router' )->is_admin_ip() ) { defined( 'LSCWP_LOG_BYPASS_NOTADMIN' ) || define( 'LSCWP_LOG_BYPASS_NOTADMIN', true ); return; } } /** * Check if hit URI includes/excludes * This is after LSCWP_LOG_BYPASS_NOTADMIN to make `log_purge()` still work * * @since 3.0 */ $list = $this->conf( Base::O_DEBUG_INC ); if ( $list ) { $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : ''; $result = Utility::str_hit_array( $request_uri, $list ); if ( ! $result ) { return; } } $list = $this->conf( Base::O_DEBUG_EXC ); if ( $list ) { $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : ''; $result = Utility::str_hit_array( $request_uri, $list ); if ( $result ) { return; } } if ( ! defined( 'LSCWP_LOG' ) ) { $this->_init_request(); define( 'LSCWP_LOG', true ); } } /** * Create the initial log record with request context. * * @since 1.0.12 * @access private * * @param string|null $log_file Optional specific log file path. * @return void */ private function _init_request( $log_file = null ) { if ( ! $log_file ) { $log_file = self::$log_path; } // Rotate if exceeding configured size (MiB). $log_file_size = (int) $this->conf( Base::O_DEBUG_FILESIZE ); if ( file_exists( $log_file ) && filesize( $log_file ) > $log_file_size * 1000000 ) { File::save( $log_file, '' ); } // Add extra spacing if last write was > 2 seconds ago. if ( file_exists( $log_file ) && ( time() - filemtime( $log_file ) ) > 2 ) { File::append( $log_file, "\n\n\n\n" ); } if ( 'cli' === PHP_SAPI ) { return; } $servervars = array( 'Query String' => '', 'HTTP_ACCEPT' => '', 'HTTP_USER_AGENT' => '', 'HTTP_ACCEPT_ENCODING' => '', 'HTTP_COOKIE' => '', 'REQUEST_METHOD' => '', 'SERVER_PROTOCOL' => '', 'X-LSCACHE' => '', 'LSCACHE_VARY_COOKIE' => '', 'LSCACHE_VARY_VALUE' => '', 'ESI_CONTENT_TYPE' => '', ); $server = array_merge($servervars, $_SERVER); $params = array(); if ( isset( $_SERVER['HTTPS'] ) && 'on' === $_SERVER['HTTPS'] ) { $server['SERVER_PROTOCOL'] .= ' (HTTPS) '; } $param = sprintf('💓 ------%s %s %s', $server['REQUEST_METHOD'], $server['SERVER_PROTOCOL'], strtok($server['REQUEST_URI'], '?')); $qs = !empty($server['QUERY_STRING']) ? $server['QUERY_STRING'] : ''; if ( $this->conf( Base::O_DEBUG_COLLAPSE_QS ) ) { $qs = $this->_omit_long_message( $qs ); if ( $qs ) { $param .= ' ? ' . $qs; } $params[] = $param; } else { $params[] = $param; $params[] = 'Query String: ' . $qs; } if ( ! empty( $server['HTTP_REFERER'] ) ) { $params[] = 'HTTP_REFERER: ' . $this->_omit_long_message( $server['HTTP_REFERER'] ); } if ( defined( 'LSCWP_LOG_MORE' ) ) { $params[] = 'User Agent: ' . $this->_omit_long_message( $server['HTTP_USER_AGENT'] ); $params[] = 'Accept: ' . $server['HTTP_ACCEPT']; $params[] = 'Accept Encoding: ' . $server['HTTP_ACCEPT_ENCODING']; } if ( isset( $_COOKIE['_lscache_vary'] ) ) { $params[] = 'Cookie _lscache_vary: ' . sanitize_text_field( wp_unslash( $_COOKIE['_lscache_vary'] ) ); } if ( defined( 'LSCWP_LOG_MORE' ) ) { $params[] = 'X-LSCACHE: ' . ( ! empty( $server['X-LSCACHE'] ) ? 'true' : 'false' ); } if ( $server['LSCACHE_VARY_COOKIE'] ) { $params[] = 'LSCACHE_VARY_COOKIE: ' . $server['LSCACHE_VARY_COOKIE']; } if ( $server['LSCACHE_VARY_VALUE'] ) { $params[] = 'LSCACHE_VARY_VALUE: ' . $server['LSCACHE_VARY_VALUE']; } if ( $server['ESI_CONTENT_TYPE'] ) { $params[] = 'ESI_CONTENT_TYPE: ' . $server['ESI_CONTENT_TYPE']; } $request = array_map( __CLASS__ . '::format_message', $params ); File::append( $log_file, $request ); } /** * Trim long message to keep logs compact. * * @since 6.3 * @param string $msg Message. * @return string */ private function _omit_long_message( $msg ) { if ( strlen( $msg ) > 53 ) { $msg = substr( $msg, 0, 53 ) . '...'; } return $msg; } /** * Format a single log line with timestamp and prefix. * * @since 1.0.12 * @access private * * @param string $msg Message to log. * @return string Formatted line. */ private static function format_message( $msg ) { if ( ! defined( 'LSCWP_LOG_TAG' ) ) { return $msg . "\n"; } if ( ! isset( self::$_prefix ) ) { // address/identity. if ( 'cli' === PHP_SAPI ) { $addr = '=CLI='; if ( isset( $_SERVER['USER'] ) ) { $addr .= sanitize_text_field( wp_unslash( $_SERVER['USER'] ) ); } elseif ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) { $addr .= sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ); } } else { $ip = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : ''; $port = isset( $_SERVER['REMOTE_PORT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_PORT'] ) ) : ''; $addr = "$ip:$port"; } self::$_prefix = sprintf( ' [%s %s %s] ', $addr, LSCWP_LOG_TAG, Str::rrand( 3 ) ); } list( $usec, $sec ) = explode( ' ', microtime() ); // Use gmdate to avoid tz-related warnings; apply offset if defined. $ts = gmdate( 'm/d/y H:i:s', (int) $sec + ( defined( 'LITESPEED_TIME_OFFSET' ) ? (int) LITESPEED_TIME_OFFSET : 0 ) ); return $ts . substr( $usec, 1, 4 ) . self::$_prefix . $msg . "\n"; } /** * Log a debug message. * * @since 1.1.3 * @access public * * @param string $msg Message to write. * @param int|array $backtrace_limit Depth for backtrace or payload to append. * @return void */ public static function debug( $msg, $backtrace_limit = false ) { if ( ! defined( 'LSCWP_LOG' ) ) { return; } if ( defined( 'LSCWP_DEBUG_EXC_STRINGS' ) && Utility::str_hit_array( $msg, LSCWP_DEBUG_EXC_STRINGS ) ) { return; } if ( false !== $backtrace_limit ) { if ( ! is_numeric( $backtrace_limit ) ) { $backtrace_limit = self::trim_longtext( $backtrace_limit ); if ( is_array( $backtrace_limit ) && 1 === count( $backtrace_limit ) && ! empty( $backtrace_limit[0] ) ) { $msg .= ' --- ' . $backtrace_limit[0]; } else { // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export $msg .= ' --- ' . var_export( $backtrace_limit, true ); } self::push( $msg ); return; } self::push( $msg, (int) $backtrace_limit + 1 ); return; } self::push( $msg ); } /** * Trim strings inside arrays/object dumps to reasonable length. * * @since 3.3 * @param mixed $backtrace_limit Data to trim. * @return mixed */ public static function trim_longtext( $backtrace_limit ) { if ( is_array( $backtrace_limit ) ) { $backtrace_limit = array_map( __CLASS__ . '::trim_longtext', $backtrace_limit ); } if ( is_string( $backtrace_limit ) && strlen( $backtrace_limit ) > 500 ) { $backtrace_limit = substr( $backtrace_limit, 0, 1000 ) . '...'; } return $backtrace_limit; } /** * Log a verbose debug message (requires O_DEBUG_LEVEL). * * @since 1.2.0 * @access public * * @param string $msg Message. * @param int|array $backtrace_limit Backtrace depth or payload to append. * @return void */ public static function debug2( $msg, $backtrace_limit = false ) { if ( ! defined( 'LSCWP_LOG_MORE' ) ) { return; } self::debug( $msg, $backtrace_limit ); } /** * Append a message to the active log file. * * @since 1.1.0 * @access private * * @param string $msg Message. * @param int|bool $backtrace_limit Backtrace depth. * @return void */ private static function push( $msg, $backtrace_limit = false ) { if ( defined( 'LSCWP_LOG_MORE' ) && false !== $backtrace_limit ) { $msg .= self::_backtrace_info( (int) $backtrace_limit ); } File::append( self::$log_path, self::format_message( $msg ) ); } /** * Create a compact backtrace string. * * @since 2.7 * @access private * * @param int $backtrace_limit Depth. * @return string */ private static function _backtrace_info( $backtrace_limit ) { $msg = ''; $limit = (int) $backtrace_limit; // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace $trace = debug_backtrace( false, $limit + 3 ); for ( $i = 2; $i <= $limit + 2; $i++ ) { // 0 => _backtrace_info(), 1 => push(). if ( empty( $trace[ $i ]['class'] ) ) { if ( empty( $trace[ $i ]['file'] ) ) { break; } $log = "\n" . $trace[ $i ]['file']; } else { if ( __CLASS__ === $trace[ $i ]['class'] ) { continue; } $args = ''; if ( ! empty( $trace[ $i ]['args'] ) ) { foreach ( $trace[ $i ]['args'] as $v ) { if ( is_array( $v ) ) { $v = 'ARRAY'; } if ( is_string( $v ) || is_numeric( $v ) ) { $args .= $v . ','; } } $args = substr( $args, 0, strlen( $args ) > 100 ? 100 : -1 ); } $log = str_replace( 'Core', 'LSC', $trace[ $i ]['class'] ) . $trace[ $i ]['type'] . $trace[ $i ]['function'] . '(' . $args . ')'; } if ( ! empty( $trace[ $i - 1 ]['line'] ) ) { $log .= '@' . $trace[ $i - 1 ]['line']; } $msg .= " => $log"; } return $msg; } /** * Clear all log files (debug|purge|crawler). * * @since 1.6.6 * @access private * @return void */ private function _clear_log() { $logs = [ 'debug', 'purge', 'crawler' ]; foreach ( $logs as $log ) { File::save( $this->path( $log ), '' ); } } /** * Download a log file. * * @since 7.0 * @access private * @return void */ private function _download_log() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified in Router::verify_type() $log_type = isset( $_GET['log'] ) ? sanitize_text_field( wp_unslash( $_GET['log'] ) ) : ''; $valid_logs = [ 'debug', 'purge', 'crawler' ]; if ( ! in_array( $log_type, $valid_logs, true ) ) { wp_die( esc_html__( 'Invalid log type.', 'litespeed-cache' ) ); } $file = $this->path( $log_type ); if ( ! file_exists( $file ) ) { wp_die( esc_html__( 'Log file not found.', 'litespeed-cache' ) ); } $filename = 'litespeed-' . $log_type . '-' . gmdate( 'Y-m-d_His' ) . '.log'; header( 'Content-Type: text/plain; charset=utf-8' ); header( 'Content-Disposition: attachment; filename="' . $filename . '"' ); header( 'Content-Length: ' . filesize( $file ) ); header( 'Cache-Control: no-cache, no-store, must-revalidate' ); header( 'Pragma: no-cache' ); header( 'Expires: 0' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile -- Direct file output for download readfile( $file ); exit; } /** * Handle requests routed to this class. * * @since 1.6.6 * @access public * @return void */ public function handler() { $type = Router::verify_type(); switch ( $type ) { case self::TYPE_CLEAR_LOG: $this->_clear_log(); break; case self::TYPE_BETA_TEST: $this->beta_test(); break; case self::TYPE_DOWNLOAD_LOG: $this->_download_log(); return; // _download_log() calls exit, but return here for clarity default: break; } Admin::redirect(); } } src/optimize.cls.php000064400000115220152075713340010474 0ustar00#isU"; private $content; private $content_ori; private $cfg_css_min; private $cfg_css_comb; private $cfg_js_min; private $cfg_js_comb; private $cfg_css_async; private $cfg_js_delay_inc = array(); private $cfg_js_defer; private $cfg_js_defer_exc = false; private $cfg_ggfonts_async; private $_conf_css_font_display; private $cfg_ggfonts_rm; private $dns_prefetch; private $dns_preconnect; private $_ggfonts_urls = array(); private $_ccss; private $_ucss = false; private $__optimizer; private $html_foot = ''; // The html info append to private $html_head = ''; // The html info append to private $html_head_early = ''; // The html info prepend to top of head private static $_var_i = 0; private $_var_preserve_js = array(); private $_request_url; /** * Constructor * * @since 4.0 */ public function __construct() { self::debug('init'); $this->__optimizer = $this->cls('Optimizer'); } /** * Init optimizer * * @since 3.0 * @access protected */ public function init() { $this->cfg_css_async = defined('LITESPEED_GUEST_OPTM') || $this->conf(self::O_OPTM_CSS_ASYNC); if ($this->cfg_css_async) { if (!$this->cls('Cloud')->activated()) { self::debug('❌ CCSS set to OFF due to QC not activated'); $this->cfg_css_async = false; } if ((defined('LITESPEED_GUEST_OPTM') || ($this->conf(self::O_OPTM_UCSS) && $this->conf(self::O_OPTM_CSS_COMB))) && $this->conf(self::O_OPTM_UCSS_INLINE)) { self::debug('⚠️ CCSS set to OFF due to UCSS Inline'); $this->cfg_css_async = false; } } $this->cfg_js_defer = $this->conf(self::O_OPTM_JS_DEFER); if (defined('LITESPEED_GUEST_OPTM')) { $this->cfg_js_defer = 2; } if ($this->cfg_js_defer == 2) { add_filter( 'litespeed_optm_cssjs', function ( $con, $file_type ) { if ($file_type == 'js') { $con = str_replace('DOMContentLoaded', 'DOMContentLiteSpeedLoaded', $con); // $con = str_replace( 'addEventListener("load"', 'addEventListener("litespeedLoad"', $con ); } return $con; }, 20, 2 ); } // To remove emoji from WP if ($this->conf(self::O_OPTM_EMOJI_RM)) { $this->_emoji_rm(); } if ($this->conf(self::O_OPTM_QS_RM)) { add_filter('style_loader_src', array( $this, 'remove_query_strings' ), 999); add_filter('script_loader_src', array( $this, 'remove_query_strings' ), 999); } // GM JS exclude @since 4.1 if (defined('LITESPEED_GUEST_OPTM')) { $this->cfg_js_defer_exc = apply_filters('litespeed_optm_gm_js_exc', $this->conf(self::O_OPTM_GM_JS_EXC)); } else { /** * Exclude js from deferred setting * * @since 1.5 */ if ($this->cfg_js_defer) { add_filter('litespeed_optm_js_defer_exc', array( $this->cls('Data'), 'load_js_defer_exc' )); $this->cfg_js_defer_exc = apply_filters('litespeed_optm_js_defer_exc', $this->conf(self::O_OPTM_JS_DEFER_EXC)); $this->cfg_js_delay_inc = apply_filters('litespeed_optm_js_delay_inc', $this->conf(self::O_OPTM_JS_DELAY_INC)); } } // Add vary filter for Role Excludes @since 1.6 add_filter('litespeed_vary', array( $this, 'vary_add_role_exclude' )); // DNS optm (Prefetch/Preconnect) @since 7.3 $this->_dns_optm_init(); add_filter('litespeed_buffer_finalize', array( $this, 'finalize' ), 20); // Inject a dummy CSS file to control final optimized data location in wp_enqueue_style(Core::PLUGIN_NAME . '-dummy', LSWCP_PLUGIN_URL . 'assets/css/litespeed-dummy.css'); } /** * Exclude role from optimization filter * * @since 1.6 * @access public */ public function vary_add_role_exclude( $vary ) { if ($this->cls('Conf')->in_optm_exc_roles()) { $vary['role_exclude_optm'] = 1; } return $vary; } /** * Remove emoji from WP * * @since 1.4 * @since 2.9.8 Changed to private * @access private */ private function _emoji_rm() { remove_action('wp_head', 'print_emoji_detection_script', 7); remove_action('admin_print_scripts', 'print_emoji_detection_script'); remove_filter('the_content_feed', 'wp_staticize_emoji'); remove_filter('comment_text_rss', 'wp_staticize_emoji'); /** * Added for better result * * @since 1.6.2.1 */ remove_action('wp_print_styles', 'print_emoji_styles'); remove_action('admin_print_styles', 'print_emoji_styles'); remove_filter('wp_mail', 'wp_staticize_emoji_for_email'); } /** * Delete file-based cache folder * * @since 2.1 * @access public */ public function rm_cache_folder( $subsite_id = false ) { if ($subsite_id) { file_exists(LITESPEED_STATIC_DIR . '/css/' . $subsite_id) && File::rrmdir(LITESPEED_STATIC_DIR . '/css/' . $subsite_id); file_exists(LITESPEED_STATIC_DIR . '/js/' . $subsite_id) && File::rrmdir(LITESPEED_STATIC_DIR . '/js/' . $subsite_id); return; } file_exists(LITESPEED_STATIC_DIR . '/css') && File::rrmdir(LITESPEED_STATIC_DIR . '/css'); file_exists(LITESPEED_STATIC_DIR . '/js') && File::rrmdir(LITESPEED_STATIC_DIR . '/js'); } /** * Remove QS * * @since 1.3 * @access public */ public function remove_query_strings( $src ) { if (strpos($src, '_litespeed_rm_qs=0') || strpos($src, '/recaptcha')) { return $src; } if (!Utility::is_internal_file($src)) { return $src; } if (strpos($src, '.js?') !== false || strpos($src, '.css?') !== false) { $src = preg_replace('/\?.*/', '', $src); } return $src; } /** * Run optimize process * NOTE: As this is after cache finalized, can NOT set any cache control anymore * * @since 1.2.2 * @access public * @return string The content that is after optimization */ public function finalize( $content ) { $content = $this->_finalize($content); // Fallback to replace dummy css placeholder if (false !== preg_match(self::DUMMY_CSS_REGEX, $content)) { self::debug('Fallback to drop dummy CSS'); $content = preg_replace( self::DUMMY_CSS_REGEX, '', $content ); } return $content; } private function _finalize( $content ) { if (defined('LITESPEED_NO_PAGEOPTM')) { self::debug2('bypass: NO_PAGEOPTM const'); return $content; } if (!defined('LITESPEED_IS_HTML')) { self::debug('bypass: Not frontend HTML type'); return $content; } if (!defined('LITESPEED_GUEST_OPTM')) { if (!Control::is_cacheable()) { self::debug('bypass: Not cacheable'); return $content; } // Check if hit URI excludes add_filter('litespeed_optm_uri_exc', array( $this->cls('Data'), 'load_optm_uri_exc' )); $excludes = apply_filters('litespeed_optm_uri_exc', $this->conf(self::O_OPTM_EXC)); $result = Utility::str_hit_array($_SERVER['REQUEST_URI'], $excludes); if ($result) { self::debug('bypass: hit URI Excludes setting: ' . $result); return $content; } } self::debug('start'); $this->content_ori = $this->content = $content; $this->_optimize(); return $this->content; } /** * Optimize css src * * @since 1.2.2 * @access private */ private function _optimize() { global $wp; // get current request url $permalink_structure = get_option( 'permalink_structure' ); if ( ! empty( $permalink_structure ) ) { $this->_request_url = trailingslashit( home_url( $wp->request ) ); } else { $qs_add = $wp->query_string ? '?' . (string) $wp->query_string : '' ; $this->_request_url = home_url( $wp->request ) . $qs_add; } $this->cfg_css_min = defined('LITESPEED_GUEST_OPTM') || $this->conf(self::O_OPTM_CSS_MIN); $this->cfg_css_comb = defined('LITESPEED_GUEST_OPTM') || $this->conf(self::O_OPTM_CSS_COMB); $this->cfg_js_min = defined('LITESPEED_GUEST_OPTM') || $this->conf(self::O_OPTM_JS_MIN); $this->cfg_js_comb = defined('LITESPEED_GUEST_OPTM') || $this->conf(self::O_OPTM_JS_COMB); $this->cfg_ggfonts_rm = defined('LITESPEED_GUEST_OPTM') || $this->conf(self::O_OPTM_GGFONTS_RM); $this->cfg_ggfonts_async = !defined('LITESPEED_GUEST_OPTM') && $this->conf(self::O_OPTM_GGFONTS_ASYNC); // forced rm already $this->_conf_css_font_display = !defined('LITESPEED_GUEST_OPTM') && $this->conf(self::O_OPTM_CSS_FONT_DISPLAY); if (!$this->cls('Router')->can_optm()) { self::debug('bypass: admin/feed/preview'); return; } if ($this->cfg_css_async) { $this->_ccss = $this->cls('CSS')->prepare_ccss(); if (!$this->_ccss) { self::debug('❌ CCSS set to OFF due to CCSS not generated yet'); $this->cfg_css_async = false; } elseif (strpos($this->_ccss, '' . $this->html_head; } // Check if there is any critical css rules setting if ($this->cfg_css_async && $this->_ccss) { $this->html_head = $this->_ccss . $this->html_head; } // Replace html head part $this->html_head_early = apply_filters('litespeed_optm_html_head_early', $this->html_head_early); if ($this->html_head_early) { // Put header content to be after charset if (false !== strpos($this->content, ''); $this->content = preg_replace('#]*)>#isU', '' . $this->html_head_early, $this->content, 1); } else { self::debug('Put early optm data to be right after '); $this->content = preg_replace('#]*)>#isU', '' . $this->html_head_early, $this->content, 1); } } $this->html_head = apply_filters('litespeed_optm_html_head', $this->html_head); if ($this->html_head) { if (apply_filters('litespeed_optm_html_after_head', false)) { $this->content = str_replace('', $this->html_head . '', $this->content); } else { // Put header content to dummy css position if (false !== preg_match(self::DUMMY_CSS_REGEX, $this->content)) { self::debug('Put optm data to dummy css location'); $this->content = preg_replace( self::DUMMY_CSS_REGEX, $this->html_head, $this->content ); } // Fallback: try to be after charset elseif (strpos($this->content, ''); $this->content = preg_replace('#]*)>#isU', '' . $this->html_head, $this->content, 1); } else { self::debug('Put optm data to be after '); $this->content = preg_replace('#]*)>#isU', '' . $this->html_head, $this->content, 1); } } } // Replace html foot part $this->html_foot = apply_filters('litespeed_optm_html_foot', $this->html_foot); if ($this->html_foot) { $this->content = str_replace('', $this->html_foot . '', $this->content); } // Drop noscript if enabled if ($this->conf(self::O_OPTM_NOSCRIPT_RM)) { // $this->content = preg_replace( '##isU', '', $this->content ); } // Inline font-face optimize $this->content = $this->__optimizer->optm_font_face( $this->content ); // HTML minify if (defined('LITESPEED_GUEST_OPTM') || $this->conf(self::O_OPTM_HTML_MIN)) { $this->content = $this->__optimizer->html_min($this->content); } } /** * Build a full JS tag * * @since 4.0 */ private function _build_js_tag( $src ) { if ($this->cfg_js_defer === 2 || Utility::str_hit_array($src, $this->cfg_js_delay_inc)) { return ''; } if ($this->cfg_js_defer) { return ''; } return ''; } /** * Build a full inline JS snippet * * @since 4.0 */ private function _build_js_inline( $script, $minified = false ) { if ($this->cfg_js_defer) { $deferred = $this->_js_inline_defer($script, false, $minified); if ($deferred) { return $deferred; } } return ''; } /** * Load JS delay lib * * @since 4.0 */ private function _maybe_js_delay() { if ($this->cfg_js_defer !== 2 && !$this->cfg_js_delay_inc) { return; } if (!defined('LITESPEED_JS_DELAY_LIB_LOADED')) { define('LITESPEED_JS_DELAY_LIB_LOADED', true); $this->html_foot .= ''; } } /** * Google font async * * @since 2.7.3 * @access private */ private function _async_ggfonts() { if (!$this->cfg_ggfonts_async || !$this->_ggfonts_urls) { return; } self::debug2('google fonts async found: ', $this->_ggfonts_urls); $this->html_head_early .= ''; /** * Append fonts * * Could be multiple fonts * * * * -> family: PT Sans:400,700|PT Sans Narrow:400|Montserrat:600 * */ $script = 'WebFontConfig={google:{families:['; $families = array(); foreach ($this->_ggfonts_urls as $v) { $qs = wp_specialchars_decode($v); $qs = urldecode($qs); $qs = parse_url($qs, PHP_URL_QUERY); parse_str($qs, $qs); if (empty($qs['family'])) { self::debug('ERR ggfonts failed to find family: ' . $v); continue; } $subset = empty($qs['subset']) ? '' : ':' . $qs['subset']; foreach (array_filter(explode('|', $qs['family'])) as $v2) { $families[] = Str::trim_quotes($v2 . $subset); } } $script .= '"' . implode('","', $families) . ($this->_conf_css_font_display ? '&display=swap' : '') . '"'; $script .= ']}};'; // if webfontloader lib was loaded before WebFontConfig variable, call WebFont.load $script .= 'if ( typeof WebFont === "object" && typeof WebFont.load === "function" ) { WebFont.load( WebFontConfig ); }'; $html = $this->_build_js_inline($script); // https://cdnjs.cloudflare.com/ajax/libs/webfont/1.6.28/webfontloader.js $webfont_lib_url = LSWCP_PLUGIN_URL . self::LIB_FILE_WEBFONTLOADER; // default async, if js defer set use defer $html .= $this->_build_js_tag($webfont_lib_url); // Put this in the very beginning for preconnect $this->html_head = $html . $this->html_head; } /** * Font optm * * @since 3.0 * @access private */ private function _font_optm() { if (!$this->_conf_css_font_display || !$this->_ggfonts_urls) { return; } self::debug2('google fonts optm ', $this->_ggfonts_urls); foreach ($this->_ggfonts_urls as $v) { if (strpos($v, 'display=')) { continue; } $this->html_head = str_replace($v, $v . '&display=swap', $this->html_head); $this->html_foot = str_replace($v, $v . '&display=swap', $this->html_foot); $this->content = str_replace($v, $v . '&display=swap', $this->content); } } /** * Prefetch DNS * * @since 1.7.1 DNS prefetch * @since 5.6.1 DNS preconnect * @access private */ private function _dns_optm_init() { // Widely enable link DNS prefetch if (defined('LITESPEED_GUEST_OPTM') || $this->conf(self::O_OPTM_DNS_PREFETCH_CTRL)) { @header('X-DNS-Prefetch-Control: on'); } $this->dns_prefetch = $this->conf(self::O_OPTM_DNS_PREFETCH); $this->dns_preconnect = $this->conf(self::O_OPTM_DNS_PRECONNECT); if (!$this->dns_prefetch && !$this->dns_preconnect) { return; } if (function_exists('wp_resource_hints')) { add_filter('wp_resource_hints', array( $this, 'dns_optm_filter' ), 10, 2); } else { add_action('litespeed_optm', array( $this, 'dns_optm_output' )); } } /** * DNS optm hook for WP * * @since 1.7.1 * @access public */ public function dns_optm_filter( $urls, $relation_type ) { if ('dns-prefetch' === $relation_type) { foreach ($this->dns_prefetch as $v) { if ($v) { $urls[] = $v; } } } if ('preconnect' === $relation_type) { foreach ($this->dns_preconnect as $v) { if ($v) { $urls[] = $v; } } } return $urls; } /** * DNS optm output directly * * @since 1.7.1 DNS prefetch * @since 5.6.1 DNS preconnect * @access public */ public function dns_optm_output() { foreach ($this->dns_prefetch as $v) { if ($v) { $this->html_head_early .= ''; } } foreach ($this->dns_preconnect as $v) { if ($v) { $this->html_head_early .= ''; } } } /** * Run minify with src queue list * * @since 1.2.2 * @access private */ private function _src_queue_handler( $src_list, $html_list, $file_type = 'css' ) { $html_list_ori = $html_list; $can_webp = $this->cls('Media')->webp_support(); $tag = $file_type == 'css' ? 'link' : 'script'; foreach ($src_list as $key => $src_info) { // Minify inline CSS/JS if (!empty($src_info['inl'])) { if ($file_type == 'css') { $code = Optimizer::minify_css($src_info['src']); $can_webp && ($code = $this->cls('Media')->replace_background_webp($code)); $snippet = str_replace($src_info['src'], $code, $html_list[$key]); } else { // Inline defer JS if ($this->cfg_js_defer) { $attrs = !empty($src_info['attrs']) ? $src_info['attrs'] : ''; $snippet = $this->_js_inline_defer($src_info['src'], $attrs) ?: $html_list[$key]; } else { $code = Optimizer::minify_js($src_info['src']); $snippet = str_replace($src_info['src'], $code, $html_list[$key]); } } } // CSS/JS files else { $url = $this->_build_single_hash_url($src_info['src'], $file_type); if ($url) { $snippet = str_replace($src_info['src'], $url, $html_list[$key]); } // Handle css async load if ($file_type == 'css' && $this->cfg_css_async) { $snippet = $this->_async_css($snippet); } // Handle js defer if ($file_type === 'js' && $this->cfg_js_defer) { $snippet = $this->_js_defer($snippet, $src_info['src']) ?: $snippet; } } $snippet = str_replace("<$tag ", '<' . $tag . ' data-optimized="1" ', $snippet); $html_list[$key] = $snippet; } $this->content = str_replace($html_list_ori, $html_list, $this->content); } /** * Build a single URL mapped filename (This will not save in DB) * * @since 4.0 */ private function _build_single_hash_url( $src, $file_type = 'css' ) { $content = $this->__optimizer->load_file($src, $file_type); $is_min = $this->__optimizer->is_min($src); $content = $this->__optimizer->optm_snippet($content, $file_type, !$is_min, $src); $filepath_prefix = $this->_build_filepath_prefix($file_type); // Save to file $filename = $filepath_prefix . md5($this->remove_query_strings($src)) . '.' . $file_type; $static_file = LITESPEED_STATIC_DIR . $filename; File::save($static_file, $content, true); // QS is required as $src may contains version info $qs_hash = substr(md5($src), -5); return LITESPEED_STATIC_URL . "$filename?ver=$qs_hash"; } /** * Generate full URL path with hash for a list of src * * @since 1.2.2 * @access private */ private function _build_hash_url( $src_list, $file_type = 'css' ) { // $url_sensitive = $this->conf( self::O_OPTM_CSS_UNIQUE ) && $file_type == 'css'; // If need to keep unique CSS per URI // Replace preserved ESI (before generating hash) if ($file_type == 'js') { foreach ($src_list as $k => $v) { if (empty($v['inl'])) { continue; } $src_list[$k]['src'] = $this->_preserve_esi($v['src']); } } $minify = $file_type === 'css' ? $this->cfg_css_min : $this->cfg_js_min; $filename_info = $this->__optimizer->serve($this->_request_url, $file_type, $minify, $src_list); if (!$filename_info) { return false; // Failed to generate } list($filename, $type) = $filename_info; // Add cache tag in case later file deleted to avoid lscache served stale non-existed files @since 4.4.1 Tag::add(Tag::TYPE_MIN . '.' . $filename); $qs_hash = substr(md5(self::get_option(self::ITEM_TIMESTAMP_PURGE_CSS)), -5); // As filename is already related to filecon md5, no need QS anymore $filepath_prefix = $this->_build_filepath_prefix($type); return LITESPEED_STATIC_URL . $filepath_prefix . $filename . '?ver=' . $qs_hash; } /** * Parse js src * * @since 1.2.2 * @access private */ private function _parse_js() { $excludes = apply_filters('litespeed_optimize_js_excludes', $this->conf(self::O_OPTM_JS_EXC)); $combine_ext_inl = $this->conf(self::O_OPTM_JS_COMB_EXT_INL); if (!apply_filters('litespeed_optm_js_comb_ext_inl', true)) { self::debug2('js_comb_ext_inl bypassed via litespeed_optm_js_comb_ext_inl filter'); $combine_ext_inl = false; } $src_list = array(); $html_list = array(); // V7 added: (?:\r\n?|\n?) to fix replacement leaving empty new line $content = preg_replace('#(?:\r\n?|\n?)#sU', '', $this->content); preg_match_all('#]*)>(.*)(?:\r\n?|\n?)#isU', $content, $matches, PREG_SET_ORDER); foreach ($matches as $match) { $attrs = empty($match[1]) ? array() : Utility::parse_attr($match[1]); if (isset($attrs['data-optimized'])) { continue; } if (!empty($attrs['data-no-optimize'])) { continue; } if (!empty($attrs['data-cfasync']) && $attrs['data-cfasync'] === 'false') { continue; } if (!empty($attrs['type']) && $attrs['type'] != 'text/javascript') { continue; } // to avoid multiple replacement if (in_array($match[0], $html_list)) { continue; } $this_src_arr = array(); // JS files if (!empty($attrs['src'])) { // Exclude check $js_excluded = Utility::str_hit_array($attrs['src'], $excludes); $is_internal = Utility::is_internal_file($attrs['src']); $is_file = substr($attrs['src'], 0, 5) != 'data:'; $ext_excluded = !$combine_ext_inl && !$is_internal; if ($js_excluded || $ext_excluded || !$is_file) { // Maybe defer if ($this->cfg_js_defer) { $deferred = $this->_js_defer($match[0], $attrs['src']); if ($deferred) { $this->content = str_replace($match[0], $deferred, $this->content); } } self::debug2('_parse_js bypassed due to ' . ($js_excluded ? 'js files excluded [hit] ' . $js_excluded : 'external js')); continue; } if (strpos($attrs['src'], '/localres/') !== false) { continue; } if (strpos($attrs['src'], 'instant_click') !== false) { continue; } $this_src_arr['src'] = $attrs['src']; } // Inline JS elseif (!empty($match[2])) { // self::debug( '🌹🌹🌹 ' . $match[2] . '🌹' ); // Exclude check $js_excluded = Utility::str_hit_array($match[2], $excludes); if ($js_excluded || !$combine_ext_inl) { // Maybe defer if ($this->cfg_js_defer) { $deferred = $this->_js_inline_defer($match[2], $match[1]); if ($deferred) { $this->content = str_replace($match[0], $deferred, $this->content); } } self::debug2('_parse_js bypassed due to ' . ($js_excluded ? 'js excluded [hit] ' . $js_excluded : 'inline js')); continue; } $this_src_arr['inl'] = true; $this_src_arr['src'] = $match[2]; if ($match[1]) { $this_src_arr['attrs'] = $match[1]; } } else { // Compatibility to those who changed src to data-src already self::debug2('No JS src or inline JS content'); continue; } $src_list[] = $this_src_arr; $html_list[] = $match[0]; } return array( $src_list, $html_list ); } /** * Inline JS defer * * @since 3.0 * @access private */ private function _js_inline_defer( $con, $attrs = false, $minified = false ) { if (strpos($attrs, 'data-no-defer') !== false) { self::debug2('bypass: attr api data-no-defer'); return false; } $hit = Utility::str_hit_array($con, $this->cfg_js_defer_exc); if ($hit) { self::debug2('inline js defer excluded [setting] ' . $hit); return false; } $con = trim($con); // Minify JS first if (!$minified) { // && $this->cfg_js_defer !== 2 $con = Optimizer::minify_js($con); } if (!$con) { return false; } // Check if the content contains ESI nonce or not $con = $this->_preserve_esi($con); if ($this->cfg_js_defer === 2) { // Drop type attribute from $attrs $attrs = Utility::remove_attr( $attrs, 'type' ); // Replace DOMContentLoaded $con = str_replace('DOMContentLoaded', 'DOMContentLiteSpeedLoaded', $con); return '' . $con . ''; // return ''; // return '' . $con . ''; } return ''; } /** * Replace ESI to JS inline var (mainly used to avoid nonce timeout) * * @since 3.5.1 */ private function _preserve_esi( $con ) { $esi_placeholder_list = $this->cls('ESI')->contain_preserve_esi($con); if (!$esi_placeholder_list) { return $con; } foreach ($esi_placeholder_list as $esi_placeholder) { $js_var = '__litespeed_var_' . self::$_var_i++ . '__'; $con = str_replace($esi_placeholder, $js_var, $con); $this->_var_preserve_js[] = $js_var . '=' . $esi_placeholder; } return $con; } /** * Parse css src and remove to-be-removed css * * @since 1.2.2 * @access private * @return array All the src & related raw html list */ private function _parse_css() { $excludes = apply_filters('litespeed_optimize_css_excludes', $this->conf(self::O_OPTM_CSS_EXC)); $ucss_file_exc_inline = apply_filters('litespeed_optimize_ucss_file_exc_inline', $this->conf(self::O_OPTM_UCSS_FILE_EXC_INLINE)); // Append dummy css to exclude list $excludes[] = 'litespeed-dummy.css'; $combine_ext_inl = $this->conf(self::O_OPTM_CSS_COMB_EXT_INL); if (!apply_filters('litespeed_optm_css_comb_ext_inl', true)) { self::debug2('css_comb_ext_inl bypassed via litespeed_optm_css_comb_ext_inl filter'); $combine_ext_inl = false; } $css_to_be_removed = apply_filters('litespeed_optm_css_to_be_removed', array()); $src_list = array(); $html_list = array(); // $dom = new \PHPHtmlParser\Dom; // $dom->load( $content );return $val; // $items = $dom->find( 'link' ); // V7 added: (?:\r\n?|\n?) to fix replacement leaving empty new line $content = preg_replace( array( '#(?:\r\n?|\n?)#sU', '#]*)>.*(?:\r\n?|\n?)#isU', '#]*)>.*(?:\r\n?|\n?)#isU' ), '', $this->content ); preg_match_all('#]+)/?>|]*)>([^<]+)(?:\r\n?|\n?)#isU', $content, $matches, PREG_SET_ORDER); foreach ($matches as $match) { // to avoid multiple replacement if (in_array($match[0], $html_list)) { continue; } if ($exclude = Utility::str_hit_array($match[0], $excludes)) { self::debug2('_parse_css bypassed exclude ' . $exclude); continue; } $this_src_arr = array(); if (strpos($match[0], 'content = str_replace($match[0], '', $this->content); continue; } // Check if need to inline this css file if ($this->conf(self::O_OPTM_UCSS) && Utility::str_hit_array($attrs['href'], $ucss_file_exc_inline)) { self::debug('ucss_file_exc_inline hit ' . $attrs['href']); // Replace this css to inline from orig html $inline_script = ''; $this->content = str_replace($match[0], $inline_script, $this->content); continue; } // Check Google fonts hit if (strpos($attrs['href'], 'fonts.googleapis.com') !== false) { /** * For async gg fonts, will add webfont into head, hence remove it from buffer and store the matches to use later * * @since 2.7.3 * @since 3.0 For font display optm, need to parse google fonts URL too */ if (!in_array($attrs['href'], $this->_ggfonts_urls)) { $this->_ggfonts_urls[] = $attrs['href']; } if ($this->cfg_ggfonts_rm || $this->cfg_ggfonts_async) { self::debug('rm css snippet [Google fonts] ' . $attrs['href']); $this->content = str_replace($match[0], '', $this->content); continue; } } if (isset($attrs['data-optimized'])) { // $this_src_arr[ 'exc' ] = true; continue; } elseif (!empty($attrs['data-no-optimize'])) { // $this_src_arr[ 'exc' ] = true; continue; } $is_internal = Utility::is_internal_file($attrs['href']); $ext_excluded = !$combine_ext_inl && !$is_internal; if ($ext_excluded) { self::debug2('Bypassed due to external link'); // Maybe defer if ($this->cfg_css_async) { $snippet = $this->_async_css($match[0]); if ($snippet != $match[0]) { $this->content = str_replace($match[0], $snippet, $this->content); } } continue; } if (!empty($attrs['media']) && $attrs['media'] !== 'all') { $this_src_arr['media'] = $attrs['media']; } $this_src_arr['src'] = $attrs['href']; } else { // Inline style if (!$combine_ext_inl) { self::debug2('Bypassed due to inline'); continue; } $attrs = Utility::parse_attr($match[2]); if (!empty($attrs['data-no-optimize'])) { continue; } if (!empty($attrs['media']) && $attrs['media'] !== 'all') { $this_src_arr['media'] = $attrs['media']; } $this_src_arr['inl'] = true; $this_src_arr['src'] = $match[3]; } $src_list[] = $this_src_arr; $html_list[] = $match[0]; } return array( $src_list, $html_list ); } /** * Replace css to async loaded css * * @since 1.3 * @access private */ private function _async_css_list( $html_list, $src_list ) { foreach ($html_list as $k => $ori) { if (!empty($src_list[$k]['inl'])) { continue; } $html_list[$k] = $this->_async_css($ori); } return $html_list; } /** * Async CSS snippet * * @since 3.5 */ private function _async_css( $ori ) { if (strpos($ori, 'data-asynced') !== false) { self::debug2('bypass: attr data-asynced exist'); return $ori; } if (strpos($ori, 'data-no-async') !== false) { self::debug2('bypass: attr api data-no-async'); return $ori; } // async replacement $v = str_replace('stylesheet', 'preload', $ori); $v = str_replace('conf(self::O_OPTM_NOSCRIPT_RM)) { $v .= ''; } return $v; } /** * Defer JS snippet * * @since 3.5 */ private function _js_defer( $ori, $src ) { $ori = Utility::remove_attr( $ori, 'async' ); if (strpos($ori, 'defer') !== false) { return false; } if (strpos($ori, 'data-deferred') !== false) { self::debug2('bypass: attr data-deferred exist'); return false; } if (strpos($ori, 'data-no-defer') !== false) { self::debug2('bypass: attr api data-no-defer'); return false; } /** * Exclude JS from setting * * @since 1.5 */ if (Utility::str_hit_array($src, $this->cfg_js_defer_exc)) { self::debug('js defer exclude ' . $src); return false; } if ($this->cfg_js_defer === 2 || Utility::str_hit_array($src, $this->cfg_js_delay_inc)) { $ori = Utility::remove_attr( $ori, 'type' ); return str_replace(' src=', ' type="litespeed/javascript" data-src=', $ori); } return str_replace('>', ' defer data-deferred="1">', $ori); } /** * Delay JS for included setting * * @since 5.6 */ private function _js_delay( $ori, $src ) { $ori = Utility::remove_attr( $ori, 'async' ); if (strpos($ori, 'defer') !== false) { return false; } if (strpos($ori, 'data-deferred') !== false) { self::debug2('bypass: attr data-deferred exist'); return false; } if (strpos($ori, 'data-no-defer') !== false) { self::debug2('bypass: attr api data-no-defer'); return false; } if (!Utility::str_hit_array($src, $this->cfg_js_delay_inc)) { return; } $ori = Utility::remove_attr( $ori, 'type' ); return str_replace(' src=', ' type="litespeed/javascript" data-src=', $ori); } } src/avatar.cls.php000064400000021234152075713340010113 0ustar00 rewritten URL to avoid duplicates. * * @var array */ private $_avatar_realtime_gen_dict = []; /** * Summary/status data for last requests. * * @var array */ protected $_summary; /** * Init. * * @since 1.4 */ public function __construct() { $this->_tb = Data::cls()->tb( 'avatar' ); if ( ! $this->conf( self::O_DISCUSS_AVATAR_CACHE ) ) { return; } self::debug2( '[Avatar] init' ); $this->_conf_cache_ttl = $this->conf( self::O_DISCUSS_AVATAR_CACHE_TTL ); add_filter( 'get_avatar_url', [ $this, 'crawl_avatar' ] ); $this->_summary = self::get_summary(); } /** * Check whether DB table is needed. * * @since 3.0 * @access public * @return bool */ public function need_db() { return (bool) $this->conf( self::O_DISCUSS_AVATAR_CACHE ); } /** * Serve static avatar by md5 (used by local static route). * * @since 3.0 * @access public * @param string $md5 MD5 hash of original avatar URL. * @return void */ public function serve_static( $md5 ) { global $wpdb; self::debug( '[Avatar] is avatar request' ); if ( strlen( $md5 ) !== 32 ) { self::debug( '[Avatar] wrong md5 ' . $md5 ); return; } $url = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->prepare( 'SELECT url FROM `' . $this->_tb . '` WHERE md5 = %s', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $md5 ) ); if ( ! $url ) { self::debug( '[Avatar] no matched url for md5 ' . $md5 ); return; } $url = $this->_generate( $url ); wp_safe_redirect( $url ); exit; } /** * Localize/replace avatar URL with cached one (filter callback). * * @since 3.0 * @access public * @param string $url Original avatar URL. * @return string Rewritten/cached avatar URL (or original). */ public function crawl_avatar( $url ) { if ( ! $url ) { return $url; } // Check if already generated in this request. if ( ! empty( $this->_avatar_realtime_gen_dict[ $url ] ) ) { self::debug2( '[Avatar] already in dict [url] ' . $url ); return $this->_avatar_realtime_gen_dict[ $url ]; } $realpath = $this->_realpath( $url ); $mtime = file_exists( $realpath ) ? filemtime( $realpath ) : false; if ( $mtime && time() - (int) $mtime <= $this->_conf_cache_ttl ) { self::debug2( '[Avatar] cache file exists [url] ' . $url ); return $this->_rewrite( $url, $mtime ); } // Only handle gravatar or known remote avatar providers; keep generic check for "gravatar.com". if ( strpos( $url, 'gravatar.com' ) === false ) { return $url; } // Throttle generation. if ( ! empty( $this->_summary['curr_request'] ) && time() - (int) $this->_summary['curr_request'] < 300 ) { self::debug2( '[Avatar] Bypass generating due to interval limit [url] ' . $url ); return $url; } // Generate immediately and track for this request. $this->_avatar_realtime_gen_dict[ $url ] = $this->_generate( $url ); return $this->_avatar_realtime_gen_dict[ $url ]; } /** * Count queued avatars (expired ones) for cron. * * @since 3.0 * @access public * @return int|false */ public function queue_count() { global $wpdb; if ( ! Data::cls()->tb_exist( 'avatar' ) ) { Data::cls()->tb_create( 'avatar' ); } $cnt = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->prepare( 'SELECT COUNT(*) FROM `' . $this->_tb . '` WHERE dateline < %d', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared time() - $this->_conf_cache_ttl ) ); return (int) $cnt; } /** * Build final local URL for cached avatar. * * @since 3.0 * @param string $url Original URL. * @param int|null $time Optional filemtime for cache busting. * @return string Local URL. */ private function _rewrite( $url, $time = null ) { $qs = $time ? '?ver=' . $time : ''; return LITESPEED_STATIC_URL . '/avatar/' . $this->_filepath( $url ) . $qs; } /** * Generate filesystem realpath for cache file. * * @since 3.0 * @access private * @param string $url Original URL. * @return string Absolute filesystem path. */ private function _realpath( $url ) { return LITESPEED_STATIC_DIR . '/avatar/' . $this->_filepath( $url ); } /** * Get relative filepath for cached avatar. * * @since 4.0 * @param string $url Original URL. * @return string Relative path under avatar/ (may include blog id). */ private function _filepath( $url ) { $filename = md5( $url ) . '.jpg'; if ( is_multisite() ) { $filename = get_current_blog_id() . '/' . $filename; } return $filename; } /** * Cron generation for expired avatars. * * @since 3.0 * @access public * @param bool $force Bypass throttle. * @return void */ public static function cron( $force = false ) { global $wpdb; $_instance = self::cls(); if ( ! $_instance->queue_count() ) { self::debug( '[Avatar] no queue' ); return; } // For cron, need to check request interval too. if ( ! $force ) { if ( ! empty( $_instance->_summary['curr_request'] ) && time() - (int) $_instance->_summary['curr_request'] < 300 ) { self::debug( '[Avatar] curr_request too close' ); return; } } $list = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->prepare( 'SELECT url FROM `' . $_instance->_tb . '` WHERE dateline < %d ORDER BY id DESC LIMIT %d', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared time() - $_instance->_conf_cache_ttl, (int) apply_filters( 'litespeed_avatar_limit', 30 ) ) ); self::debug( '[Avatar] cron job [count] ' . ( $list ? count( $list ) : 0 ) ); if ( $list ) { foreach ( $list as $v ) { self::debug( '[Avatar] cron job [url] ' . $v->url ); $_instance->_generate( $v->url ); } } } /** * Download and store the avatar locally, then update DB row. * * @since 3.0 * @access private * @param string $url Original avatar URL. * @return string Rewritten local URL (fallback to original on failure). */ private function _generate( $url ) { global $wpdb; $file = $this->_realpath( $url ); // Mark request start self::save_summary( [ 'curr_request' => time(), ] ); // Ensure cache directory exists $this->_maybe_mk_cache_folder( 'avatar' ); $response = wp_safe_remote_get( $url, [ 'timeout' => 180, 'stream' => true, 'filename' => $file, ] ); self::debug( '[Avatar] _generate [url] ' . $url ); // Parse response data if ( is_wp_error( $response ) ) { $error_message = $response->get_error_message(); if ( file_exists( $file ) ) { wp_delete_file( $file ); } self::debug( '[Avatar] failed to get: ' . $error_message ); return $url; } // Save summary data self::save_summary( [ 'last_spent' => time() - (int) $this->_summary['curr_request'], 'last_request' => $this->_summary['curr_request'], 'curr_request' => 0, ] ); // Update/insert DB record $md5 = md5( $url ); $existed = $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->prepare( 'UPDATE `' . $this->_tb . '` SET dateline = %d WHERE md5 = %s', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared time(), $md5 ) ); if ( ! $existed ) { $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->prepare( 'INSERT INTO `' . $this->_tb . '` (url, md5, dateline) VALUES (%s, %s, %d)', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $url, $md5, time() ) ); } self::debug( '[Avatar] saved avatar ' . $file ); return $this->_rewrite( $url ); } /** * Handle all request actions from main cls. * * @since 3.0 * @access public * @return void */ public function handler() { $type = Router::verify_type(); switch ( $type ) { case self::TYPE_GENERATE: self::cron( true ); break; default: break; } Admin::redirect(); } } src/optimizer.cls.php000064400000025002152075713340010654 0ustar00_conf_css_font_display = $this->conf(Base::O_OPTM_CSS_FONT_DISPLAY); } /** * Run HTML minify process and return final content * * @since 1.9 * @access public */ public function html_min( $content, $force_inline_minify = false ) { if (!apply_filters('litespeed_html_min', true)) { Debug2::debug2('[Optmer] html_min bypassed via litespeed_html_min filter'); return $content; } $options = array(); if ($force_inline_minify) { $options['jsMinifier'] = __CLASS__ . '::minify_js'; } $skip_comments = $this->conf(Base::O_OPTM_HTML_SKIP_COMMENTS); if ($skip_comments) { $options['skipComments'] = $skip_comments; } /** * Added exception capture when minify * * @since 2.2.3 */ try { $obj = new Lib\HTML_MIN($content, $options); $content_final = $obj->process(); // check if content from minification is empty if ($content_final == '') { Debug2::debug('Failed to minify HTML: HTML minification resulted in empty HTML'); return $content; } if (!defined('LSCACHE_ESI_SILENCE')) { $content_final .= "\n" . ''; } return $content_final; } catch (\Exception $e) { Debug2::debug('******[Optmer] html_min failed: ' . $e->getMessage()); error_log('****** LiteSpeed Optimizer html_min failed: ' . $e->getMessage()); return $content; } } /** * Run minify process and save content * * @since 1.9 * @access public */ public function serve( $request_url, $file_type, $minify, $src_list ) { // Try Unique CSS if ($file_type == 'css') { $content = false; if (defined('LITESPEED_GUEST_OPTM') || $this->conf(Base::O_OPTM_UCSS)) { $filename = $this->cls('UCSS')->load($request_url); if ($filename) { return array( $filename, 'ucss' ); } } } // Before generated, don't know the contented hash filename yet, so used url hash as tmp filename $file_path_prefix = $this->_build_filepath_prefix($file_type); $url_tag = $request_url; $url_tag_for_file = md5($request_url); if (is_404()) { $url_tag_for_file = $url_tag = '404'; } elseif ($file_type == 'css' && apply_filters('litespeed_ucss_per_pagetype', false)) { $url_tag_for_file = $url_tag = Utility::page_type(); } $static_file = LITESPEED_STATIC_DIR . $file_path_prefix . $url_tag_for_file . '.' . $file_type; // Create tmp file to avoid conflict $tmp_static_file = $static_file . '.tmp'; if (file_exists($tmp_static_file) && time() - filemtime($tmp_static_file) <= 600) { // some other request is generating return false; } // File::save( $tmp_static_file, '/* ' . ( is_404() ? '404' : $request_url ) . ' */', true ); // Can't use this bcos this will get filecon md5 changed File::save($tmp_static_file, '', true); // Load content $real_files = array(); foreach ($src_list as $src_info) { $is_min = false; if (!empty($src_info['inl'])) { // Load inline $content = $src_info['src']; } else { // Load file $content = $this->load_file($src_info['src'], $file_type); if (!$content) { continue; } $is_min = $this->is_min($src_info['src']); } $content = $this->optm_snippet($content, $file_type, $minify && !$is_min, $src_info['src'], !empty($src_info['media']) ? $src_info['media'] : false); // Write to file File::save($tmp_static_file, $content, true, true); } // if CSS - run the minification on the saved file. // Will move imports to the top of file and remove extra spaces. if ($file_type == 'css') { $obj = new Lib\CSS_JS_MIN\Minify\CSS(); $file_content_combined = $obj->moveImportsToTop(File::read($tmp_static_file)); File::save($tmp_static_file, $file_content_combined); } // validate md5 $filecon_md5 = md5_file($tmp_static_file); $final_file_path = $file_path_prefix . $filecon_md5 . '.' . $file_type; $realfile = LITESPEED_STATIC_DIR . $final_file_path; if (!file_exists($realfile)) { rename($tmp_static_file, $realfile); Debug2::debug2('[Optmer] Saved static file [path] ' . $realfile); } else { unlink($tmp_static_file); } $vary = $this->cls('Vary')->finalize_full_varies(); Debug2::debug2("[Optmer] Save URL to file for [file_type] $file_type [file] $filecon_md5 [vary] $vary "); $this->cls('Data')->save_url($url_tag, $vary, $file_type, $filecon_md5, dirname($realfile)); return array( $filecon_md5 . '.' . $file_type, $file_type ); } /** * Add font optimization to content provided. * * @param string $content Content to change * @return string Changed content * @since 7.8 */ public function optm_font_face( $content ) { // skip $this->_conf_css_font_display if not true or empty content if ( ! $this->_conf_css_font_display || empty( $content ) ) { return $content; } $optimize_value = apply_filters( 'litespeed_font_optimize_value', 'swap' ); $search_pattern = "/font-display\s*:(?!\s*" . preg_quote($optimize_value, '/') . ")[^;]+;?/ism"; return preg_replace_callback( '/@font-face\s*\{([^\}]*)\}/iS', function( $matches ) use ( $optimize_value, $search_pattern ) { $block_content = $matches[1]; // add font-display if content do not have if ( stripos( $block_content, 'font-display' ) === false ) { $block_content = "font-display:" . $optimize_value . ";" . $block_content; } else if( 0 !== preg_match( $search_pattern, $block_content ) ) { // font-display have other value than swap $block_content = preg_replace( '/font-display\s*:\s*[^;\}]+;?/is', "font-display:$optimize_value;", $block_content ); } return "@font-face{ $block_content }"; }, $content ); } /** * Load a single file * * @since 4.0 */ public function optm_snippet( $content, $file_type, $minify, $src, $media = false ) { // CSS related features if ($file_type == 'css') { // Font optimize $content = $this->optm_font_face( $content ); $content = preg_replace('/@charset[^;]+;\\s*/', '', $content); if ($media) { $content = '@media ' . $media . '{' . $content . "\n}"; } if ($minify) { $content = self::minify_css($content); } $content = $this->cls('CDN')->finalize($content); if ((defined('LITESPEED_GUEST_OPTM') || $this->conf(Base::O_IMG_OPTM_WEBP)) && $this->cls('Media')->webp_support()) { $content = $this->cls('Media')->replace_background_webp($content); } } else { if ($minify) { $content = self::minify_js($content); } else { $content = $this->_null_minifier($content); } $content .= "\n;"; } // Add filter $content = apply_filters('litespeed_optm_cssjs', $content, $file_type, $src); return $content; } /** * Load remote resource from cache if existed * * @since 4.7 */ private function load_cached_file( $url, $file_type ) { $file_path_prefix = $this->_build_filepath_prefix($file_type); $folder_name = LITESPEED_STATIC_DIR . $file_path_prefix; $to_be_deleted_folder = $folder_name . date('Ymd', strtotime('-2 days')); if (file_exists($to_be_deleted_folder)) { Debug2::debug('[Optimizer] ❌ Clearing folder [name] ' . $to_be_deleted_folder); File::rrmdir($to_be_deleted_folder); } $today_file = $folder_name . date('Ymd') . '/' . md5($url); if (file_exists($today_file)) { return File::read($today_file); } // Write file $res = wp_safe_remote_get($url); $res_code = wp_remote_retrieve_response_code($res); if (is_wp_error($res) || $res_code != 200) { Debug2::debug2('[Optimizer] ❌ Load Remote error [code] ' . $res_code); return false; } $con = wp_remote_retrieve_body($res); if (!$con) { return false; } Debug2::debug('[Optimizer] ✅ Save remote file to cache [name] ' . $today_file); File::save($today_file, $con, true); return $con; } /** * Load remote/local resource * * @since 3.5 */ public function load_file( $src, $file_type = 'css' ) { $real_file = Utility::is_internal_file($src); $postfix = pathinfo(parse_url($src, PHP_URL_PATH), PATHINFO_EXTENSION); if (!$real_file || $postfix != $file_type) { Debug2::debug2('[CSS] Load Remote [' . $file_type . '] ' . $src); $this_url = substr($src, 0, 2) == '//' ? set_url_scheme($src) : $src; $con = $this->load_cached_file($this_url, $file_type); if ($file_type == 'css') { $dirname = dirname($this_url) . '/'; $con = Lib\UriRewriter::prepend($con, $dirname); } } else { Debug2::debug2('[CSS] Load local [' . $file_type . '] ' . $real_file[0]); $con = File::read($real_file[0]); if ($file_type == 'css') { $dirname = dirname($real_file[0]); $con = Lib\UriRewriter::rewrite($con, $dirname); } } return $con; } /** * Minify CSS * * @since 2.2.3 * @access private */ public static function minify_css( $data ) { try { $obj = new Lib\CSS_JS_MIN\Minify\CSS(); $obj->add($data); return $obj->minify(); } catch (\Exception $e) { Debug2::debug('******[Optmer] minify_css failed: ' . $e->getMessage()); error_log('****** LiteSpeed Optimizer minify_css failed: ' . $e->getMessage()); return $data; } } /** * Minify JS * * Added exception capture when minify * * @since 2.2.3 * @access private */ public static function minify_js( $data, $js_type = '' ) { // For inline JS optimize, need to check if it's js type if ($js_type) { preg_match('#type=([\'"])(.+)\g{1}#isU', $js_type, $matches); if ($matches && $matches[2] != 'text/javascript') { Debug2::debug('******[Optmer] minify_js bypass due to type: ' . $matches[2]); return $data; } } try { $obj = new Lib\CSS_JS_MIN\Minify\JS(); $obj->add($data); return $obj->minify(); } catch (\Exception $e) { Debug2::debug('******[Optmer] minify_js failed: ' . $e->getMessage()); // error_log( '****** LiteSpeed Optimizer minify_js failed: ' . $e->getMessage() ); return $data; } } /** * Basic minifier * * @access private */ private function _null_minifier( $content ) { $content = str_replace("\r\n", "\n", $content); return trim($content); } /** * Check if the file is already min file * * @since 1.9 */ public function is_min( $filename ) { $basename = basename($filename); if (preg_match('/[-\.]min\.(?:[a-zA-Z]+)$/i', $basename)) { return true; } return false; } } src/cloud-node.trait.php000064400000013712152075713340011232 0ustar00_summary[ 'server.' . $service ] ) ) { unset( $this->_summary[ 'server.' . $service ] ); } if ( isset( $this->_summary[ 'server_date.' . $service ] ) ) { unset( $this->_summary[ 'server_date.' . $service ] ); } } self::save_summary(); self::debug( 'Cleared all local service node caches' ); } /** * Ping clouds to find the fastest node * * @since 3.0 * @access public * * @param string $service Service. * @param bool $force Force redetect. * @return string|false */ public function detect_cloud( $service, $force = false ) { if ( in_array( $service, self::$center_svc_set, true ) ) { return $this->_cloud_server; } if ( in_array( $service, self::$wp_svc_set, true ) ) { return $this->_cloud_server_wp; } // Check if the stored server needs to be refreshed if ( ! $force ) { if ( ! empty( $this->_summary[ 'server.' . $service ] ) && ! empty( $this->_summary[ 'server_date.' . $service ] ) && (int) $this->_summary[ 'server_date.' . $service ] > time() - 86400 * self::TTL_NODE ) { $server = $this->_summary[ 'server.' . $service ]; if ( false === strpos( $this->_cloud_server, 'preview.' ) && false === strpos( $server, 'preview.' ) ) { return $server; } if ( false !== strpos( $this->_cloud_server, 'preview.' ) && false !== strpos( $server, 'preview.' ) ) { return $server; } } } if ( ! $service || ! in_array( $service, self::$services, true ) ) { $msg = __( 'Cloud Error', 'litespeed-cache' ) . ': ' . $service; Admin_Display::error( $msg ); return false; } // Send request to Quic Online Service $json = $this->_post( self::SVC_D_NODES, [ 'svc' => $this->_maybe_queue( $service ) ] ); // Check if get list correctly if ( empty( $json['list'] ) || ! is_array( $json['list'] ) ) { self::debug( 'request cloud list failed: ', $json ); if ( $json ) { $msg = __( 'Cloud Error', 'litespeed-cache' ) . ": [Service] $service [Info] " . wp_json_encode( $json ); Admin_Display::error( $msg ); } return false; } // Ping closest cloud $valid_clouds = false; if ( ! empty( $json['list_preferred'] ) ) { $valid_clouds = $this->_get_closest_nodes( $json['list_preferred'], $service ); } if ( ! $valid_clouds ) { $valid_clouds = $this->_get_closest_nodes( $json['list'], $service ); } if ( ! $valid_clouds ) { return false; } // Check server load if ( in_array( $service, self::$services_load_check, true ) ) { // TODO $valid_cloud_loads = []; foreach ( $valid_clouds as $v ) { $response = wp_safe_remote_get( $v, [ 'timeout' => 5 ] ); if ( is_wp_error( $response ) ) { $error_message = $response->get_error_message(); self::debug( 'failed to do load checker: ' . $error_message ); continue; } $curr_load = \json_decode( $response['body'], true ); if ( ! empty( $curr_load['_res'] ) && 'ok' === $curr_load['_res'] && isset( $curr_load['load'] ) ) { $valid_cloud_loads[ $v ] = $curr_load['load']; } } if ( ! $valid_cloud_loads ) { $msg = __( 'Cloud Error', 'litespeed-cache' ) . ": [Service] $service [Info] " . __( 'No available Cloud Node after checked server load.', 'litespeed-cache' ); Admin_Display::error( $msg ); return false; } self::debug( 'Closest nodes list after load check', $valid_cloud_loads ); $qualified_list = array_keys( $valid_cloud_loads, min( $valid_cloud_loads ), true ); } else { $qualified_list = $valid_clouds; } $closest = $qualified_list[ array_rand( $qualified_list ) ]; self::debug( 'Chose node: ' . $closest ); // store data into option locally $this->_summary[ 'server.' . $service ] = $closest; $this->_summary[ 'server_date.' . $service ] = time(); self::save_summary(); return $this->_summary[ 'server.' . $service ]; } /** * Ping to choose the closest nodes * * @since 7.0 * * @param array $nodes_list Node list. * @param string $service Service. * @return array|false */ private function _get_closest_nodes( $nodes_list, $service ) { $speed_list = []; foreach ( $nodes_list as $v ) { // Exclude possible failed 503 nodes if ( ! empty( $this->_summary['disabled_node'] ) && ! empty( $this->_summary['disabled_node'][ $v ] ) && time() - (int) $this->_summary['disabled_node'][ $v ] < 86400 ) { continue; } $speed_list[ $v ] = Utility::ping( $v ); } if ( ! $speed_list ) { self::debug( 'nodes are in 503 failed nodes' ); return false; } $min = min( $speed_list ); if ( 99999 === (int) $min ) { self::debug( 'failed to ping all clouds' ); return false; } // Random pick same time range ip (230ms 250ms) $range_len = strlen( $min ); $range_num = substr( $min, 0, 1 ); $valid_clouds = []; foreach ( $speed_list as $node => $speed ) { if ( strlen( $speed ) === $range_len && substr( $speed, 0, 1 ) === $range_num ) { $valid_clouds[] = $node; } elseif ( $speed < $min * 4 ) { // Append the lower speed ones $valid_clouds[] = $node; } } if ( ! $valid_clouds ) { $msg = __( 'Cloud Error', 'litespeed-cache' ) . ": [Service] $service [Info] " . __( 'No available Cloud Node.', 'litespeed-cache' ); Admin_Display::error( $msg ); return false; } self::debug( 'Closest nodes list', $valid_clouds ); return $valid_clouds; } /** * May need to convert to queue service * * @param string $service Service. * @return string */ private function _maybe_queue( $service ) { if ( in_array( $service, self::$_queue_svc_set, true ) ) { return self::SVC_QUEUE; } return $service; } } src/tag.cls.php000064400000022411152075713340007406 0ustar00conf(Base::O_CACHE_PAGE_LOGIN)) { return; } if (Control::isset_notcacheable()) { return; } if (!empty($_GET)) { Control::set_nocache('has GET request'); return; } $this->cls('Control')->set_cacheable(); self::add(self::TYPE_LOGIN); // we need to send lsc-cookie manually to make it be sent to all other users when is cacheable $list = headers_list(); if (empty($list)) { return; } foreach ($list as $hdr) { if (strncasecmp($hdr, 'set-cookie:', 11) == 0) { $cookie = substr($hdr, 12); @header('lsc-cookie: ' . $cookie, false); } } } /** * Register purge tag for pages with recent posts widget * of the plugin. * * @since 1.0.15 * @access public * @param array $params [WordPress params for widget_posts_args] */ public function add_widget_recent_posts( $params ) { self::add(self::TYPE_PAGES_WITH_RECENT_POSTS); return $params; } /** * Adds cache tags to the list of cache tags for the current page. * * @since 1.0.5 * @access public * @param mixed $tags A string or array of cache tags to add to the current list. */ public static function add( $tags ) { if (!is_array($tags)) { $tags = array( $tags ); } Debug2::debug('💰 [Tag] Add ', $tags); self::$_tags = array_merge(self::$_tags, $tags); // Send purge header immediately $tag_header = self::cls()->output(true); @header($tag_header); } /** * Add a post id to cache tag * * @since 3.0 * @access public */ public static function add_post( $pid ) { self::add(self::TYPE_POST . $pid); } /** * Add a widget id to cache tag * * @since 3.0 * @access public */ public static function add_widget( $id ) { self::add(self::TYPE_WIDGET . $id); } /** * Add a private ESI to cache tag * * @since 3.0 * @access public */ public static function add_private_esi( $tag ) { self::add_private(self::TYPE_ESI . $tag); } /** * Adds private cache tags to the list of cache tags for the current page. * * @since 1.6.3 * @access public * @param mixed $tags A string or array of cache tags to add to the current list. */ public static function add_private( $tags ) { if (!is_array($tags)) { $tags = array( $tags ); } self::$_tags_priv = array_merge(self::$_tags_priv, $tags); } /** * Return tags for Admin QS * * @since 1.1.3 * @access public */ public static function output_tags() { return self::$_tags; } /** * Will get a hash of the URI. Removes query string and appends a '/' if it is missing. * * @since 1.0.12 * @access public * @param string $uri The uri to get the hash of. * @param boolean $ori Return the original url or not * @return bool|string False on input error, hash otherwise. */ public static function get_uri_tag( $uri, $ori = false ) { $no_qs = strtok($uri, '?'); if (empty($no_qs)) { return false; } $slashed = trailingslashit($no_qs); // If only needs uri tag if ($ori) { return $slashed; } if (defined('LSCWP_LOG')) { return self::TYPE_URL . $slashed; } return self::TYPE_URL . md5($slashed); } /** * Get the unique tag based on self url. * * @since 1.1.3 * @access public * @param boolean $ori Return the original url or not */ public static function build_uri_tag( $ori = false ) { return self::get_uri_tag(urldecode($_SERVER['REQUEST_URI']), $ori); } /** * Gets the cache tags to set for the page. * * This includes site wide post types (e.g. front page) as well as * any third party plugin specific cache tags. * * @since 1.0.0 * @access private * @return array The list of cache tags to set. */ private static function _build_type_tags() { $tags = array(); $tags[] = Utility::page_type(); $tags[] = self::build_uri_tag(); if (is_front_page()) { $tags[] = self::TYPE_FRONTPAGE; } elseif (is_home()) { $tags[] = self::TYPE_HOME; } global $wp_query; if (isset($wp_query)) { $queried_obj_id = get_queried_object_id(); if (is_archive()) { // An Archive is a Category, Tag, Author, Date, Custom Post Type or Custom Taxonomy based pages. if (is_category() || is_tag() || is_tax()) { $tags[] = self::TYPE_ARCHIVE_TERM . $queried_obj_id; } elseif (is_post_type_archive() && ($post_type = get_post_type())) { $tags[] = self::TYPE_ARCHIVE_POSTTYPE . $post_type; } elseif (is_author()) { $tags[] = self::TYPE_AUTHOR . $queried_obj_id; } elseif (is_date()) { global $post; if ($post && isset($post->post_date)) { $date = $post->post_date; $date = strtotime($date); if (is_day()) { $tags[] = self::TYPE_ARCHIVE_DATE . date('Ymd', $date); } elseif (is_month()) { $tags[] = self::TYPE_ARCHIVE_DATE . date('Ym', $date); } elseif (is_year()) { $tags[] = self::TYPE_ARCHIVE_DATE . date('Y', $date); } } } } elseif (is_singular()) { // $this->is_singular = $this->is_single || $this->is_page || $this->is_attachment; $tags[] = self::TYPE_POST . $queried_obj_id; if (is_page()) { $tags[] = self::TYPE_PAGES; } } elseif (is_feed()) { $tags[] = self::TYPE_FEED; } } // Check REST API if (REST::cls()->is_rest()) { $tags[] = self::TYPE_REST; $path = !empty($_SERVER['SCRIPT_URL']) ? $_SERVER['SCRIPT_URL'] : false; if ($path) { // posts collections tag if (substr($path, -6) == '/posts') { $tags[] = self::TYPE_LIST; // Not used for purge yet } // single post tag global $post; if (!empty($post->ID) && substr($path, -strlen($post->ID) - 1) === '/' . $post->ID) { $tags[] = self::TYPE_POST . $post->ID; } // pages collections & single page tag if (stripos($path, '/pages') !== false) { $tags[] = self::TYPE_PAGES; } } } // Append AJAX action tag if (Router::is_ajax() && !empty($_REQUEST['action'])) { $tags[] = self::TYPE_AJAX . $_REQUEST['action']; } return $tags; } /** * Generate all cache tags before output * * @access private * @since 1.1.3 */ private static function _finalize() { // run 3rdparty hooks to tag do_action('litespeed_tag_finalize'); // generate wp tags if (!defined('LSCACHE_IS_ESI')) { $type_tags = self::_build_type_tags(); self::$_tags = array_merge(self::$_tags, $type_tags); } if (defined('LITESPEED_GUEST') && LITESPEED_GUEST) { self::$_tags[] = 'guest'; } // append blog main tag self::$_tags[] = ''; // removed duplicates self::$_tags = array_unique(self::$_tags); } /** * Sets up the Cache Tags header. * ONLY need to run this if is cacheable * * @since 1.1.3 * @access public * @return string empty string if empty, otherwise the cache tags header. */ public function output( $no_finalize = false ) { if (defined('LSCACHE_NO_CACHE') && LSCACHE_NO_CACHE) { return; } if (!$no_finalize) { self::_finalize(); } $prefix_tags = array(); /** * Only append blog_id when is multisite * * @since 2.9.3 */ $prefix = LSWCP_TAG_PREFIX . (is_multisite() ? get_current_blog_id() : '') . '_'; // If is_private and has private tags, append them first, then specify prefix to `public` for public tags if (Control::is_private()) { foreach (self::$_tags_priv as $priv_tag) { $prefix_tags[] = $prefix . $priv_tag; } $prefix = 'public:' . $prefix; } foreach (self::$_tags as $tag) { $prefix_tags[] = $prefix . $tag; } $hdr = self::X_HEADER . ': ' . implode(',', $prefix_tags); return $hdr; } } src/img-optm-send.trait.php000064400000053626152075713340011671 0ustar00_existed_src_list ) { // To aavoid extra query when recalling this function self::debug( 'SELECT src from img_optm table' ); if ( $this->__data->tb_exist( 'img_optm' ) ) { $q = "SELECT src FROM `$this->_table_img_optm` WHERE post_id = %d"; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $list = $wpdb->get_results( $wpdb->prepare( $q, $post_id ) ); foreach ( $list as $v ) { $this->_existed_src_list[] = $post_id . '.' . $v->src; } } if ( $this->__data->tb_exist( 'img_optming' ) ) { $q = "SELECT src FROM `$this->_table_img_optming` WHERE post_id = %d"; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $list = $wpdb->get_results( $wpdb->prepare( $q, $post_id ) ); foreach ( $list as $v ) { $this->_existed_src_list[] = $post_id . '.' . $v->src; } } else { $this->__data->tb_create( 'img_optming' ); } } // Prepare images $this->tmp_pid = $post_id; $this->tmp_path = pathinfo( $meta_value['file'], PATHINFO_DIRNAME ) . '/'; $this->_append_img_queue( $meta_value, true ); if ( ! empty( $meta_value['sizes'] ) ) { foreach ( $meta_value['sizes'] as $img_size_name => $img_size ) { $this->_append_img_queue( $img_size, false, $img_size_name ); } } if ( ! $this->_img_in_queue ) { self::debug( 'auto update attachment meta 2 bypass: empty _img_in_queue' ); return; } // Save to DB $this->_save_raw(); // $this->_send_request(); } /** * Auto send optm request * * @since 2.4.1 * @access public */ public static function cron_auto_request() { if ( ! wp_doing_cron() ) { return false; } $instance = self::cls(); $instance->new_req(); } /** * Calculate wet run allowance * * @since 3.0 * @return int|false The wet limit or false if no limit. */ public function wet_limit() { $wet_limit = 1; if ( ! empty( $this->_summary['img_taken'] ) ) { $wet_limit = pow( $this->_summary['img_taken'], 2 ); } if ( 1 === $wet_limit && ! empty( $this->_summary[ 'img_status.' . self::STATUS_ERR_OPTM ] ) ) { $wet_limit = pow( $this->_summary[ 'img_status.' . self::STATUS_ERR_OPTM ], 2 ); } if ( $wet_limit < Cloud::IMG_OPTM_DEFAULT_GROUP ) { return $wet_limit; } // No limit return false; } /** * Push raw img to image optm server * * @since 1.6 * @access public */ public function new_req() { global $wpdb; // check if is running if ( ! empty( $this->_summary['is_running'] ) && time() - $this->_summary['is_running'] < apply_filters( 'litespeed_imgoptm_new_req_interval', 3600 ) ) { self::debug( 'The previous req was in 3600s.' ); return; } $this->_summary['is_running'] = time(); self::save_summary(); // Check if has credit to push $err = false; $allowance = Cloud::cls()->allowance( Cloud::SVC_IMG_OPTM, $err ); $wet_limit = $this->wet_limit(); self::debug( "allowance_max $allowance wet_limit $wet_limit" ); if ( $wet_limit && $wet_limit < $allowance ) { $allowance = $wet_limit; } if ( ! $allowance ) { self::debug( '❌ No credit' ); Admin_Display::error( Error::msg( $err ) ); $this->_finished_running(); return; } self::debug( 'preparing images to push' ); $this->__data->tb_create( 'img_optming' ); $q = "SELECT COUNT(1) FROM `$this->_table_img_optming` WHERE optm_status = %d"; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $q = $wpdb->prepare( $q, [ self::STATUS_REQUESTED ] ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $total_requested = $wpdb->get_var( $q ); $max_requested = $allowance * 1; if ( $total_requested > $max_requested ) { self::debug( '❌ Too many queued images (' . $total_requested . ' > ' . $max_requested . ')' ); Admin_Display::error( Error::msg( 'too_many_requested' ) ); $this->_finished_running(); return; } $allowance -= $total_requested; if ( $allowance < 1 ) { self::debug( '❌ Too many requested images ' . $total_requested ); Admin_Display::error( Error::msg( 'too_many_requested' ) ); $this->_finished_running(); return; } // Limit maximum number of items waiting to be pulled $q = "SELECT COUNT(1) FROM `$this->_table_img_optming` WHERE optm_status = %d"; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $q = $wpdb->prepare( $q, [ self::STATUS_NOTIFIED ] ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $total_notified = $wpdb->get_var( $q ); if ( $total_notified > 0 ) { self::debug( '❌ Too many notified images (' . $total_notified . ')' ); Admin_Display::error( Error::msg( 'too_many_notified' ) ); $this->_finished_running(); return; } $q = "SELECT COUNT(1) FROM `$this->_table_img_optming` WHERE optm_status IN (%d, %d)"; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $q = $wpdb->prepare( $q, [ self::STATUS_NEW, self::STATUS_RAW ] ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $total_new = $wpdb->get_var( $q ); // $allowance -= $total_new; // May need to get more images $list = []; $more = $allowance - $total_new; if ( $more > 0 ) { $q = "SELECT b.post_id, b.meta_value FROM `$wpdb->posts` a LEFT JOIN `$wpdb->postmeta` b ON b.post_id = a.ID WHERE b.meta_key = '_wp_attachment_metadata' AND a.post_type = 'attachment' AND a.post_status = 'inherit' AND a.ID>%d AND a.post_mime_type IN ('image/jpeg', 'image/png', 'image/gif') ORDER BY a.ID LIMIT %d "; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $q = $wpdb->prepare( $q, [ $this->_summary['next_post_id'], $more ] ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $list = $wpdb->get_results( $q ); foreach ( $list as $v ) { if ( ! $v->post_id ) { continue; } $this->_summary['next_post_id'] = $v->post_id; $meta_value = $this->_parse_wp_meta_value( $v ); if ( ! $meta_value ) { continue; } $meta_value['file'] = wp_normalize_path( $meta_value['file'] ); $basedir = $this->wp_upload_dir['basedir'] . '/'; if ( strpos( $meta_value['file'], $basedir ) === 0 ) { $meta_value['file'] = substr( $meta_value['file'], strlen( $basedir ) ); } $this->tmp_pid = $v->post_id; $this->tmp_path = pathinfo( $meta_value['file'], PATHINFO_DIRNAME ) . '/'; $this->_append_img_queue( $meta_value, true ); if ( ! empty( $meta_value['sizes'] ) ) { foreach ( $meta_value['sizes'] as $img_size_name => $img_size ) { $this->_append_img_queue( $img_size, false, $img_size_name ); } } } self::save_summary(); $num_a = count( $this->_img_in_queue ); self::debug( 'Images found: ' . $num_a ); $this->_filter_duplicated_src(); self::debug( 'Images after duplicated: ' . count( $this->_img_in_queue ) ); $this->_filter_invalid_src(); self::debug( 'Images after invalid: ' . count( $this->_img_in_queue ) ); // Check w/ legacy imgoptm table, bypass finished images $this->_filter_legacy_src(); $num_b = count( $this->_img_in_queue ); if ( $num_b !== $num_a ) { self::debug( 'Images after filtered duplicated/invalid/legacy src: ' . $num_b ); } // Save to DB $this->_save_raw(); } // Push to Cloud server $accepted_imgs = $this->_send_request( $allowance ); $this->_finished_running(); if ( ! $accepted_imgs ) { return; } $placeholder1 = Admin_Display::print_plural( $accepted_imgs[0], 'image' ); $placeholder2 = Admin_Display::print_plural( $accepted_imgs[1], 'image' ); $msg = sprintf( __( 'Pushed %1$s to Cloud server, accepted %2$s.', 'litespeed-cache' ), $placeholder1, $placeholder2 ); Admin_Display::success( $msg ); } /** * Set running to done * * @since 3.0 * @access private */ private function _finished_running() { $this->_summary['is_running'] = 0; self::save_summary(); } /** * Add a new img to queue which will be pushed to request * * @since 1.6 * @since 7.5 Allow to choose which image sizes should be optimized + added parameter $img_size_name. * @access private * @param array $meta_value The meta value array. * @param bool $is_ori_file Whether this is the original file. * @param string|bool $img_size_name The image size name or false. */ private function _append_img_queue( $meta_value, $is_ori_file = false, $img_size_name = false ) { if ( empty( $meta_value['file'] ) || empty( $meta_value['width'] ) || empty( $meta_value['height'] ) ) { self::debug2( 'bypass image due to lack of file/w/h: pid ' . $this->tmp_pid, $meta_value ); return; } $short_file_path = $meta_value['file']; // Test if need to skip image size. if ( ! $is_ori_file ) { $short_file_path = $this->tmp_path . $short_file_path; $skip = false !== array_search( $img_size_name, $this->_sizes_skipped, true ); if ( $skip ) { self::debug2( 'bypass image ' . $short_file_path . ' due to skipped size: ' . $img_size_name ); return; } } // Check if src is gathered already or not if ( in_array( $this->tmp_pid . '.' . $short_file_path, $this->_existed_src_list, true ) ) { // Debug2::debug2( '[Img_Optm] bypass image due to gathered: pid ' . $this->tmp_pid . ' ' . $short_file_path ); return; } else { // Append handled images $this->_existed_src_list[] = $this->tmp_pid . '.' . $short_file_path; } // check file exists or not $_img_info = $this->__media->info( $short_file_path, $this->tmp_pid ); $extension = pathinfo( $short_file_path, PATHINFO_EXTENSION ); if ( ! $_img_info || ! in_array( $extension, [ 'jpg', 'jpeg', 'png', 'gif' ], true ) ) { self::debug2( 'bypass image due to file not exist: pid ' . $this->tmp_pid . ' ' . $short_file_path ); return; } // Check if optimized file exists or not $target_needed = false; if ( $this->_format ) { $target_file_path = $short_file_path . '.' . $this->_format; if ( ! $this->__media->info( $target_file_path, $this->tmp_pid ) ) { $target_needed = true; } } if ( $this->conf( self::O_IMG_OPTM_ORI ) ) { $target_file_path = substr( $short_file_path, 0, -strlen( $extension ) ) . 'bk.' . $extension; if ( ! $this->__media->info( $target_file_path, $this->tmp_pid ) ) { $target_needed = true; } } if ( ! $target_needed ) { self::debug2( 'bypass image due to optimized file exists: pid ' . $this->tmp_pid . ' ' . $short_file_path ); return; } // Debug2::debug2( '[Img_Optm] adding image: pid ' . $this->tmp_pid ); $this->_img_in_queue[] = [ 'pid' => $this->tmp_pid, 'md5' => $_img_info['md5'], 'url' => $_img_info['url'], 'src' => $short_file_path, // not needed in LiteSpeed IAPI, just leave for local storage after post 'mime_type' => ! empty( $meta_value['mime-type'] ) ? $meta_value['mime-type'] : '', ]; } /** * Save gathered image raw data * * @since 3.0 * @access private */ private function _save_raw() { if ( empty( $this->_img_in_queue ) ) { return; } $data = []; $pid_list = []; foreach ( $this->_img_in_queue as $k => $v ) { $_img_info = $this->__media->info( $v['src'], $v['pid'] ); // attachment doesn't exist, delete the record if ( empty( $_img_info['url'] ) || empty( $_img_info['md5'] ) ) { unset( $this->_img_in_queue[ $k ] ); continue; } $pid_list[] = (int) $v['pid']; $data[] = $v['pid']; $data[] = self::STATUS_RAW; $data[] = $v['src']; } global $wpdb; $fields = 'post_id, optm_status, src'; $q = "INSERT INTO `$this->_table_img_optming` ( $fields ) VALUES "; // Add placeholder $q .= Utility::chunk_placeholder( $data, $fields ); // Store data // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $wpdb->query( $wpdb->prepare( $q, $data ) ); $count = count( $this->_img_in_queue ); self::debug( 'Added raw images [total] ' . $count ); $this->_img_in_queue = []; // Save thumbnail groups for future rescan index $this->_gen_thumbnail_set(); $pid_list = array_unique( $pid_list ); self::debug( 'pid list to append to postmeta', $pid_list ); $pid_list = array_diff( $pid_list, $this->_pids_set ); $this->_pids_set = array_merge( $this->_pids_set, $pid_list ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $existed_meta = $wpdb->get_results( "SELECT * FROM `$wpdb->postmeta` WHERE post_id IN ('" . implode( "','", $pid_list ) . "') AND meta_key='" . self::DB_SET . "'" ); $existed_pid = []; if ( $existed_meta ) { foreach ( $existed_meta as $v ) { $existed_pid[] = $v->post_id; } self::debug( 'pid list to update postmeta', $existed_pid ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->query( // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $existed_pid is array of sanitized IDs $wpdb->prepare( "UPDATE `$wpdb->postmeta` SET meta_value=%s WHERE post_id IN (" . implode( ',', $existed_pid ) . ') AND meta_key=%s', [ $this->_thumbnail_set, self::DB_SET, ] ) ); } // Add new meta $new_pids = $existed_pid ? array_diff( $pid_list, $existed_pid ) : $pid_list; if ( $new_pids ) { self::debug( 'pid list to update postmeta', $new_pids ); foreach ( $new_pids as $v ) { self::debug( 'New group set info [pid] ' . $v ); $q = "INSERT INTO `$wpdb->postmeta` (post_id, meta_key, meta_value) VALUES (%d, %s, %s)"; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $wpdb->query( $wpdb->prepare( $q, [ $v, self::DB_SET, $this->_thumbnail_set ] ) ); } } } /** * Generate thumbnail sets of current image group * * @since 5.4 * @access private */ private function _gen_thumbnail_set() { if ( $this->_thumbnail_set ) { return; } $set = []; foreach ( Media::cls()->get_image_sizes() as $size ) { $curr_size = $size['width'] . 'x' . $size['height']; if ( in_array( $curr_size, $set, true ) ) { continue; } $set[] = $curr_size; } $this->_thumbnail_set = implode( PHP_EOL, $set ); } /** * Filter duplicated src in work table and $this->_img_in_queue, then mark them as duplicated * * @since 2.0 * @access private */ private function _filter_duplicated_src() { global $wpdb; $srcpath_list = []; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $list = $wpdb->get_results( "SELECT src FROM `$this->_table_img_optming`" ); foreach ( $list as $v ) { $srcpath_list[] = $v->src; } foreach ( $this->_img_in_queue as $k => $v ) { if ( in_array( $v['src'], $srcpath_list, true ) ) { unset( $this->_img_in_queue[ $k ] ); continue; } $srcpath_list[] = $v['src']; } } /** * Filter legacy finished ones * * @since 5.4 * @access private */ private function _filter_legacy_src() { global $wpdb; if ( ! $this->__data->tb_exist( 'img_optm' ) ) { return; } if ( ! $this->_img_in_queue ) { return; } $finished_ids = []; Utility::compatibility(); $post_ids = array_unique( array_column( $this->_img_in_queue, 'pid' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $list = $wpdb->get_results( "SELECT post_id FROM `$this->_table_img_optm` WHERE post_id in (" . implode( ',', $post_ids ) . ') GROUP BY post_id' ); foreach ( $list as $v ) { $finished_ids[] = $v->post_id; } foreach ( $this->_img_in_queue as $k => $v ) { if ( in_array( $v['pid'], $finished_ids, true ) ) { self::debug( 'Legacy image optimized [pid] ' . $v['pid'] ); unset( $this->_img_in_queue[ $k ] ); continue; } } // Drop all existing legacy records // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $wpdb->query( "DELETE FROM `$this->_table_img_optm` WHERE post_id in (" . implode( ',', $post_ids ) . ')' ); } /** * Filter the invalid src before sending * * @since 3.0.8.3 * @access private */ private function _filter_invalid_src() { $img_in_queue_invalid = []; foreach ( $this->_img_in_queue as $k => $v ) { if ( $v['src'] ) { $extension = pathinfo( $v['src'], PATHINFO_EXTENSION ); } if ( ! $v['src'] || empty( $extension ) || ! in_array( $extension, [ 'jpg', 'jpeg', 'png', 'gif' ], true ) ) { $img_in_queue_invalid[] = $v['id']; unset( $this->_img_in_queue[ $k ] ); continue; } } if ( ! $img_in_queue_invalid ) { return; } $count = count( $img_in_queue_invalid ); $msg = sprintf( __( 'Cleared %1$s invalid images.', 'litespeed-cache' ), $count ); Admin_Display::success( $msg ); self::debug( 'Found invalid src [total] ' . $count ); } /** * Push img request to Cloud server * * @since 1.6.7 * @access private * @param int $allowance The allowance limit. * @return array|void Array with pushed and accepted counts. */ private function _send_request( $allowance ) { global $wpdb; $q = "SELECT id, src, post_id FROM `$this->_table_img_optming` WHERE optm_status=%d LIMIT %d"; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $q = $wpdb->prepare( $q, [ self::STATUS_RAW, $allowance ] ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $_img_in_queue = $wpdb->get_results( $q ); if ( ! $_img_in_queue ) { return; } self::debug( 'Load img in queue [total] ' . count( $_img_in_queue ) ); $list = []; foreach ( $_img_in_queue as $v ) { $_img_info = $this->__media->info( $v->src, $v->post_id ); // If record is invalid, remove from img_optming table if ( empty( $_img_info['url'] ) || empty( $_img_info['md5'] ) ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $wpdb->query( $wpdb->prepare( "DELETE FROM `$this->_table_img_optming` WHERE id=%d", $v->id ) ); continue; } $img = [ 'id' => $v->id, 'url' => $_img_info['url'], 'md5' => $_img_info['md5'], ]; // Build the needed image types for request as we now support soft reset counter if ( $this->_format ) { $target_file_path = $v->src . '.' . $this->_format; if ( $this->__media->info( $target_file_path, $v->post_id ) ) { $img[ 'optm_' . $this->_format ] = 0; } } if ( $this->conf( self::O_IMG_OPTM_ORI ) ) { $extension = pathinfo( $v->src, PATHINFO_EXTENSION ); $target_file_path = substr( $v->src, 0, -strlen( $extension ) ) . 'bk.' . $extension; if ( $this->__media->info( $target_file_path, $v->post_id ) ) { $img['optm_ori'] = 0; } } $list[] = $img; } if ( ! $list ) { $msg = __( 'No valid image found in the current request.', 'litespeed-cache' ); Admin_Display::error( $msg ); return; } $data = [ 'action' => self::CLOUD_ACTION_NEW_REQ, 'list' => wp_json_encode( $list ), 'optm_ori' => $this->conf( self::O_IMG_OPTM_ORI ) ? 1 : 0, 'optm_lossless' => $this->conf( self::O_IMG_OPTM_LOSSLESS ) ? 1 : 0, 'keep_exif' => $this->conf( self::O_IMG_OPTM_EXIF ) ? 1 : 0, ]; if ( $this->_format ) { $data[ 'optm_' . $this->_format ] = 1; } // Push to Cloud server $json = Cloud::post( Cloud::SVC_IMG_OPTM, $data ); if ( ! $json ) { return; } // Check data format if ( empty( $json['ids'] ) ) { self::debug( 'Failed to parse response data from Cloud server ', $json ); $msg = __( 'No valid image found by Cloud server in the current request.', 'litespeed-cache' ); Admin_Display::error( $msg ); return; } self::debug( 'Returned data from Cloud server count: ' . count( $json['ids'] ) ); $ids = implode( ',', array_map( 'intval', $json['ids'] ) ); // Update img table $q = "UPDATE `$this->_table_img_optming` SET optm_status = '" . self::STATUS_REQUESTED . "' WHERE id IN ( $ids )"; // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared $wpdb->query( $q ); $this->_summary['last_requested'] = time(); self::save_summary(); return [ count( $list ), count( $json['ids'] ) ]; } /** * Parse wp's meta value * * @since 1.6.7 * @access private * @param object $v The database row object. * @return array|false The parsed meta value or false on failure. */ private function _parse_wp_meta_value( $v ) { if ( empty( $v ) ) { self::debug( 'bypassed parsing meta due to null value' ); return false; } if ( ! $v->meta_value ) { self::debug( 'bypassed parsing meta due to no meta_value: pid ' . $v->post_id ); return false; } // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Suppress warnings from corrupted metadata $meta_value = @maybe_unserialize( $v->meta_value ); if ( ! is_array( $meta_value ) ) { self::debug( 'bypassed parsing meta due to meta_value not json: pid ' . $v->post_id ); return false; } if ( empty( $meta_value['file'] ) ) { self::debug( 'bypassed parsing meta due to no ori file: pid ' . $v->post_id ); return false; } return $meta_value; } } src/img-optm.cls.php000064400000012407152075713340010370 0ustar00wp_upload_dir = wp_upload_dir(); $this->__media = $this->cls( 'Media' ); $this->__data = $this->cls( 'Data' ); $this->_table_img_optm = $this->__data->tb( 'img_optm' ); $this->_table_img_optming = $this->__data->tb( 'img_optming' ); $this->_summary = self::get_summary(); if ( empty( $this->_summary['next_post_id'] ) ) { $this->_summary['next_post_id'] = 0; } if ( $this->conf( Base::O_IMG_OPTM_WEBP ) ) { $this->_format = 'webp'; if ( $this->conf( Base::O_IMG_OPTM_WEBP ) === 2 ) { $this->_format = 'avif'; } } // Allow users to ignore custom sizes. $this->_sizes_skipped = apply_filters( 'litespeed_imgoptm_sizes_skipped', $this->conf( Base::O_IMG_OPTM_SIZES_SKIPPED ) ); } /** * Handle all request actions from main cls * * @since 2.0 * @access public */ public function handler() { $type = Router::verify_type(); switch ( $type ) { case self::TYPE_RESET_ROW: // phpcs:ignore WordPress.Security.NonceVerification.Recommended $id = ! empty( $_GET['id'] ) ? absint( wp_unslash( $_GET['id'] ) ) : false; $this->reset_row( $id ); break; case self::TYPE_CALC_BKUP: $this->_calc_bkup(); break; case self::TYPE_RM_BKUP: $this->rm_bkup(); break; case self::TYPE_NEW_REQ: $this->new_req(); break; case self::TYPE_RESCAN: $this->_rescan(); break; case self::TYPE_RESET_COUNTER: $this->_reset_counter(); break; case self::TYPE_DESTROY: $this->_destroy(); break; case self::TYPE_CLEAN: $this->clean(); break; case self::TYPE_PULL: self::start_async(); break; case self::TYPE_BATCH_SWITCH_ORI: case self::TYPE_BATCH_SWITCH_OPTM: $this->batch_switch( $type ); break; case substr( $type, 0, 4 ) === 'avif': case substr( $type, 0, 4 ) === 'webp': case substr( $type, 0, 4 ) === 'orig': $this->_switch_optm_file( $type ); break; default: break; } Admin::redirect(); } } src/str.cls.php000064400000006124152075713340007446 0ustar00xxxx` to `xxxx`. * * @since 7.0 * @access public * @param string $html The HTML string to process. * @return string The processed HTML string. */ public static function translate_qc_apis( $html ) { preg_match_all( '/ $html_to_be_replaced ) { $link = ' [], 'class' => [], 'target' => [], 'src' => [], 'color' => [], 'href' => [], ]; $tags = [ 'hr', 'h3', 'h4', 'h5', 'ul', 'li', 'br', 'strong', 'p', 'span', 'img', 'a', 'div', 'font' ]; $allowed_tags = []; foreach ( $tags as $tag ) { $allowed_tags[ $tag ] = $common_attrs; } return wp_kses( $html, $allowed_tags ); } /** * Generate random string * * Creates a random string of specified length and character type. * * @since 1.3 * @access public * @param int $len Length of string. * @param int $type Character type: 1-Number, 2-LowerChar, 4-UpperChar, 7-All. * @return string Randomly generated string. */ public static function rrand( $len, $type = 7 ) { switch ( $type ) { case 0: $charlist = '012'; break; case 1: $charlist = '0123456789'; break; case 2: $charlist = 'abcdefghijklmnopqrstuvwxyz'; break; case 3: $charlist = '0123456789abcdefghijklmnopqrstuvwxyz'; break; case 4: $charlist = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; break; case 5: $charlist = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; break; case 6: $charlist = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; break; case 7: $charlist = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; break; } $str = ''; $max = strlen( $charlist ) - 1; for ( $i = 0; $i < $len; $i++ ) { $str .= $charlist[ random_int( 0, $max ) ]; } return $str; } /** * Trim double quotes from a string * * Removes double quotes from a string for use as a preformatted src in HTML. * * @since 6.5.3 * @access public * @param string $text The string to process. * @return string The string with double quotes removed. */ public static function trim_quotes( $text ) { return str_replace( '"', '', $text ); } } src/admin.cls.php000064400000014204152075713340007724 0ustar00cls( 'Admin_Display' ); // Initialize admin actions. add_action( 'admin_init', [ $this, 'admin_init' ] ); // Add link to plugin list page. add_filter( 'plugin_action_links_' . LSCWP_BASENAME, [ $this->cls( 'Admin_Display' ), 'add_plugin_links' ] ); } /** * Callback that initializes the admin options for LiteSpeed Cache. * * @since 1.0.0 * @return void */ public function admin_init() { // Hook to reset optimization data when image is replaced. add_filter( 'wp_generate_attachment_metadata', [ $this, 'wp_generate_attachment_metadata' ], 10, 3 ); // Hook attachment upload auto optimization. if ( $this->conf( Base::O_IMG_OPTM_AUTO ) ) { add_filter( 'wp_update_attachment_metadata', [ $this, 'wp_update_attachment_metadata' ], 9999, 2 ); } $this->_proceed_admin_action(); // Terminate if user doesn't have access to settings. $capability = is_network_admin() ? 'manage_network_options' : 'manage_options'; if ( ! current_user_can( $capability ) ) { return; } // Add privacy policy (since 2.2.6). if ( function_exists( 'wp_add_privacy_policy_content' ) ) { wp_add_privacy_policy_content( Core::NAME, Doc::privacy_policy() ); } $this->cls( 'Media' )->after_admin_init(); do_action( 'litespeed_after_admin_init' ); if ( $this->cls( 'Router' )->esi_enabled() ) { add_action( 'in_widget_form', [ $this->cls( 'Admin_Display' ), 'show_widget_edit' ], 100, 3 ); add_filter( 'widget_update_callback', __NAMESPACE__ . '\Admin_Settings::validate_widget_save', 10, 4 ); } } /** * Handle attachment metadata generation. * Reset optimization data if this is a replaced image (has existing optimization records). * * @since 7.8 * * @param array $metadata Attachment metadata. * @param int $attachment_id Attachment ID. * @param string $context Context: 'create' or 'update'. * @return array Filtered metadata. */ public function wp_generate_attachment_metadata( $metadata, $attachment_id, $context = 'create' ) { // Only process on 'create' context (replacement also uses 'create') if ( 'create' !== $context ) { return $metadata; } $img_optm = $this->cls( 'Img_Optm' ); // Check if has existing optimization records, if so it's a replacement if ( $img_optm->has_optm_record( $attachment_id, $metadata ) ) { self::debug( 'Image replaced, resetting optimization data [pid] ' . $attachment_id ); $img_optm->reset_row( $attachment_id, true ); } return $metadata; } /** * Handle attachment metadata update. * * @since 4.0 * * @param array $data Attachment meta. * @param int $post_id Attachment ID. * @return array Filtered meta. */ public function wp_update_attachment_metadata( $data, $post_id ) { $this->cls( 'Img_Optm' )->wp_update_attachment_metadata( $data, $post_id ); return $data; } /** * Run LiteSpeed admin actions routed via Router. * * @since 1.1.0 * @return void */ private function _proceed_admin_action() { $action = Router::get_action(); switch ( $action ) { case Router::ACTION_SAVE_SETTINGS: $this->cls( 'Admin_Settings' )->save( wp_unslash( $_POST ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing break; case Router::ACTION_SAVE_SETTINGS_NETWORK: $this->cls( 'Admin_Settings' )->network_save( wp_unslash( $_POST ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing break; default: break; } } /** * Clean up the input (array or scalar) of any extra slashes/spaces. * * @since 1.0.4 * * @param mixed $input The input value to clean. * @return mixed Cleaned value. */ public static function cleanup_text( $input ) { if ( is_array( $input ) ) { return array_map( __CLASS__ . '::cleanup_text', $input ); } return stripslashes(trim($input)); } /** * After a LSCWP_CTRL action, redirect back to same page * without nonce and action in the query string. * * If the redirect URL cannot be determined, redirects to the homepage. * * @since 1.0.12 * * @param string|false $url Optional destination URL. * @return void */ public static function redirect( $url = false ) { global $pagenow; // If originated, go back to referrer or home. if ( ! empty( $_GET['_litespeed_ori'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $ref = wp_get_referer(); wp_safe_redirect( $ref ? $ref : get_home_url() ); exit; } if ( ! $url ) { $clean = []; // Sanitize current query args while removing our internals. if ( ! empty( $_GET ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended foreach ( $_GET as $k => $v ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( in_array( $k, [ Router::ACTION, Router::NONCE, Router::TYPE, 'litespeed_i', 'litespeed_tb' ], true ) ) { continue; } // Normalize to string for URL building. $clean[ $k ] = is_array( $v ) ? array_map( 'sanitize_text_field', wp_unslash( $v ) ) : sanitize_text_field( wp_unslash( $v ) ); } } $qs = ''; if ( ! empty( $clean ) ) { $qs = '?' . http_build_query( $clean ); } $url = is_network_admin() ? network_admin_url( $pagenow . $qs ) : admin_url( $pagenow . $qs ); } wp_safe_redirect( $url ); exit; } } src/tool.cls.php000064400000010254152075713340007612 0ustar00 [ 'User-Agent' => 'curl/8.7.1', ], ] ); if ( is_wp_error( $response ) ) { return esc_html__( 'Failed to detect IP', 'litespeed-cache' ); } $ip = trim( $response['body'] ); self::debug( 'result [ip] ' . $ip ); if ( Utility::valid_ipv4( $ip ) ) { return $ip; } return esc_html__( 'Failed to detect IP', 'litespeed-cache' ); } /** * Heartbeat Control * * Configures WordPress heartbeat settings for frontend, backend, and editor. * * @since 3.0 * @access public */ public function heartbeat() { add_action( 'wp_enqueue_scripts', [ $this, 'heartbeat_frontend' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'heartbeat_backend' ] ); add_filter( 'heartbeat_settings', [ $this, 'heartbeat_settings' ] ); } /** * Heartbeat Control frontend control * * Manages heartbeat settings for the frontend. * * @since 3.0 * @access public */ public function heartbeat_frontend() { if ( ! $this->conf( Base::O_MISC_HEARTBEAT_FRONT ) ) { return; } if ( ! $this->conf( Base::O_MISC_HEARTBEAT_FRONT_TTL ) ) { wp_deregister_script( 'heartbeat' ); Debug2::debug( '[Tool] Deregistered frontend heartbeat' ); } } /** * Heartbeat Control backend control * * Manages heartbeat settings for the backend and editor. * * @since 3.0 * @access public */ public function heartbeat_backend() { if ( $this->is_editor() ) { if ( ! $this->conf( Base::O_MISC_HEARTBEAT_EDITOR ) ) { return; } if ( ! $this->conf( Base::O_MISC_HEARTBEAT_EDITOR_TTL ) ) { wp_deregister_script( 'heartbeat' ); Debug2::debug( '[Tool] Deregistered editor heartbeat' ); } } else { if ( ! $this->conf( Base::O_MISC_HEARTBEAT_BACK ) ) { return; } if ( ! $this->conf( Base::O_MISC_HEARTBEAT_BACK_TTL ) ) { wp_deregister_script( 'heartbeat' ); Debug2::debug( '[Tool] Deregistered backend heartbeat' ); } } } /** * Heartbeat Control settings * * Adjusts heartbeat interval settings based on configuration. * * @since 3.0 * @access public * @param array $settings Existing heartbeat settings. * @return array Modified heartbeat settings. */ public function heartbeat_settings( $settings ) { // Check editor first to make frontend editor valid too if ( $this->is_editor() ) { if ( $this->conf( Base::O_MISC_HEARTBEAT_EDITOR ) ) { $settings['interval'] = $this->conf( Base::O_MISC_HEARTBEAT_EDITOR_TTL ); Debug2::debug( '[Tool] Heartbeat interval set to ' . $this->conf( Base::O_MISC_HEARTBEAT_EDITOR_TTL ) ); } } elseif ( ! is_admin() ) { if ( $this->conf( Base::O_MISC_HEARTBEAT_FRONT ) ) { $settings['interval'] = $this->conf( Base::O_MISC_HEARTBEAT_FRONT_TTL ); Debug2::debug( '[Tool] Heartbeat interval set to ' . $this->conf( Base::O_MISC_HEARTBEAT_FRONT_TTL ) ); } } elseif ( $this->conf( Base::O_MISC_HEARTBEAT_BACK ) ) { $settings['interval'] = $this->conf( Base::O_MISC_HEARTBEAT_BACK_TTL ); Debug2::debug( '[Tool] Heartbeat interval set to ' . $this->conf( Base::O_MISC_HEARTBEAT_BACK_TTL ) ); } return $settings; } /** * Check if in editor * * Determines if the current request is within the WordPress editor. * * @since 3.0 * @access public * @return bool True if in editor, false otherwise. */ public function is_editor() { $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : ''; $res = is_admin() && Utility::str_hit_array( $request_uri, [ 'post.php', 'post-new.php' ] ); return apply_filters( 'litespeed_is_editor', $res ); } } src/htaccess.cls.php000064400000073476152075713340010451 0ustar00 */ private $__rewrite_on; /** * Lines that turn on and guard general rewrite/module blocks. * * @var array */ private $__rewrite_general; const LS_MODULE_START = ''; const EXPIRES_MODULE_START = ''; const LS_MODULE_END = ''; const LS_MODULE_REWRITE_START = ''; const REWRITE_ON = 'RewriteEngine on'; const LS_MODULE_DONOTEDIT = '## LITESPEED WP CACHE PLUGIN - Do not edit the contents of this block! ##'; const MARKER = 'LSCACHE'; const MARKER_NONLS = 'NON_LSCACHE'; const MARKER_LOGIN_COOKIE = '### marker LOGIN COOKIE'; const MARKER_ASYNC = '### marker ASYNC'; const MARKER_CRAWLER = '### marker CRAWLER'; const MARKER_MOBILE = '### marker MOBILE'; const MARKER_NOCACHE_COOKIES = '### marker NOCACHE COOKIES'; const MARKER_NOCACHE_USER_AGENTS = '### marker NOCACHE USER AGENTS'; const MARKER_CACHE_RESOURCE = '### marker CACHE RESOURCE'; const MARKER_BROWSER_CACHE = '### marker BROWSER CACHE'; const MARKER_MINIFY = '### marker MINIFY'; const MARKER_CORS = '### marker CORS'; const MARKER_WEBP = '### marker WEBP'; const MARKER_DROPQS = '### marker DROPQS'; const MARKER_START = ' start ###'; const MARKER_END = ' end ###'; /** * Initialize the class and set its properties. * * @since 1.0.7 */ public function __construct() { $this->_path_set(); $this->_default_frontend_htaccess = $this->frontend_htaccess; $this->_default_backend_htaccess = $this->backend_htaccess; $frontend_htaccess = defined( 'LITESPEED_CFG_HTACCESS' ) ? constant( 'LITESPEED_CFG_HTACCESS' ) : false; if ( $frontend_htaccess && substr( $frontend_htaccess, -10 ) === '/.htaccess' ) { $this->frontend_htaccess = $frontend_htaccess; } $backend_htaccess = defined( 'LITESPEED_CFG_HTACCESS_BACKEND' ) ? constant( 'LITESPEED_CFG_HTACCESS_BACKEND' ) : false; if ( $backend_htaccess && substr( $backend_htaccess, -10 ) === '/.htaccess' ) { $this->backend_htaccess = $backend_htaccess; } // Filter for frontend & backend htaccess path. $this->frontend_htaccess = apply_filters( 'litespeed_frontend_htaccess', $this->frontend_htaccess ); $this->backend_htaccess = apply_filters( 'litespeed_backend_htaccess', $this->backend_htaccess ); clearstatcache(); // Frontend .htaccess privilege. $test_permissions = file_exists( $this->frontend_htaccess ) ? $this->frontend_htaccess : dirname( $this->frontend_htaccess ); if ( is_readable( $test_permissions ) ) { $this->frontend_htaccess_readable = true; } if ( is_writable( $test_permissions ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable -- Checking permissions, not file operations. $this->frontend_htaccess_writable = true; } // General Rewrite Rules (Files/Logs protection) $this->__rewrite_general = [ self::LS_MODULE_REWRITE_START, // self::REWRITE_ON, // RewriteEngine on 'RewriteRule ' . preg_quote(LITESPEED_DATA_FOLDER) . '/debug/.*\.log$ - [F,L]', // phpcs:ignore WordPress.PHP.PregQuoteDelimiter.Missing 'RewriteRule ' . preg_quote(self::CONF_FILE) . ' - [F,L]', // phpcs:ignore WordPress.PHP.PregQuoteDelimiter.Missing self::LS_MODULE_END, // ]; $this->__rewrite_on = [ 'CacheLookup on', 'RewriteRule .* - [E=Cache-Control:no-autoflush]', ]; // Backend .htaccess privilege. if ( $this->frontend_htaccess === $this->backend_htaccess ) { $this->backend_htaccess_readable = $this->frontend_htaccess_readable; $this->backend_htaccess_writable = $this->frontend_htaccess_writable; } else { $test_permissions = file_exists( $this->backend_htaccess ) ? $this->backend_htaccess : dirname( $this->backend_htaccess ); if ( is_readable( $test_permissions ) ) { $this->backend_htaccess_readable = true; } if ( is_writable( $test_permissions ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable -- Checking permissions, not file operations. $this->backend_htaccess_writable = true; } } } /** * Get if htaccess file is readable. * * @since 1.1.0 * * @param string $kind 'frontend' or 'backend'. * @return bool */ private function _readable( $kind = 'frontend' ) { if ( 'frontend' === $kind ) { return $this->frontend_htaccess_readable; } if ( 'backend' === $kind ) { return $this->backend_htaccess_readable; } return false; } /** * Get if htaccess file is writable. * * @since 1.1.0 * * @param string $kind 'frontend' or 'backend'. * @return bool */ public function writable( $kind = 'frontend' ) { if ( 'frontend' === $kind ) { return $this->frontend_htaccess_writable; } if ( 'backend' === $kind ) { return $this->backend_htaccess_writable; } return false; } /** * Get frontend htaccess path. * * @since 1.1.0 * * @param bool $show_default Whether to return the default/auto-detected path. * @return string */ public static function get_frontend_htaccess( $show_default = false ) { if ( $show_default ) { return self::cls()->_default_frontend_htaccess; } return self::cls()->frontend_htaccess; } /** * Get backend htaccess path. * * @since 1.1.0 * * @param bool $show_default Whether to return the default/auto-detected path. * @return string */ public static function get_backend_htaccess( $show_default = false ) { if ( $show_default ) { return self::cls()->_default_backend_htaccess; } return self::cls()->backend_htaccess; } /** * Check to see if .htaccess exists starting at $start_path and going up directories until it hits DOCUMENT_ROOT. * * As dirname() strips the ending '/', paths passed in must exclude the final '/'. * * @since 1.0.11 * @access private * * @param string $start_path Absolute path to begin searching from (without trailing slash). * @return string|false The directory containing .htaccess, or false if not found. */ private function _htaccess_search( $start_path ) { while ( ! file_exists( $start_path . '/.htaccess' ) ) { if ( '/' === $start_path || ! $start_path ) { return false; } $doc_root = ! empty( $_SERVER['DOCUMENT_ROOT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['DOCUMENT_ROOT'] ) ) : ''; if ( $doc_root && wp_normalize_path( $start_path ) === wp_normalize_path( $doc_root ) ) { return false; } if ( dirname( $start_path ) === $start_path ) { return false; } $start_path = dirname( $start_path ); } return $start_path; } /** * Set the path class variables. * * @since 1.0.11 * @access private * @return void */ private function _path_set() { $frontend = Router::frontend_path(); $frontend_htaccess_search = $this->_htaccess_search( $frontend ); // The existing .htaccess path to be used for frontend .htaccess. $this->frontend_htaccess = $frontend; if ( $frontend_htaccess_search ) { $this->frontend_htaccess = $frontend_htaccess_search; } $this->frontend_htaccess .= '/.htaccess'; $backend = realpath( ABSPATH ); // /home/user/public_html/backend/ if ( $frontend === $backend ) { $this->backend_htaccess = $this->frontend_htaccess; return; } // Backend is a different path. $backend_htaccess_search = $this->_htaccess_search( $backend ); // Found affected .htaccess. if ( $backend_htaccess_search ) { $this->backend_htaccess = $backend_htaccess_search . '/.htaccess'; return; } // Frontend path is the parent of backend path. if ( 0 === stripos( (string) $backend, $frontend . '/' ) ) { // Backend uses frontend htaccess. $this->backend_htaccess = $this->frontend_htaccess; return; } $this->backend_htaccess = $backend . '/.htaccess'; } /** * Get corresponding htaccess path. * * @since 1.1.0 * * @param string $kind Frontend or backend. * @return string Path. */ public function htaccess_path( $kind = 'frontend' ) { switch ( $kind ) { case 'backend': $path = $this->backend_htaccess; break; case 'frontend': default: $path = $this->frontend_htaccess; break; } return $path; } /** * Get the content of the rules file. * * NOTE: will throw error if failed. * * @since 1.0.4 * @since 2.9 Used exception for failed reading. * @access public * * @param string $kind 'frontend' or 'backend'. * @return string The file content. * @throws \Exception If the file is not readable or cannot be retrieved. */ public function htaccess_read( $kind = 'frontend' ) { $path = $this->htaccess_path( $kind ); if ( ! $path || ! file_exists( $path ) ) { return "\n"; } if ( ! $this->_readable( $kind ) ) { Error::t( 'HTA_R' ); } $content = File::read( $path ); if ( false === $content ) { Error::t( 'HTA_GET' ); } // Remove ^M characters. $content = str_ireplace( "\x0D", '', $content ); return $content; } /** * Try to backup the .htaccess file if we didn't save one before. * * NOTE: will throw error if failed. * * @since 1.0.10 * @access private * * @param string $kind 'frontend' or 'backend'. * @return void * @throws \Exception If backup fails. */ private function _htaccess_backup( $kind = 'frontend' ) { $path = $this->htaccess_path( $kind ); if ( ! file_exists( $path ) ) { return; } if ( file_exists( $path . '.bk' ) ) { return; } $res = copy( $path, $path . '.bk' ); // Failed to backup, abort. if ( ! $res ) { Error::t( 'HTA_BK' ); } } /** * Get mobile view rule from htaccess file. * * NOTE: will throw error if failed. * * @since 1.1.0 * * @return string The user agent regex for mobile detection. * @throws \Exception If the rule cannot be found. */ public function current_mobile_agents() { $rules = $this->_get_rule_by( self::MARKER_MOBILE ); if ( ! isset( $rules[0] ) ) { Error::t( 'HTA_DNF', self::MARKER_MOBILE ); } $rule = trim( $rules[0] ); $match = substr( $rule, strlen( 'RewriteCond %{HTTP_USER_AGENT} ' ), -strlen( ' [NC]' ) ); if ( ! $match ) { Error::t( 'HTA_DNF', __( 'Mobile Agent Rules', 'litespeed-cache' ) ); } return $match; } /** * Parse rewrites rule from the .htaccess file. * * NOTE: will throw error if failed. * * @since 1.1.0 * @access public * * @param string $kind 'frontend' or 'backend'. * @return string The parsed login-cookie vary rule. * @throws \Exception If the rule cannot be found or is invalid. */ public function current_login_cookie( $kind = 'frontend' ) { $rule = $this->_get_rule_by( self::MARKER_LOGIN_COOKIE, $kind ); if ( ! $rule ) { Error::t( 'HTA_DNF', self::MARKER_LOGIN_COOKIE ); } if ( 0 !== strpos( $rule, 'RewriteRule .? - [E=' ) ) { Error::t( 'HTA_LOGIN_COOKIE_INVALID' ); } $rule_cookie = substr( $rule, strlen( 'RewriteRule .? - [E=' ), -1 ); if ( LITESPEED_SERVER_TYPE === 'LITESPEED_SERVER_OLS' ) { $rule_cookie = trim( $rule_cookie, '"' ); } // Drop `Cache-Vary:`. $rule_cookie = substr( $rule_cookie, strlen( 'Cache-Vary:' ) ); return $rule_cookie; } /** * Get rewrite rules based on the marker. * * @since 2.0 * @access private * * @param string $cond Marker constant (e.g. self::MARKER_MOBILE). * @param string $kind 'frontend' or 'backend'. * @return string|array|false Rule(s) or false if not found. */ private function _get_rule_by( $cond, $kind = 'frontend' ) { clearstatcache(); $path = $this->htaccess_path( $kind ); if ( ! $this->_readable( $kind ) ) { return false; } $rules = File::extract_from_markers( $path, self::MARKER ); if ( ! in_array( $cond . self::MARKER_START, $rules, true ) || ! in_array( $cond . self::MARKER_END, $rules, true ) ) { return false; } $key_start = array_search( $cond . self::MARKER_START, $rules, true ); $key_end = array_search( $cond . self::MARKER_END, $rules, true ); if ( false === $key_start || false === $key_end ) { return false; } $results = array_slice( $rules, $key_start + 1, $key_end - $key_start - 1 ); if ( ! $results ) { return false; } if ( count( $results ) === 1 ) { return trim( $results[0] ); } return array_filter( $results ); } /** * Generate browser cache rules. * * @since 1.3 * @access private * * @param array $cfg The plugin configuration. * @return array Rules set. */ private function _browser_cache_rules( $cfg ) { /** * Add ttl setting. * * @since 1.6.3 */ $id = Base::O_CACHE_TTL_BROWSER; $ttl = $cfg[ $id ]; $rules = array( self::EXPIRES_MODULE_START, 'ExpiresActive on', 'ExpiresByType application/pdf A' . $ttl, 'ExpiresByType image/x-icon A' . $ttl, 'ExpiresByType image/vnd.microsoft.icon A' . $ttl, 'ExpiresByType image/svg+xml A' . $ttl, '', 'ExpiresByType image/jpg A' . $ttl, 'ExpiresByType image/jpeg A' . $ttl, 'ExpiresByType image/png A' . $ttl, 'ExpiresByType image/gif A' . $ttl, 'ExpiresByType image/webp A' . $ttl, 'ExpiresByType image/avif A' . $ttl, '', 'ExpiresByType video/ogg A' . $ttl, 'ExpiresByType audio/ogg A' . $ttl, 'ExpiresByType video/mp4 A' . $ttl, 'ExpiresByType video/webm A' . $ttl, '', 'ExpiresByType text/css A' . $ttl, 'ExpiresByType text/javascript A' . $ttl, 'ExpiresByType application/javascript A' . $ttl, 'ExpiresByType application/x-javascript A' . $ttl, '', 'ExpiresByType application/x-font-ttf A' . $ttl, 'ExpiresByType application/x-font-woff A' . $ttl, 'ExpiresByType application/font-woff A' . $ttl, 'ExpiresByType application/font-woff2 A' . $ttl, 'ExpiresByType application/vnd.ms-fontobject A' . $ttl, 'ExpiresByType font/ttf A' . $ttl, 'ExpiresByType font/otf A' . $ttl, 'ExpiresByType font/woff A' . $ttl, 'ExpiresByType font/woff2 A' . $ttl, '', self::LS_MODULE_END, ); return $rules; } /** * Generate CORS rules for fonts. * * @since 1.5 * @access private * * @return array Rules set. */ private function _cors_rules() { return array( '', '', 'Header set Access-Control-Allow-Origin "*"', '', '', ); } /** * Generate rewrite rules based on settings. * * @since 1.3 * @access private * * @param array $cfg The settings to be used for rewrite rule. * @return array{0:array,1:array,2:array,3:array} Rules arrays [frontend_ls, backend_ls, frontend_nonls, backend_nonls]. */ private function _generate_rules( $cfg ) { $new_rules = array(); $new_rules_nonls = array(); $new_rules_backend = array(); $new_rules_backend_nonls = array(); // continual crawler. $new_rules[] = self::MARKER_ASYNC . self::MARKER_START; $new_rules[] = 'RewriteCond %{REQUEST_URI} /wp-admin/admin-ajax\.php'; $new_rules[] = 'RewriteCond %{QUERY_STRING} action=async_litespeed'; $new_rules[] = 'RewriteRule .* - [E=noabort:1]'; $new_rules[] = self::MARKER_ASYNC . self::MARKER_END; $new_rules[] = ''; // mobile agents. $id = Base::O_CACHE_MOBILE_RULES; if ( ( ! empty( $cfg[ Base::O_CACHE_MOBILE ] ) || ! empty( $cfg[ Base::O_GUEST ] ) ) && ! empty( $cfg[ $id ] ) ) { $new_rules[] = self::MARKER_MOBILE . self::MARKER_START; $new_rules[] = 'RewriteCond %{HTTP_USER_AGENT} ' . Utility::arr2regex( $cfg[ $id ], true ) . ' [NC]'; $new_rules[] = 'RewriteRule .* - [E=Cache-Control:vary=%{ENV:LSCACHE_VARY_VALUE}+ismobile]'; $new_rules[] = self::MARKER_MOBILE . self::MARKER_END; $new_rules[] = ''; } // nocache cookie. $id = Base::O_CACHE_EXC_COOKIES; if ( ! empty( $cfg[ $id ] ) ) { $new_rules[] = self::MARKER_NOCACHE_COOKIES . self::MARKER_START; $new_rules[] = 'RewriteCond %{HTTP_COOKIE} ' . Utility::arr2regex( $cfg[ $id ], true ); $new_rules[] = 'RewriteRule .* - [E=Cache-Control:no-cache]'; $new_rules[] = self::MARKER_NOCACHE_COOKIES . self::MARKER_END; $new_rules[] = ''; } // nocache user agents. $id = Base::O_CACHE_EXC_USERAGENTS; if ( ! empty( $cfg[ $id ] ) ) { $new_rules[] = self::MARKER_NOCACHE_USER_AGENTS . self::MARKER_START; $new_rules[] = 'RewriteCond %{HTTP_USER_AGENT} ' . Utility::arr2regex( $cfg[ $id ], true ) . ' [NC]'; $new_rules[] = 'RewriteRule .* - [E=Cache-Control:no-cache]'; $new_rules[] = self::MARKER_NOCACHE_USER_AGENTS . self::MARKER_END; $new_rules[] = ''; } // check login cookie. $vary_cookies = $cfg[ Base::O_CACHE_VARY_COOKIES ]; $id = Base::O_CACHE_LOGIN_COOKIE; if ( ! empty( $cfg[ $id ] ) ) { $vary_cookies[] = $cfg[ $id ]; } if ( LITESPEED_SERVER_TYPE === 'LITESPEED_SERVER_OLS' ) { // Need to keep this due to different behavior of OLS when handling response vary header @Sep/22/2018. if ( defined( 'COOKIEHASH' ) ) { $vary_cookies[] = ',wp-postpass_' . COOKIEHASH; } } $vary_cookies = apply_filters( 'litespeed_vary_cookies', $vary_cookies ); // todo: test if response vary header can work in latest OLS, drop the above two lines. // frontend and backend. if ( $vary_cookies ) { $env = 'Cache-Vary:' . implode( ',', $vary_cookies ); $env = '"' . $env . '"'; $new_rules[] = self::MARKER_LOGIN_COOKIE . self::MARKER_START; $new_rules_backend[] = self::MARKER_LOGIN_COOKIE . self::MARKER_START; $new_rules[] = 'RewriteRule .? - [E=' . $env . ']'; $new_rules_backend[] = 'RewriteRule .? - [E=' . $env . ']'; $new_rules[] = self::MARKER_LOGIN_COOKIE . self::MARKER_END; $new_rules_backend[] = self::MARKER_LOGIN_COOKIE . self::MARKER_END; $new_rules[] = ''; } // CORS font rules. $id = Base::O_CDN; if ( ! empty( $cfg[ $id ] ) ) { $new_rules[] = self::MARKER_CORS . self::MARKER_START; $new_rules = array_merge( $new_rules, $this->_cors_rules() ); // todo: network. $new_rules[] = self::MARKER_CORS . self::MARKER_END; $new_rules[] = ''; } // webp/next-gen support. $id = Base::O_IMG_OPTM_WEBP; if ( ! empty( $cfg[ $id ] ) ) { $next_gen_format = 'webp'; if ( 2 === (int) $cfg[ $id ] ) { $next_gen_format = 'avif'; } $new_rules[] = self::MARKER_WEBP . self::MARKER_START; // Check for WebP/AVIF support via HTTP_ACCEPT. $new_rules[] = 'RewriteCond %{HTTP_ACCEPT} image/' . $next_gen_format . ' [OR]'; // Check for iPhone browsers (version > 13). $new_rules[] = 'RewriteCond %{HTTP_USER_AGENT} iPhone\ OS\ (1[4-9]|[2-9][0-9]) [OR]'; // Check for macOS Safari (version >= 16.4). $new_rules[] = 'RewriteCond %{HTTP_USER_AGENT} Macintosh.*Version/((1[7-9]|[2-9][0-9])|16\.([4-9]|[1-9][0-9])) [OR]'; // Check for Firefox (version >= 65). $new_rules[] = 'RewriteCond %{HTTP_USER_AGENT} Firefox/([6-9][0-9]|[1-9][0-9]{2,})'; // Add vary. $new_rules[] = 'RewriteRule .* - [E=Cache-Control:vary=%{ENV:LSCACHE_VARY_VALUE}+webp]'; $new_rules[] = self::MARKER_WEBP . self::MARKER_END; $new_rules[] = ''; } // drop qs support. $id = Base::O_CACHE_DROP_QS; if ( ! empty( $cfg[ $id ] ) ) { $new_rules[] = self::MARKER_DROPQS . self::MARKER_START; foreach ( $cfg[ $id ] as $v ) { $new_rules[] = 'CacheKeyModify -qs:' . $v; } $new_rules[] = self::MARKER_DROPQS . self::MARKER_END; $new_rules[] = ''; } // Browser cache. $id = Base::O_CACHE_BROWSER; if ( ! empty( $cfg[ $id ] ) ) { $new_rules_nonls[] = self::MARKER_BROWSER_CACHE . self::MARKER_START; $new_rules_nonls = array_merge( $new_rules_nonls, $this->_browser_cache_rules( $cfg ) ); $new_rules_nonls[] = self::MARKER_BROWSER_CACHE . self::MARKER_END; $new_rules_nonls[] = ''; $new_rules_backend_nonls[] = self::MARKER_BROWSER_CACHE . self::MARKER_START; $new_rules_backend_nonls = array_merge( $new_rules_backend_nonls, $this->_browser_cache_rules( $cfg ) ); $new_rules_backend_nonls[] = self::MARKER_BROWSER_CACHE . self::MARKER_END; $new_rules_backend_nonls[] = ''; } // Add module wrapper for LiteSpeed rules. if ( $new_rules ) { $new_rules = $this->_wrap_ls_module( $new_rules ); } if ( $new_rules_backend ) { $new_rules_backend = $this->_wrap_ls_module( $new_rules_backend ); } return array( $new_rules, $new_rules_backend, $new_rules_nonls, $new_rules_backend_nonls ); } /** * Add LiteSpeed module wrapper with rewrite on. * * @since 2.1.1 * @access private * * @param array $rules Rules to wrap. * @return array Wrapped rules. */ private function _wrap_ls_module( $rules = array() ) { return array_merge( $this->__rewrite_general, array( self::LS_MODULE_START ), $this->__rewrite_on, array( '' ), $rules, array( self::LS_MODULE_END ) ); } /** * Insert LiteSpeed module wrapper with rewrite on. * * @since 2.1.1 * @access public * @return void */ public function insert_ls_wrapper() { $rules = $this->_wrap_ls_module(); $this->_insert_wrapper( $rules ); } /** * Wrap rules with do-not-edit markers. * * @since 1.1.5 * * @param array|false $rules Rules array or false. * @return array|false Wrapped rules, or false if $rules was false. */ private function _wrap_do_no_edit( $rules ) { // When clearing rules, don't need DO NOT EDIT msg. if ( false === $rules || ! is_array( $rules ) ) { return $rules; } $rules = array_merge( array( self::LS_MODULE_DONOTEDIT ), $rules, array( self::LS_MODULE_DONOTEDIT ) ); return $rules; } /** * Write to htaccess with rules. * * NOTE: will throw error if failed. * * @since 1.1.0 * @access private * * @param array|false $rules Rules to write. Pass false to clear. * @param string|false $kind 'frontend' or 'backend'. Defaults to 'frontend'. * @param string|false $marker Marker name. Defaults to self::MARKER. * @return void * @throws \Exception If write fails. */ private function _insert_wrapper( $rules = array(), $kind = false, $marker = false ) { if ( 'backend' !== $kind ) { $kind = 'frontend'; } // Default marker is LiteSpeed marker `LSCACHE`. if ( false === $marker ) { $marker = self::MARKER; } $this->_htaccess_backup( $kind ); File::insert_with_markers( $this->htaccess_path( $kind ), $this->_wrap_do_no_edit( $rules ), $marker, true ); } /** * Update rewrite rules based on setting. * * NOTE: will throw error if failed. * * @since 1.3 * @access public * * @param array $cfg Plugin configuration. * @return bool True on success. * @throws \Exception When automatic update fails (provides manual instructions). */ public function update( $cfg ) { list( $frontend_rules, $backend_rules, $frontend_rules_nonls, $backend_rules_nonls ) = $this->_generate_rules( $cfg ); // Check frontend content. list( $rules, $rules_nonls ) = $this->_extract_rules(); // Check Non-LiteSpeed rules. if ( $this->_wrap_do_no_edit( $frontend_rules_nonls ) !== $rules_nonls ) { Debug2::debug( '[Rules] Update non-ls frontend rules' ); // Need to update frontend htaccess. try { $this->_insert_wrapper( $frontend_rules_nonls, false, self::MARKER_NONLS ); } catch ( \Exception $e ) { $manual_guide_codes = $this->_rewrite_codes_msg( $this->frontend_htaccess, $frontend_rules_nonls, self::MARKER_NONLS ); Debug2::debug( '[Rules] Update Failed' ); throw new \Exception( $manual_guide_codes ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Message is for admin display. } } // Check LiteSpeed rules. if ( $this->_wrap_do_no_edit( $frontend_rules ) !== $rules ) { Debug2::debug( '[Rules] Update frontend rules' ); // Need to update frontend htaccess. try { $this->_insert_wrapper( $frontend_rules ); } catch ( \Exception $e ) { Debug2::debug( '[Rules] Update Failed' ); $manual_guide_codes = $this->_rewrite_codes_msg( $this->frontend_htaccess, $frontend_rules ); throw new \Exception( $manual_guide_codes ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Message is for admin display. } } if ( $this->frontend_htaccess !== $this->backend_htaccess ) { list( $rules, $rules_nonls ) = $this->_extract_rules( 'backend' ); // Check Non-LiteSpeed rules for backend. if ( $this->_wrap_do_no_edit( $backend_rules_nonls ) !== $rules_nonls ) { Debug2::debug( '[Rules] Update non-ls backend rules' ); // Need to update backend htaccess. try { $this->_insert_wrapper( $backend_rules_nonls, 'backend', self::MARKER_NONLS ); } catch ( \Exception $e ) { Debug2::debug( '[Rules] Update Failed' ); $manual_guide_codes = $this->_rewrite_codes_msg( $this->backend_htaccess, $backend_rules_nonls, self::MARKER_NONLS ); throw new \Exception( $manual_guide_codes ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Message is for admin display. } } // Check backend content. if ( $this->_wrap_do_no_edit( $backend_rules ) !== $rules ) { Debug2::debug( '[Rules] Update backend rules' ); // Need to update backend htaccess. try { $this->_insert_wrapper( $backend_rules, 'backend' ); } catch ( \Exception $e ) { Debug2::debug( '[Rules] Update Failed' ); $manual_guide_codes = $this->_rewrite_codes_msg( $this->backend_htaccess, $backend_rules ); throw new \Exception( $manual_guide_codes ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Message is for admin display. } } } return true; } /** * Get existing rewrite rules. * * NOTE: will throw error if failed. * * @since 1.3 * @access private * * @param string $kind Frontend or backend .htaccess file. * @return array{0:array,1:array} A tuple of [ls_rules, nonls_rules]. * @throws \Exception If file is not readable. */ private function _extract_rules( $kind = 'frontend' ) { clearstatcache(); $path = $this->htaccess_path( $kind ); if ( ! $this->_readable( $kind ) ) { Error::t( 'E_HTA_R' ); } $rules = File::extract_from_markers( $path, self::MARKER ); $rules_nonls = File::extract_from_markers( $path, self::MARKER_NONLS ); return array( $rules, $rules_nonls ); } /** * Output the msg with rules plain data for manual insert. * * @since 1.1.5 * * @param string $file The target file path. * @param array $rules The rules to be inserted. * @param string|false $marker Optional marker name. Defaults to LiteSpeed's marker. * @return string The final message (HTML) to output. */ private function _rewrite_codes_msg( $file, $rules, $marker = false ) { return sprintf( /* translators: 1: file path, 2: code block */ __( '

Please add/replace the following codes into the beginning of %1$s:

%2$s', 'litespeed-cache' ), esc_html( $file ), '' ); } /** * Generate rules plain data for manual insert. * * @since 1.1.5 * * @param array|false $rules Rules to wrap or false. * @param string|false $marker Optional marker name. Defaults to LiteSpeed's marker. * @return string The plain text of the rules with markers. */ private function _wrap_rules_with_marker( $rules, $marker = false ) { // Default marker is LiteSpeed marker `LSCACHE`. if ( false === $marker ) { $marker = self::MARKER; } $start_marker = "# BEGIN {$marker}"; $end_marker = "# END {$marker}"; $new_file_data = implode( "\n", array_merge( array( $start_marker ), $this->_wrap_do_no_edit( $rules ), array( $end_marker ) ) ); return $new_file_data; } /** * Clear the rules file of any changes added by the plugin specifically. * * @since 1.0.4 * @access public * * @return void */ public function clear_rules() { $this->_insert_wrapper( false ); // Use false to avoid do-not-edit msg. // Clear non ls rules. $this->_insert_wrapper( false, false, self::MARKER_NONLS ); if ( $this->frontend_htaccess !== $this->backend_htaccess ) { $this->_insert_wrapper( false, 'backend' ); $this->_insert_wrapper( false, 'backend', self::MARKER_NONLS ); } } } src/api.cls.php000064400000024564152075713340007417 0ustar00cls( 'Admin_Display' ), 'enroll' ], 10, 4 ); add_action( 'litespeed_build_switch', [ $this->cls( 'Admin_Display' ), 'build_switch' ] ); // Action `litespeed_settings_content` // Action `litespeed_settings_tab` } /** * Disable All * * Disables all LiteSpeed Cache features with a given reason. * * @since 2.9.7.2 * @access public * @param string $reason The reason for disabling all features. */ public function disable_all( $reason ) { do_action( 'litespeed_debug', '[API] Disabled_all due to ' . $reason ); ! defined( 'LITESPEED_DISABLE_ALL' ) && define( 'LITESPEED_DISABLE_ALL', true ); } /** * Append commenter vary * * Adds commenter vary to the cache vary cookies. * * @since 3.0 * @access public */ public static function vary_append_commenter() { Vary::cls()->append_commenter(); } /** * Check if is from Cloud * * Checks if the current request originates from QUIC.cloud. * * @since 4.2 * @access public * @return bool True if from QUIC.cloud, false otherwise. */ public function is_from_cloud() { return $this->cls( 'Cloud' )->is_from_cloud(); } /** * Purge post * * Purges the cache for a specific post. * * @since 3.0 * @access public * @param int $pid Post ID to purge. */ public function purge_post( $pid ) { $this->cls( 'Purge' )->purge_post( $pid ); } /** * Purge URL * * Purges the cache for a specific URL. * * @since 3.0 * @access public * @param string $url URL to purge. */ public function purge_url( $url ) { $this->cls( 'Purge' )->purge_url( $url ); } /** * Set cacheable * * Marks the current request as cacheable. * * @since 3.0 * @access public * @param string|bool $reason Optional reason for setting cacheable. */ public function set_cacheable( $reason = false ) { $this->cls( 'Control' )->set_cacheable( $reason ); } /** * Check ESI enabled * * Returns whether ESI is enabled. * * @since 3.0 * @access public * @return bool True if ESI is enabled, false otherwise. */ public function esi_enabled() { return $this->cls( 'Router' )->esi_enabled(); } /** * Get TTL * * Retrieves the cache TTL (time to live). * * @since 3.0 * @access public * @return int Cache TTL value. */ public function get_ttl() { return $this->cls( 'Control' )->get_ttl(); } /** * Generate ESI block URL * * Generates a URL for an ESI block. * * @since 3.0 * @access public * @param string $block_id ESI block ID. * @param string $wrapper Wrapper identifier. * @param array $params Parameters for the ESI block. * @param string $control Cache control settings. * @param bool $silence Silence output flag. * @param bool $preserved Preserved flag. * @param bool $svar Server variable flag. * @param array $inline_param Inline parameters. * @return string ESI block URL. */ public function sub_esi_block( $block_id, $wrapper, $params = [], $control = 'private,no-vary', $silence = false, $preserved = false, $svar = false, $inline_param = [] ) { return $this->cls( 'ESI' )->sub_esi_block( $block_id, $wrapper, $params, $control, $silence, $preserved, $svar, $inline_param ); } /** * Set and sync conf * * Updates and synchronizes configuration settings. * * @since 7.2 * @access public * @param bool|array $the_matrix Configuration data to update. */ public function save_conf( $the_matrix = false ) { $this->cls( 'Conf' )->update_confs( $the_matrix ); } } src/purge.cls.php000064400000104643152075713340007765 0ustar00 */ protected $_pub_purge = []; /** * Public purge tags for X-LiteSpeed-Purge2. * * @var array */ protected $_pub_purge2 = []; /** * Private purge tags for X-LiteSpeed-Purge (private section). * * @var array */ protected $_priv_purge = []; /** * Whether to purge only current URL (QS helper). * * @var bool */ protected $_purge_single = false; const X_HEADER = 'X-LiteSpeed-Purge'; const X_HEADER2 = 'X-LiteSpeed-Purge2'; const DB_QUEUE = 'queue'; const DB_QUEUE2 = 'queue2'; const TYPE_PURGE_ALL = 'purge_all'; const TYPE_PURGE_ALL_LSCACHE = 'purge_all_lscache'; const TYPE_PURGE_ALL_CSSJS = 'purge_all_cssjs'; const TYPE_PURGE_ALL_LOCALRES = 'purge_all_localres'; const TYPE_PURGE_ALL_CCSS = 'purge_all_ccss'; const TYPE_PURGE_ALL_UCSS = 'purge_all_ucss'; const TYPE_PURGE_ALL_LQIP = 'purge_all_lqip'; const TYPE_PURGE_ALL_VPI = 'purge_all_vpi'; const TYPE_PURGE_ALL_AVATAR = 'purge_all_avatar'; const TYPE_PURGE_ALL_OBJECT = 'purge_all_object'; const TYPE_PURGE_ALL_OPCACHE = 'purge_all_opcache'; const TYPE_PURGE_FRONT = 'purge_front'; const TYPE_PURGE_UCSS = 'purge_ucss'; const TYPE_PURGE_FRONTPAGE = 'purge_frontpage'; const TYPE_PURGE_PAGES = 'purge_pages'; const TYPE_PURGE_ERROR = 'purge_error'; /** * Init hooks. * * @since 3.0 * @return void */ public function init() { $purge_post_events = apply_filters( 'litespeed_purge_post_events', [ 'delete_post', 'wp_trash_post', 'wp_update_comment_count', ] ); foreach ( $purge_post_events as $event ) { add_action( $event, [ $this, 'purge_post' ] ); } // Purge post only when status is/was publish. add_action( 'transition_post_status', [ $this, 'purge_publish' ], 10, 3 ); add_action( 'wp_update_comment_count', [ $this, 'purge_feeds' ] ); if ( $this->conf( self::O_OPTM_UCSS ) ) { add_action( 'edit_post', __NAMESPACE__ . '\Purge::purge_ucss' ); } } /** * Only purge publish related status post. * * @since 3.0 * @param string $new_status New status. * @param string $old_status Old status. * @param \WP_Post $post Post object. * @return void */ public function purge_publish( $new_status, $old_status, $post ) { if ( 'publish' !== $new_status && 'publish' !== $old_status ) { return; } $this->purge_post( $post->ID ); } /** * Handle all request actions from main cls. * * @since 1.8 * @since 7.6 Add VPI clear. * @access public */ public function handler() { $type = Router::verify_type(); switch ( $type ) { case self::TYPE_PURGE_ALL: $this->_purge_all(); break; case self::TYPE_PURGE_ALL_LSCACHE: $this->_purge_all_lscache(); break; case self::TYPE_PURGE_ALL_CSSJS: $this->_purge_all_cssjs(); break; case self::TYPE_PURGE_ALL_LOCALRES: $this->_purge_all_localres(); break; case self::TYPE_PURGE_ALL_CCSS: $this->_purge_all_ccss(); break; case self::TYPE_PURGE_ALL_UCSS: $this->_purge_all_ucss(); break; case self::TYPE_PURGE_ALL_LQIP: $this->_purge_all_lqip(); break; case self::TYPE_PURGE_ALL_VPI: $this->_purge_all_vpi(); break; case self::TYPE_PURGE_ALL_AVATAR: $this->_purge_all_avatar(); break; case self::TYPE_PURGE_ALL_OBJECT: $this->_purge_all_object(); break; case self::TYPE_PURGE_ALL_OPCACHE: $this->purge_all_opcache(); break; case self::TYPE_PURGE_FRONT: $this->_purge_front(); break; case self::TYPE_PURGE_UCSS: $this->_purge_ucss(); break; case self::TYPE_PURGE_FRONTPAGE: $this->_purge_frontpage(); break; case self::TYPE_PURGE_PAGES: $this->_purge_pages(); break; case ( 0 === strpos( $type, self::TYPE_PURGE_ERROR ) ): $this->_purge_error( substr( $type, strlen( self::TYPE_PURGE_ERROR ) ) ); break; default: break; } Admin::redirect(); } /** * Shortcut to purge all lscache. * * @since 1.0.0 * @param string|false $reason Optional reason to log. * @return void */ public static function purge_all( $reason = false ) { self::cls()->_purge_all( $reason ); } /** * Purge all caches (LSCache/CSS/JS/localres/object/opcache). * * @since 2.2 * @param string|false $reason Optional log string. * @return void */ private function _purge_all( $reason = false ) { $this->_purge_all_lscache( true ); $this->_purge_all_cssjs( true ); $this->_purge_all_localres( true ); $this->_purge_all_object( true ); $this->purge_all_opcache( true ); if ( $this->conf( self::O_CDN_CLOUDFLARE_CLEAR ) ) { CDN\Cloudflare::purge_all( 'Purge All' ); } $reason = is_string( $reason ) ? "( $reason )" : ''; self::debug( 'Purge all ' . $reason, 3 ); $msg = __( 'Purged all caches successfully.', 'litespeed-cache' ); if ( ! defined( 'LITESPEED_PURGE_SILENT' ) ) { Admin_Display::success( $msg ); } do_action( 'litespeed_purged_all' ); } /** * Shortcut to purge lscache. * * @since 7.7 * @param string|false $reason Optional reason to log. * @return void */ public static function purge_all_lscache( $reason = false ) { if ( $reason ) { self::debug( 'Purge lscache ' . $reason, 3 ); } self::cls()->_purge_all_lscache(); } /** * Alerts LiteSpeed Web Server to purge all pages. * * @since 2.2 * @param bool $silence If true, don't show admin notice. * @return void */ private function _purge_all_lscache( $silence = false ) { $this->_add( '*' ); do_action( 'litespeed_purged_all_lscache' ); if ( ! $silence ) { $msg = __( 'Notified LiteSpeed Web Server to purge all LSCache entries.', 'litespeed-cache' ); if ( ! defined( 'LITESPEED_PURGE_SILENT' ) ) { Admin_Display::success( $msg ); } } } /** * Delete all critical CSS. * * @since 2.3 * @param bool $silence If true, don't show admin notice. * @return void */ private function _purge_all_ccss( $silence = false ) { do_action( 'litespeed_purged_all_ccss' ); $this->cls( 'CSS' )->rm_cache_folder( 'ccss' ); $this->cls( 'Data' )->url_file_clean( 'ccss' ); if ( ! $silence ) { $msg = __( 'Cleaned all Critical CSS files.', 'litespeed-cache' ); if ( ! defined( 'LITESPEED_PURGE_SILENT' ) ) { Admin_Display::success( $msg ); } } } /** * Delete all unique CSS. * * @since 2.3 * @param bool $silence If true, don't show admin notice. * @return void */ private function _purge_all_ucss( $silence = false ) { do_action( 'litespeed_purged_all_ucss' ); $this->cls( 'CSS' )->rm_cache_folder( 'ucss' ); $this->cls( 'Data' )->url_file_clean( 'ucss' ); if ( ! $silence ) { $msg = __( 'Cleaned all Unique CSS files.', 'litespeed-cache' ); if ( ! defined( 'LITESPEED_PURGE_SILENT' ) ) { Admin_Display::success( $msg ); } } } /** * Purge one UCSS by URL or post ID. * * @since 4.5 * @param int|string $post_id_or_url Post ID or URL. * @return void */ public static function purge_ucss( $post_id_or_url ) { self::debug( 'Purge a single UCSS: ' . $post_id_or_url ); // If is post_id, generate URL. if ( ! preg_match( '/\D/', (string) $post_id_or_url ) ) { $post_id_or_url = get_permalink( (int) $post_id_or_url ); } $post_id_or_url = (string) $post_id_or_url; $permalink_structure = get_option( 'permalink_structure' ); if ( ! empty( $permalink_structure ) ) { $post_id_or_url = trailingslashit( (string) $post_id_or_url ); } $existing_url_files = Data::cls()->mark_as_expired( $post_id_or_url, true ); if ( $existing_url_files ) { self::cls( 'UCSS' )->add_to_q( $existing_url_files ); } } /** * Delete all LQIP images. * * @since 3.0 * @param bool $silence If true, don't show admin notice. * @return void */ private function _purge_all_lqip( $silence = false ) { do_action( 'litespeed_purged_all_lqip' ); $this->cls( 'Placeholder' )->rm_cache_folder( 'lqip' ); if ( ! $silence ) { $msg = __( 'Cleaned all LQIP files.', 'litespeed-cache' ); if ( ! defined( 'LITESPEED_PURGE_SILENT' ) ) { Admin_Display::success( $msg ); } } } /** * Delete all VPI data generated * * @since 7.6 * @param bool $silence If true, don't show admin notice. * @return void * @access private */ private function _purge_all_vpi( $silence = false ) { global $wpdb; do_action( 'litespeed_purged_all_vpi' ); $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->prepare( 'DELETE FROM `' . $wpdb->postmeta . '` WHERE meta_key = %s', VPI::POST_META ) ); $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->prepare( 'DELETE FROM `' . $wpdb->postmeta . '` WHERE meta_key = %s', VPI::POST_META_MOBILE ) ); $this->cls( 'Placeholder' )->rm_cache_folder( 'vpi' ); if ( !$silence ) { $msg = __( 'Cleaned all VPI data.', 'litespeed-cache' ); !defined( 'LITESPEED_PURGE_SILENT' ) && Admin_Display::success( $msg ); } } /** * Delete all avatar images * * @since 3.0 * @param bool $silence If true, don't show admin notice. * @return void */ private function _purge_all_avatar( $silence = false ) { do_action( 'litespeed_purged_all_avatar' ); // Clear Database table $this->cls( 'Data' )->table_truncate( 'avatar' ); // Remove the folder $this->cls( 'Avatar' )->rm_cache_folder( 'avatar' ); if ( ! $silence ) { $msg = __( 'Cleaned all Gravatar files.', 'litespeed-cache' ); if ( ! defined( 'LITESPEED_PURGE_SILENT' ) ) { Admin_Display::success( $msg ); } } } /** * Delete all localized resources. * * @since 3.3 * @param bool $silence If true, don't show admin notice. * @return void */ private function _purge_all_localres( $silence = false ) { do_action( 'litespeed_purged_all_localres' ); $this->_add( Tag::TYPE_LOCALRES ); if ( ! $silence ) { $msg = __( 'Cleaned all localized resource entries.', 'litespeed-cache' ); if ( ! defined( 'LITESPEED_PURGE_SILENT' ) ) { Admin_Display::success( $msg ); } } } /** * Purge CSS/JS assets and related LSCache entries. * * @since 1.2.2 * @param bool $silence If true, don't show admin notice. * @return void */ private function _purge_all_cssjs( $silence = false ) { if ( wp_doing_cron() || defined( 'LITESPEED_DID_send_headers' ) ) { self::debug( '❌ Bypassed cssjs delete as header sent (lscache purge after this point will fail) or doing cron' ); return; } $this->_purge_all_lscache( $silence ); // Purge CSSJS must purge lscache too to avoid 404 do_action( 'litespeed_purged_all_cssjs' ); Optimize::update_option( Optimize::ITEM_TIMESTAMP_PURGE_CSS, time() ); $this->_add( Tag::TYPE_MIN ); $this->cls( 'CSS' )->rm_cache_folder( 'css' ); $this->cls( 'CSS' )->rm_cache_folder( 'js' ); $this->cls( 'Data' )->url_file_clean( 'css' ); $this->cls( 'Data' )->url_file_clean( 'js' ); // Clear UCSS queue as it used combined CSS to generate. $this->clear_q( 'ucss', true ); if ( ! $silence ) { $msg = __( 'Notified LiteSpeed Web Server to purge CSS/JS entries.', 'litespeed-cache' ); if ( ! defined( 'LITESPEED_PURGE_SILENT' ) ) { Admin_Display::success( $msg ); } } } /** * Purge opcode cache. * * @since 1.8.2 * @since 7.3 Added test for opcode cache restriction. * @param bool $silence If true, don't show admin notice. * @return bool True on success. */ public function purge_all_opcache( $silence = false ) { if ( ! Router::opcache_enabled() ) { self::debug( '❌ Failed to reset OPcache due to OPcache not enabled' ); if ( ! $silence ) { $msg = __( 'OPcache is not enabled.', 'litespeed-cache' ); if ( ! defined( 'LITESPEED_PURGE_SILENT' ) ) { Admin_Display::error( $msg ); } } return false; } if ( Router::opcache_restricted( __FILE__ ) ) { self::debug( '❌ Failed to reset OPcache due to OPcache is restricted. File requesting the clear is not allowed.' ); if ( ! $silence ) { $msg = sprintf( __( 'OPcache is restricted by %s setting.', 'litespeed-cache' ), 'restrict_api' ); if ( ! defined( 'LITESPEED_PURGE_SILENT' ) ) { Admin_Display::error( $msg ); } } return false; } if ( ! opcache_reset() ) { self::debug( '❌ Reset OPcache not worked' ); if ( ! $silence ) { $msg = __( 'Reset the OPcache failed.', 'litespeed-cache' ); if ( ! defined( 'LITESPEED_PURGE_SILENT' ) ) { Admin_Display::success( $msg ); } } return false; } do_action( 'litespeed_purged_all_opcache' ); self::debug( 'Reset OPcache' ); if ( ! $silence ) { $msg = __( 'Reset the entire OPcache successfully.', 'litespeed-cache' ); if ( ! defined( 'LITESPEED_PURGE_SILENT' ) ) { Admin_Display::success( $msg ); } } return true; } /** * Purge object cache (public wrapper). * * @since 3.4 * @param bool $silence If true, don't show admin notice. * @return void */ public static function purge_all_object( $silence = true ) { self::cls()->_purge_all_object( $silence ); } /** * Purge object cache. * * @since 1.8 * @param bool $silence If true, don't show admin notice. * @return bool True on success. */ private function _purge_all_object( $silence = false ) { if ( ! defined( 'LSCWP_OBJECT_CACHE' ) ) { self::debug( 'Failed to flush object cache due to object cache not enabled' ); if ( ! $silence ) { $msg = __( 'Object cache is not enabled.', 'litespeed-cache' ); Admin_Display::error( $msg ); } return false; } do_action( 'litespeed_purged_all_object' ); $this->cls( 'Object_Cache' )->flush(); self::debug( 'Flushed object cache' ); if ( ! $silence ) { $msg = __( 'Purge all object caches successfully.', 'litespeed-cache' ); if ( ! defined( 'LITESPEED_PURGE_SILENT' ) ) { Admin_Display::success( $msg ); } } return true; } /** * Add public purge tags for current request. * * @since 1.1.3 * @param string|array $tags Tags to add. * @param bool $purge2 Whether to send via X-LiteSpeed-Purge2. * @return void */ public static function add( $tags, $purge2 = false ) { self::cls()->_add( $tags, $purge2 ); } /** * Add tags to purge list. * * @since 2.2 * @param string|array $tags Tags. * @param bool $purge2 Use Purge2 header. * @return void */ private function _add( $tags, $purge2 = false ) { if ( ! is_array( $tags ) ) { $tags = [ $tags ]; } $tags = $this->_prepend_bid( $tags ); if ( ! array_diff( $tags, $purge2 ? $this->_pub_purge2 : $this->_pub_purge ) ) { return; } if ( $purge2 ) { $this->_pub_purge2 = array_unique( array_merge( $this->_pub_purge2, $tags ) ); } else { $this->_pub_purge = array_unique( array_merge( $this->_pub_purge, $tags ) ); } self::debug( 'added ' . implode( ',', $tags ) . ( $purge2 ? ' [Purge2]' : '' ), 8 ); // Send purge header immediately or queue if headers already sent or delayed. $curr_built = $this->_build( $purge2 ); if ( defined( 'LITESPEED_CLI' ) ) { // Can't send, already has output, need to save and wait for next run self::update_option($purge2 ? self::DB_QUEUE2 : self::DB_QUEUE, $curr_built); self::debug( 'CLI request, queue stored: ' . $curr_built ); } else { if ( ! headers_sent() ) { header( $curr_built ); } if ( wp_doing_cron() || defined( 'LITESPEED_DID_send_headers' ) || apply_filters( 'litespeed_delay_purge', false ) ) { self::update_option( $purge2 ? self::DB_QUEUE2 : self::DB_QUEUE, $curr_built ); self::debug( 'Output existed, queue stored: ' . $curr_built ); } self::debug( $curr_built ); } } /** * Add private purge tags for current request. * * @since 1.1.3 * @param string|array $tags Tags. * @return void */ public static function add_private( $tags ) { self::cls()->_add_private( $tags ); } /** * Add private ESI tag to purge list. * * @since 3.0 * @param string $tag ESI tag. * @return void */ public static function add_private_esi( $tag ) { self::add_private( Tag::TYPE_ESI . $tag ); } /** * Add private all tag to purge list. * * @since 3.0 * @return void */ public static function add_private_all() { self::add_private( '*' ); } /** * Add private purge tags. * * @since 2.2 * @param string|array $tags Tags. * @return void */ private function _add_private( $tags ) { if ( ! is_array( $tags ) ) { $tags = [ $tags ]; } $tags = $this->_prepend_bid( $tags ); if ( ! array_diff( $tags, $this->_priv_purge ) ) { return; } self::debug( 'added [private] ' . implode( ',', $tags ), 3 ); $this->_priv_purge = array_unique( array_merge( $this->_priv_purge, $tags ) ); // Send header immediately or skip if sent. $built = $this->_build(); if ( $built && ! headers_sent() ) { header( $built ); } } /** * Add current blog ID prefix to tags (multisite). * * @since 4.0 * @param array $tags Tags. * @return array */ private function _prepend_bid( $tags ) { if ( in_array( '*', $tags, true ) ) { return [ '*' ]; } $curr_bid = is_multisite() ? get_current_blog_id() : ''; foreach ( $tags as $k => $v ) { $tags[ $k ] = $curr_bid . '_' . $v; } return $tags; } /** * Activate `purge single url tag` for Admin QS. * * @since 1.1.3 * @return void */ public static function set_purge_single() { self::cls()->_purge_single = true; do_action( 'litespeed_purged_single' ); } /** * Purge frontend url (based on HTTP_REFERER). * * @since 1.3 * @since 2.2 Access changed from public to private, renamed from `frontend_purge`. * @return void */ private function _purge_front() { if ( empty( $_SERVER['HTTP_REFERER'] ) ) { exit( 'no referer' ); } $ref = esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ); $this->purge_url( $ref ); do_action( 'litespeed_purged_front', $ref ); wp_safe_redirect( $ref ); exit; } /** * Purge single UCSS (via referer or `url_tag`). * * @since 4.7 * @return void */ private function _purge_ucss() { if ( empty( $_SERVER['HTTP_REFERER'] ) ) { exit( 'no referer' ); } $ref = esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ); $url_tag = ! empty( $_GET['url_tag'] ) ? sanitize_text_field( wp_unslash( $_GET['url_tag'] ) ) : $ref; // phpcs:ignore WordPress.Security.NonceVerification.Recommended self::debug( 'Purge ucss [url_tag] ' . $url_tag ); do_action( 'litespeed_purge_ucss', $url_tag ); $this->purge_url( $ref ); wp_safe_redirect( $ref ); exit; } /** * Purge the front page. * * @since 1.0.3 * @return void */ private function _purge_frontpage() { $this->_add( Tag::TYPE_FRONTPAGE ); if ( 'LITESPEED_SERVER_OLS' !== LITESPEED_SERVER_TYPE ) { $this->_add_private( Tag::TYPE_FRONTPAGE ); } $msg = __( 'Notified LiteSpeed Web Server to purge the front page.', 'litespeed-cache' ); if ( ! defined( 'LITESPEED_PURGE_SILENT' ) ) { Admin_Display::success( $msg ); } do_action( 'litespeed_purged_frontpage' ); } /** * Purge all pages. * * @since 1.0.15 * @return void */ private function _purge_pages() { $this->_add( Tag::TYPE_PAGES ); $msg = __( 'Notified LiteSpeed Web Server to purge all pages.', 'litespeed-cache' ); if ( ! defined( 'LITESPEED_PURGE_SILENT' ) ) { Admin_Display::success( $msg ); } do_action( 'litespeed_purged_pages' ); } /** * Purge error pages (403/404/500). * * @since 1.0.14 * @param string|false $type Error type. * @return void */ private function _purge_error( $type = false ) { $this->_add( Tag::TYPE_HTTP ); if ( ! $type || ! in_array( (string) $type, [ '403', '404', '500' ], true ) ) { return; } $this->_add( Tag::TYPE_HTTP . $type ); $msg = __( 'Notified LiteSpeed Web Server to purge error pages.', 'litespeed-cache' ); if ( ! defined( 'LITESPEED_PURGE_SILENT' ) ) { Admin_Display::success( $msg ); } } /** * Purge selected category by slug. * * @since 1.0.7 * @param string $value Category slug. * @return void */ public function purge_cat( $value ) { $val = trim( (string) $value ); if ( '' === $val ) { return; } if ( 0 === preg_match( '/^[a-zA-Z0-9-]+$/', $val ) ) { self::debug( "$val cat invalid" ); return; } $cat = get_category_by_slug( $val ); if ( false === $cat ) { self::debug( "$val cat not existed/published" ); return; } self::add( Tag::TYPE_ARCHIVE_TERM . $cat->term_id ); if ( ! defined( 'LITESPEED_PURGE_SILENT' ) ) { Admin_Display::success( sprintf( __( 'Purge category %s', 'litespeed-cache' ), $val ) ); } do_action( 'litespeed_purged_cat', $value ); } /** * Purge selected tag by slug. * * @since 1.0.7 * @param string $val Tag slug. * @return void */ public function purge_tag( $val ) { $val = trim( (string) $val ); if ( '' === $val ) { return; } if ( 0 === preg_match( '/^[a-zA-Z0-9-]+$/', $val ) ) { self::debug( "$val tag invalid" ); return; } $term = get_term_by( 'slug', $val, 'post_tag' ); if ( false === $term ) { self::debug( "$val tag not exist" ); return; } self::add( Tag::TYPE_ARCHIVE_TERM . $term->term_id ); if ( ! defined( 'LITESPEED_PURGE_SILENT' ) ) { Admin_Display::success( sprintf( __( 'Purge tag %s', 'litespeed-cache' ), $val ) ); } do_action( 'litespeed_purged_tag', $val ); } /** * Purge selected url (relative allowed). * * @since 1.0.7 * @param string $url URL. * @param bool $purge2 Use Purge2 header. * @param bool $quite If true, do not show admin notice. * @return void */ public function purge_url( $url, $purge2 = false, $quite = false ) { $val = trim( (string) $url ); if ( '' === $val ) { return; } if ( false !== strpos( $val, '<' ) ) { self::debug( "$val url contains <" ); return; } $val = Utility::make_relative( $val ); $hash = Tag::get_uri_tag( $val ); if ( false === $hash ) { self::debug( "$val url invalid" ); return; } self::add( $hash, $purge2 ); if ( ! $quite && ! defined( 'LITESPEED_PURGE_SILENT' ) ) { Admin_Display::success( sprintf( __( 'Purge url %s', 'litespeed-cache' ), $val ) ); } do_action( 'litespeed_purged_link', $url ); } /** * Purge a list based on admin selection. * * @since 1.0.7 * @return void */ public function purge_list() { if ( ! isset( $_REQUEST[ Admin_Display::PURGEBYOPT_SELECT ], $_REQUEST[ Admin_Display::PURGEBYOPT_LIST ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return; } $sel = sanitize_text_field( wp_unslash( $_REQUEST[ Admin_Display::PURGEBYOPT_SELECT ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended $list_buf = sanitize_textarea_field( wp_unslash( $_REQUEST[ Admin_Display::PURGEBYOPT_LIST ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( '' === $list_buf ) { return; } $list_buf = str_replace( ',', "\n", $list_buf ); $raw_list = explode( "\n", $list_buf ); switch ( $sel ) { case Admin_Display::PURGEBY_CAT: $cb = 'purge_cat'; break; case Admin_Display::PURGEBY_PID: $cb = 'purge_post'; break; case Admin_Display::PURGEBY_TAG: $cb = 'purge_tag'; break; case Admin_Display::PURGEBY_URL: $cb = 'purge_url'; break; default: return; } array_map( [ $this, $cb ], $raw_list ); // For redirection (safe copy back to GET). $_GET[ Admin_Display::PURGEBYOPT_SELECT ] = $sel; // phpcs:ignore WordPress.Security.NonceVerification.Recommended } /** * Purge ESI. * * @since 3.0 * @param string $tag ESI tag. * @return void */ public static function purge_esi( $tag ) { self::add( Tag::TYPE_ESI . $tag ); do_action( 'litespeed_purged_esi', $tag ); } /** * Purge a certain post type. * * @since 3.0 * @param string $post_type Post type. * @return void */ public static function purge_posttype( $post_type ) { self::add( Tag::TYPE_ARCHIVE_POSTTYPE . $post_type ); self::add( $post_type ); do_action( 'litespeed_purged_posttype', $post_type ); } /** * Purge all related tags to a post. * * @since 1.0.0 * @param int $pid Post ID. * @return void */ public function purge_post( $pid ) { $pid = (int) $pid; // Ignore the status we don't care. $status = get_post_status( $pid ); if ( ! $pid || ! in_array( $status, [ 'publish', 'trash', 'private', 'draft' ], true ) ) { return; } $purge_tags = $this->_get_purge_tags_by_post( $pid ); if ( ! $purge_tags ) { return; } self::add( $purge_tags ); if ( $this->conf( self::O_CACHE_REST ) ) { self::add( Tag::TYPE_REST ); } do_action( 'litespeed_purged_post', $pid ); } /** * Purge a widget by ID (or discover Recent Comments widget). * * Hooked to load-widgets.php. * * @since 1.1.3 * @param string|null $widget_id Widget ID. * @return void */ public static function purge_widget( $widget_id = null ) { if ( null === $widget_id ) { if ( empty( $_POST['widget-id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing return; } $widget_id = sanitize_text_field( wp_unslash( $_POST['widget-id'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( '' === $widget_id ) { return; } } self::add( Tag::TYPE_WIDGET . $widget_id ); self::add_private( Tag::TYPE_WIDGET . $widget_id ); do_action( 'litespeed_purged_widget', $widget_id ); } /** * Purges the comment widget when the count is updated. * * @since 1.1.3 * @global \WP_Widget_Factory $wp_widget_factory * @return void */ public static function purge_comment_widget() { global $wp_widget_factory; if ( ! isset( $wp_widget_factory->widgets['WP_Widget_Recent_Comments'] ) ) { return; } $recent_comments = $wp_widget_factory->widgets['WP_Widget_Recent_Comments']; if ( null !== $recent_comments ) { self::add( Tag::TYPE_WIDGET . $recent_comments->id ); self::add_private( Tag::TYPE_WIDGET . $recent_comments->id ); do_action( 'litespeed_purged_comment_widget', $recent_comments->id ); } } /** * Purges feeds on comment count update. * * @since 1.0.9 * @return void */ public function purge_feeds() { if ( $this->conf( self::O_CACHE_TTL_FEED ) > 0 ) { self::add( Tag::TYPE_FEED ); } do_action( 'litespeed_purged_feeds' ); } /** * Purges all private cache entries when the user logs out. * * @since 1.1.3 * @return void */ public static function purge_on_logout() { self::add_private_all(); do_action( 'litespeed_purged_on_logout' ); } /** * Finalize purge tags before output. * * @since 1.1.3 * @return void */ private function _finalize() { if ( ! defined( 'LITESPEED_DID_' . __FUNCTION__ ) ) { define( 'LITESPEED_DID_' . __FUNCTION__, true ); } else { return; } do_action( 'litespeed_purge_finalize' ); // Append unique uri purge tags if Admin QS is `PURGESINGLE` or `PURGE`. if ( $this->_purge_single ) { $tags = [ Tag::build_uri_tag() ]; $this->_pub_purge = array_merge( $this->_pub_purge, $this->_prepend_bid( $tags ) ); } if ( ! empty( $this->_pub_purge ) ) { $this->_pub_purge = array_unique( $this->_pub_purge ); } if ( ! empty( $this->_priv_purge ) ) { $this->_priv_purge = array_unique( $this->_priv_purge ); } } /** * Gather and return purge header string. * * @since 1.1.0 * @return string Purge header line. */ public static function output() { $instance = self::cls(); $instance->_finalize(); return $instance->_build(); } /** * Build the current purge header(s). * * @since 1.1.5 * @param bool $purge2 Whether to build X-LiteSpeed-Purge2. * @return string Purge header line. */ private function _build( $purge2 = false ) { if ( $purge2 ) { if ( empty( $this->_pub_purge2 ) ) { return ''; } } elseif ( empty( $this->_pub_purge ) && empty( $this->_priv_purge ) ) { return ''; } $purge_header = ''; $private_prefix = self::X_HEADER . ': private,'; // Handle purge2. if ( $purge2 ) { $public_tags = $this->_append_prefix( $this->_pub_purge2 ); if ( empty( $public_tags ) ) { return ''; } $purge_header = self::X_HEADER2 . ': public,'; if ( Control::is_stale() ) { $purge_header .= 'stale,'; } $purge_header .= implode( ',', $public_tags ); return $purge_header; } if ( ! empty( $this->_pub_purge ) ) { $public_tags = $this->_append_prefix( $this->_pub_purge ); if ( empty( $public_tags ) ) { return ''; // If this ends up empty, private will also end up empty } $purge_header = self::X_HEADER . ': public,'; if ( Control::is_stale() ) { $purge_header .= 'stale,'; } $purge_header .= implode( ',', $public_tags ); $private_prefix = ';private,'; } // Private purge tags. if ( ! empty( $this->_priv_purge ) ) { $private_tags = $this->_append_prefix( $this->_priv_purge, true ); $purge_header .= $private_prefix . implode( ',', $private_tags ); } return $purge_header; } /** * Append LS tag prefix to tags; handle '*' across network. * * @since 1.1.0 * @param array $purge_tags Tags. * @param bool $is_private Private tags. * @return array */ private function _append_prefix( $purge_tags, $is_private = false ) { $curr_bid = is_multisite() ? get_current_blog_id() : ''; $purge_tags = apply_filters( 'litespeed_purge_tags', $purge_tags, $is_private ); if ( ! in_array( '*', $purge_tags, true ) ) { $tags = []; foreach ( $purge_tags as $val ) { $tags[] = LSWCP_TAG_PREFIX . $val; } return $tags; } // Purge All: maybe reset crawler. if ( ! $is_private && $this->conf( self::O_CRAWLER ) ) { Crawler::cls()->reset_pos(); } if ( ( defined( 'LSWCP_EMPTYCACHE' ) && LSWCP_EMPTYCACHE ) || $is_private ) { return [ '*' ]; } if ( is_multisite() && ! $this->_is_subsite_purge() ) { $blogs = Activation::get_network_ids(); if ( empty( $blogs ) ) { self::debug( 'build_purge_headers: blog list is empty' ); return []; } $tags = []; foreach ( $blogs as $blog_id ) { $tags[] = LSWCP_TAG_PREFIX . $blog_id . '_'; } return $tags; } return [ LSWCP_TAG_PREFIX . $curr_bid . '_' ]; } /** * Check if this is a subsite purge in multisite. * * @since 4.0 * @return bool */ private function _is_subsite_purge() { if ( ! is_multisite() ) { return false; } if ( is_network_admin() ) { return false; } if ( defined( 'LSWCP_EMPTYCACHE' ) && LSWCP_EMPTYCACHE ) { return false; } // Ajax network contexts. if ( Router::is_ajax() && ( check_ajax_referer( 'updates', false, false ) || check_ajax_referer( 'litespeed-purgeall-network', false, false ) ) ) { return false; } return true; } /** * Get purge tags related to a post. * * @since 1.0.0 * @param int $post_id Post ID. * @return array */ private function _get_purge_tags_by_post( $post_id ) { if ( $this->conf( self::O_PURGE_POST_ALL ) ) { return [ '*' ]; } do_action( 'litespeed_api_purge_post', $post_id ); $purge_tags = []; // Post itself. $purge_tags[] = Tag::TYPE_POST . $post_id; $post_status = get_post_status( $post_id ); if ( function_exists( 'is_post_status_viewable' ) && is_post_status_viewable( $post_status ) ) { $purge_tags[] = Tag::get_uri_tag( wp_make_link_relative( get_permalink( $post_id ) ) ); } // Avoid overriding global $post: use explicit post object. $the_post = get_post( $post_id ); $post_type = $the_post ? $the_post->post_type : ''; // Widgets: recent posts. global $wp_widget_factory; $recent_posts = isset( $wp_widget_factory->widgets['WP_Widget_Recent_Posts'] ) ? $wp_widget_factory->widgets['WP_Widget_Recent_Posts'] : null; if ( null !== $recent_posts ) { $purge_tags[] = Tag::TYPE_WIDGET . $recent_posts->id; } // get adjacent posts id as related post tag if ( 'post' === $post_type ) { $prev_post = get_previous_post(); $next_post = get_next_post(); if ( ! empty( $prev_post->ID ) ) { $purge_tags[] = Tag::TYPE_POST . $prev_post->ID; self::debug( '--------purge_tags prev is: ' . $prev_post->ID ); } if ( ! empty( $next_post->ID ) ) { $purge_tags[] = Tag::TYPE_POST . $next_post->ID; self::debug( '--------purge_tags next is: ' . $next_post->ID ); } } if ( $this->conf( self::O_PURGE_POST_TERM ) ) { $taxonomies = get_object_taxonomies( $post_type ); // self::debug('purge by post, check tax = ' . var_export($taxonomies, true)); foreach ( $taxonomies as $tax ) { $terms = get_the_terms( $post_id, $tax ); if ( ! empty( $terms ) ) { foreach ( $terms as $term ) { $purge_tags[] = Tag::TYPE_ARCHIVE_TERM . $term->term_id; } } } } if ( $this->conf( self::O_CACHE_TTL_FEED ) ) { $purge_tags[] = Tag::TYPE_FEED; } // Author archives. if ( $this->conf( self::O_PURGE_POST_AUTHOR ) ) { $purge_tags[] = Tag::TYPE_AUTHOR . get_post_field( 'post_author', $post_id ); } // Post type archives. if ( $this->conf( self::O_PURGE_POST_POSTTYPE ) && get_post_type_archive_link( $post_type ) ) { $purge_tags[] = Tag::TYPE_ARCHIVE_POSTTYPE . $post_type; $purge_tags[] = $post_type; } if ( $this->conf( self::O_PURGE_POST_FRONTPAGE ) ) { $purge_tags[] = Tag::TYPE_FRONTPAGE; } if ( $this->conf( self::O_PURGE_POST_HOMEPAGE ) ) { $purge_tags[] = Tag::TYPE_HOME; } if ( $this->conf( self::O_PURGE_POST_PAGES ) ) { $purge_tags[] = Tag::TYPE_PAGES; } if ( $this->conf( self::O_PURGE_POST_PAGES_WITH_RECENT_POSTS ) ) { $purge_tags[] = Tag::TYPE_PAGES_WITH_RECENT_POSTS; } // Date archives (use gmdate as per WPCS). $date_gmt = $the_post ? strtotime( $the_post->post_date_gmt ) : false; if ( $date_gmt ) { if ( $this->conf( self::O_PURGE_POST_DATE ) ) { $purge_tags[] = Tag::TYPE_ARCHIVE_DATE . gmdate( 'Ymd', $date_gmt ); } if ( $this->conf( self::O_PURGE_POST_MONTH ) ) { $purge_tags[] = Tag::TYPE_ARCHIVE_DATE . gmdate( 'Ym', $date_gmt ); } if ( $this->conf( self::O_PURGE_POST_YEAR ) ) { $purge_tags[] = Tag::TYPE_ARCHIVE_DATE . gmdate( 'Y', $date_gmt ); } } return array_unique( array_filter( $purge_tags ) ); } /** * Run a filter and also purge all (utility for hooks). * * @since 1.1.5 * @param string $val Filter value. * @return string Same value. */ public static function filter_with_purge_all( $val ) { self::purge_all(); return $val; } } src/esi.cls.php000064400000066272152075713340007430 0ustar00 '' ); // val is cache control const QS_ACTION = 'lsesi'; const QS_PARAMS = 'esi'; const COMBO = '__combo'; // ESI include combine='main' handler const PARAM_ARGS = 'args'; const PARAM_ID = 'id'; const PARAM_INSTANCE = 'instance'; const PARAM_NAME = 'name'; const WIDGET_O_ESIENABLE = 'widget_esi_enable'; const WIDGET_O_TTL = 'widget_ttl'; /** * Confructor of ESI * * @since 1.2.0 * @since 4.0 Change to be after Vary init in hook 'after_setup_theme' */ public function init() { /** * Bypass ESI related funcs if disabled ESI to fix potential DIVI compatibility issue * * @since 2.9.7.2 */ if (Router::is_ajax() || !$this->cls('Router')->esi_enabled()) { return; } // Guest mode, don't need to use ESI if (defined('LITESPEED_GUEST') && LITESPEED_GUEST) { return; } if (defined('LITESPEED_ESI_OFF')) { return; } // If page is not cacheable if (defined('DONOTCACHEPAGE') && apply_filters('litespeed_const_DONOTCACHEPAGE', DONOTCACHEPAGE)) { return; } // Init ESI in `after_setup_theme` hook after detected if LITESPEED_DISABLE_ALL is ON or not $this->_hooks(); /** * Overwrite wp_create_nonce func * * @since 2.9.5 */ $this->_transform_nonce(); !defined('LITESPEED_ESI_INITED') && define('LITESPEED_ESI_INITED', true); } /** * Init ESI related hooks * * Load delayed by hook to give the ability to bypass by LITESPEED_DISABLE_ALL const * * @since 2.9.7.2 * @since 4.0 Changed to private from public * @access private */ private function _hooks() { add_filter('template_include', array( $this, 'esi_template' ), 99999); add_action('load-widgets.php', __NAMESPACE__ . '\Purge::purge_widget'); add_action('wp_update_comment_count', __NAMESPACE__ . '\Purge::purge_comment_widget'); /** * Recover REQUEST_URI * * @since 1.8.1 */ if (!empty($_GET[self::QS_ACTION])) { self::debug('ESI req'); $this->_register_esi_actions(); } /** * Shortcode ESI * * To use it, just change the original shortcode as below: * old: [someshortcode aa='bb'] * new: [esi someshortcode aa='bb' cache='private,no-vary' ttl='600'] * * 1. `cache` attribute is optional, default to 'public,no-vary'. * 2. `ttl` attribute is optional, default is your public TTL setting. * 3. `_ls_silence` attribute is optional, default is false. * * @since 2.8 * @since 2.8.1 Check is_admin for Elementor compatibility #726013 */ if (!is_admin()) { add_shortcode('esi', array( $this, 'shortcode' )); } } /** * Take over all nonce calls and transform to ESI * * @since 2.9.5 */ private function _transform_nonce() { if (is_admin()) { return; } // Load ESI nonces in conf $nonces = $this->conf(Base::O_ESI_NONCE); add_filter('litespeed_esi_nonces', array( $this->cls('Data'), 'load_esi_nonces' )); if ($nonces = apply_filters('litespeed_esi_nonces', $nonces)) { foreach ($nonces as $action) { $this->nonce_action($action); } } add_action('litespeed_nonce', array( $this, 'nonce_action' )); } /** * Register a new nonce action to convert it to ESI * * @since 2.9.5 */ public function nonce_action( $action ) { // Split the Cache Control $action = explode(' ', $action); $control = !empty($action[1]) ? $action[1] : ''; $action = $action[0]; // Wildcard supported $action = Utility::wildcard2regex($action); if (array_key_exists($action, $this->_nonce_actions)) { return; } $this->_nonce_actions[$action] = $control; // Debug2::debug('[ESI] Appended nonce action to nonce list [action] ' . $action); } /** * Check if an action is registered to replace ESI * * @since 2.9.5 */ public function is_nonce_action( $action ) { // If GM not run yet, then ESI not init yet, then ESI nonce will not be allowed even nonce func replaced. if (!defined('LITESPEED_ESI_INITED')) { return null; } if (is_admin()) { return null; } if (defined('LITESPEED_ESI_OFF')) { return null; } foreach ($this->_nonce_actions as $k => $v) { if (strpos($k, '*') !== false) { if (preg_match('#' . $k . '#iU', $action)) { return $v; } } elseif ($k == $action) { return $v; } } return null; } /** * Shortcode ESI * * @since 2.8 * @access public */ public function shortcode( $atts ) { if (empty($atts[0])) { Debug2::debug('[ESI] ===shortcode wrong format', $atts); return 'Wrong shortcode esi format'; } $cache = 'public,no-vary'; if (!empty($atts['cache'])) { $cache = $atts['cache']; unset($atts['cache']); } $silence = false; if (!empty($atts['_ls_silence'])) { $silence = true; } do_action('litespeed_esi_shortcode-' . $atts[0]); // Show ESI link return $this->sub_esi_block('esi', 'esi-shortcode', $atts, $cache, $silence); } /** * Check if the requested page has esi elements. If so, return esi on * header. * * @since 1.1.3 * @access public * @return string Esi On header if request has esi, empty string otherwise. */ public static function has_esi() { return self::$has_esi; } /** * Sets that the requested page has esi elements. * * @since 1.1.3 * @access public */ public static function set_has_esi() { self::$has_esi = true; } /** * Register all of the hooks related to the esi logic of the plugin. * Specifically when the page IS an esi page. * * @since 1.1.3 * @access private */ private function _register_esi_actions() { /** * This hook is in `init` * For any plugin need to check if page is ESI, use `LSCACHE_IS_ESI` check after `init` hook */ !defined('LSCACHE_IS_ESI') && define('LSCACHE_IS_ESI', $_GET[self::QS_ACTION]); // Reused this to ESI block ID !empty($_SERVER['ESI_REFERER']) && defined('LSCWP_LOG') && Debug2::debug('[ESI] ESI_REFERER: ' . $_SERVER['ESI_REFERER']); /** * Only when ESI's parent is not REST, replace REQUEST_URI to avoid breaking WP5 editor REST call * * @since 2.9.3 */ if (!empty($_SERVER['ESI_REFERER']) && !$this->cls('REST')->is_rest($_SERVER['ESI_REFERER'])) { self::debug('overwrite REQUEST_URI to ESI_REFERER [from] ' . $_SERVER['REQUEST_URI'] . ' [to] ' . $_SERVER['ESI_REFERER']); if (!empty($_SERVER['ESI_REFERER'])) { $_SERVER['REQUEST_URI'] = $_SERVER['ESI_REFERER']; if (substr(get_option('permalink_structure'), -1) === '/' && strpos($_SERVER['ESI_REFERER'], '?') === false) { $_SERVER['REQUEST_URI'] = trailingslashit($_SERVER['ESI_REFERER']); } } // Prevent from 301 redirecting if (!empty($_SERVER['SCRIPT_URI'])) { $SCRIPT_URI = parse_url($_SERVER['SCRIPT_URI']); $SCRIPT_URI['path'] = $_SERVER['REQUEST_URI']; Utility::compatibility(); $_SERVER['SCRIPT_URI'] = http_build_url($SCRIPT_URI); } } if (!empty($_SERVER['ESI_CONTENT_TYPE']) && strpos($_SERVER['ESI_CONTENT_TYPE'], 'application/json') === 0) { add_filter('litespeed_is_json', '__return_true'); } /** * Make REST call be able to parse ESI * NOTE: Not effective due to ESI req are all to `/` yet * * @since 2.9.4 */ add_action('rest_api_init', array( $this, 'load_esi_block' ), 101); // Register ESI blocks add_action('litespeed_esi_load-widget', array( $this, 'load_widget_block' )); add_action('litespeed_esi_load-admin-bar', array( $this, 'load_admin_bar_block' )); add_action('litespeed_esi_load-comment-form', array( $this, 'load_comment_form_block' )); add_action('litespeed_esi_load-nonce', array( $this, 'load_nonce_block' )); add_action('litespeed_esi_load-esi', array( $this, 'load_esi_shortcode' )); add_action('litespeed_esi_load-' . self::COMBO, array( $this, 'load_combo' )); } /** * Hooked to the template_include action. * Selects the esi template file when the post type is a LiteSpeed ESI page. * * @since 1.1.3 * @access public * @param string $template The template path filtered. * @return string The new template path. */ public function esi_template( $template ) { // Check if is an ESI request if (defined('LSCACHE_IS_ESI')) { self::debug('calling ESI template'); return LSCWP_DIR . 'tpl/esi.tpl.php'; } self::debug('calling default template'); $this->_register_not_esi_actions(); return $template; } /** * Register all of the hooks related to the esi logic of the plugin. * Specifically when the page is NOT an esi page. * * @since 1.1.3 * @access private */ private function _register_not_esi_actions() { do_action('litespeed_tpl_normal'); if (!Control::is_cacheable()) { return; } if (Router::is_ajax()) { return; } add_filter('widget_display_callback', array( $this, 'sub_widget_block' ), 0, 3); // Add admin_bar esi if (Router::is_logged_in()) { remove_action('wp_body_open', 'wp_admin_bar_render', 0); // Remove default Admin bar. Fix https://github.com/elementor/elementor/issues/25198 remove_action('wp_footer', 'wp_admin_bar_render', 1000); add_action('wp_footer', array( $this, 'sub_admin_bar_block' ), 1000); } // Add comment forum esi for logged-in user or commenter if (!Router::is_ajax() && Vary::has_vary()) { add_filter('comment_form_defaults', array( $this, 'register_comment_form_actions' )); } } /** * Set an ESI to be combine='sub' * * @since 3.4.2 */ public static function combine( $block_id ) { if (!isset($_SERVER['X-LSCACHE']) || strpos($_SERVER['X-LSCACHE'], 'combine') === false) { return; } if (in_array($block_id, self::$_combine_ids)) { return; } self::$_combine_ids[] = $block_id; } /** * Load combined ESI * * @since 3.4.2 */ public function load_combo() { Control::set_nocache('ESI combine request'); if (empty($_POST['esi_include'])) { return; } self::set_has_esi(); Debug2::debug('[ESI] 🍔 Load combo', $_POST['esi_include']); $output = ''; foreach ($_POST['esi_include'] as $url) { $qs = parse_url(htmlspecialchars_decode($url), PHP_URL_QUERY); parse_str($qs, $qs); if (empty($qs[self::QS_ACTION])) { continue; } $esi_id = $qs[self::QS_ACTION]; $esi_param = !empty($qs[self::QS_PARAMS]) ? $this->_parse_esi_param($qs[self::QS_PARAMS]) : false; $inline_param = apply_filters('litespeed_esi_inline-' . $esi_id, array(), $esi_param); // Returned array need to be [ val, control, tag ] if ($inline_param) { $output .= self::_build_inline($url, $inline_param); } } echo $output; } /** * Build a whole inline segment * * @since 3.4.2 */ private static function _build_inline( $url, $inline_param ) { if (!$url || empty($inline_param['val']) || empty($inline_param['control']) || empty($inline_param['tag'])) { return ''; } $url = esc_attr($url); $control = esc_attr($inline_param['control']); $tag = esc_attr($inline_param['tag']); return "" . $inline_param['val'] . ''; } /** * Build the esi url. This method will build the html comment wrapper as well as serialize and encode the parameter array. * * The block_id parameter should contain alphanumeric and '-_' only. * * @since 1.1.3 * @access private * @param string $block_id The id to use to display the correct esi block. * @param string $wrapper The wrapper for the esi comments. * @param array $params The esi parameters. * @param string $control The cache control attribute if any. * @param bool $silence If generate wrapper comment or not * @param bool $preserved If this ESI block is used in any filter, need to temporarily convert it to a string to avoid the HTML tag being removed/filtered. * @param bool $svar If store the value in memory or not, in memory will be faster * @param array $inline_param If show the current value for current request( this can avoid multiple esi requests in first time cache generating process ) */ public function sub_esi_block( $block_id, $wrapper, $params = array(), $control = 'private,no-vary', $silence = false, $preserved = false, $svar = false, $inline_param = array() ) { if (empty($block_id) || !is_array($params) || preg_match('/[^\w-]/', $block_id)) { return false; } if (defined('LITESPEED_ESI_OFF')) { Debug2::debug('[ESI] ESI OFF so force loading [block_id] ' . $block_id); do_action('litespeed_esi_load-' . $block_id, $params); return; } if ($silence) { // Don't add comment to esi block ( original for nonce used in tag property data-nonce='esi_block' ) $params['_ls_silence'] = true; } if ($this->cls('REST')->is_rest() || $this->cls('REST')->is_internal_rest()) { $params['is_json'] = 1; } $params = apply_filters('litespeed_esi_params', $params, $block_id); $control = apply_filters('litespeed_esi_control', $control, $block_id); if (!is_array($params) || !is_string($control)) { defined('LSCWP_LOG') && Debug2::debug("[ESI] 🛑 Sub hooks returned Params: \n" . var_export($params, true) . "\ncache control: \n" . var_export($control, true)); return false; } // Build params for URL $appended_params = array( self::QS_ACTION => $block_id, ); if (!empty($control)) { $appended_params['_control'] = $control; } if ($params) { $appended_params[self::QS_PARAMS] = base64_encode(\json_encode($params)); Debug2::debug2('[ESI] param ', $params); } // Append hash $appended_params['_hash'] = $this->_gen_esi_md5($appended_params); /** * Escape potential chars * * @since 2.9.4 */ $appended_params = array_map('urlencode', $appended_params); // Generate ESI URL $url = add_query_arg($appended_params, trailingslashit(wp_make_link_relative(home_url()))); $output = ''; if ($inline_param) { $output .= self::_build_inline($url, $inline_param); } $output .= "_esi_preserve_list[$hash] = $output; self::debug("Preserved to $hash"); return $hash; } return $output; } /** * Generate ESI hash md5 * * @since 2.9.6 * @access private */ private function _gen_esi_md5( $params ) { $keys = array( self::QS_ACTION, '_control', self::QS_PARAMS ); $str = ''; foreach ($keys as $v) { if (isset($params[$v]) && is_string($params[$v])) { $str .= $params[$v]; } } Debug2::debug2('[ESI] md5_string=' . $str); return md5($this->conf(Base::HASH) . $str); } /** * Parses the request parameters on an ESI request * * @since 1.1.3 * @access private */ private function _parse_esi_param( $qs_params = false ) { $req_params = false; if ($qs_params) { $req_params = $qs_params; } elseif (isset($_REQUEST[self::QS_PARAMS])) { $req_params = $_REQUEST[self::QS_PARAMS]; } if (!$req_params) { return false; } $unencrypted = base64_decode($req_params); if ($unencrypted === false) { return false; } Debug2::debug2('[ESI] params', $unencrypted); // $unencoded = urldecode($unencrypted); no need to do this as $_GET is already parsed $params = \json_decode($unencrypted, true); return $params; } /** * Select the correct esi output based on the parameters in an ESI request. * * @since 1.1.3 * @access public */ public function load_esi_block() { /** * Validate if is a legal ESI req * * @since 2.9.6 */ if (empty($_GET['_hash']) || $this->_gen_esi_md5($_GET) != $_GET['_hash']) { Debug2::debug('[ESI] ❌ Failed to validate _hash'); return; } $params = $this->_parse_esi_param(); if (defined('LSCWP_LOG')) { $logInfo = '[ESI] ⭕ '; if (!empty($params[self::PARAM_NAME])) { $logInfo .= ' Name: ' . $params[self::PARAM_NAME] . ' ----- '; } $logInfo .= ' [ID] ' . LSCACHE_IS_ESI; Debug2::debug($logInfo); } if (!empty($params['_ls_silence'])) { !defined('LSCACHE_ESI_SILENCE') && define('LSCACHE_ESI_SILENCE', true); } /** * Buffer needs to be JSON format * * @since 2.9.4 */ if (!empty($params['is_json'])) { add_filter('litespeed_is_json', '__return_true'); } Tag::add(rtrim(Tag::TYPE_ESI, '.')); Tag::add(Tag::TYPE_ESI . LSCACHE_IS_ESI); // Debug2::debug(var_export($params, true )); /** * Handle default cache control 'private,no-vary' for sub_esi_block() @ticket #923505 * * @since 2.2.3 */ if (!empty($_GET['_control'])) { $control = explode(',', $_GET['_control']); if (in_array('private', $control)) { Control::set_private(); } if (in_array('no-vary', $control)) { Control::set_no_vary(); } } do_action('litespeed_esi_load-' . LSCACHE_IS_ESI, $params); } // The *_sub_* functions are helpers for the sub_* functions. // The *_load_* functions are helpers for the load_* functions. /** * Loads the default options for default WordPress widgets. * * @since 1.1.3 * @access public */ public static function widget_default_options( $options, $widget ) { if (!is_array($options)) { return $options; } $widget_name = get_class($widget); switch ($widget_name) { case 'WP_Widget_Recent_Posts': case 'WP_Widget_Recent_Comments': $options[self::WIDGET_O_ESIENABLE] = Base::VAL_OFF; $options[self::WIDGET_O_TTL] = 86400; break; default: break; } return $options; } /** * Hooked to the widget_display_callback filter. * If the admin configured the widget to display via esi, this function * will set up the esi request and cancel the widget display. * * @since 1.1.3 * @access public * @param array $instance Parameter used to build the widget. * @param \WP_Widget $widget The widget to build. * @param array $args Parameter used to build the widget. * @return mixed Return false if display through esi, instance otherwise. */ public function sub_widget_block( $instance, $widget, $args ) { // #210407 if (!is_array($instance)) { return $instance; } $name = get_class($widget); if (!isset($instance[Base::OPTION_NAME])) { return $instance; } $options = $instance[Base::OPTION_NAME]; if (!isset($options) || !$options[self::WIDGET_O_ESIENABLE]) { defined('LSCWP_LOG') && Debug2::debug('ESI 0 ' . $name . ': ' . (!isset($options) ? 'not set' : 'set off')); return $instance; } $esi_private = $options[self::WIDGET_O_ESIENABLE] == Base::VAL_ON2 ? 'private,' : ''; $params = array( self::PARAM_NAME => $name, self::PARAM_ID => $widget->id, self::PARAM_INSTANCE => $instance, self::PARAM_ARGS => $args, ); echo $this->sub_esi_block('widget', 'widget ' . $name, $params, $esi_private . 'no-vary'); return false; } /** * Hooked to the wp_footer action. * Sets up the ESI request for the admin bar. * * @access public * @since 1.1.3 * @global type $wp_admin_bar */ public function sub_admin_bar_block() { global $wp_admin_bar; if (!is_admin_bar_showing() || !is_object($wp_admin_bar)) { return; } // To make each admin bar ESI request different for `Edit` button different link $params = array( 'ref' => $_SERVER['REQUEST_URI'], ); echo $this->sub_esi_block('admin-bar', 'adminbar', $params); } /** * Parses the esi input parameters and generates the widget for esi display. * * @access public * @since 1.1.3 * @global $wp_widget_factory * @param array $params Input parameters needed to correctly display widget */ public function load_widget_block( $params ) { // global $wp_widget_factory; // $widget = $wp_widget_factory->widgets[ $params[ self::PARAM_NAME ] ]; $option = $params[self::PARAM_INSTANCE]; $option = $option[Base::OPTION_NAME]; // Since we only reach here via esi, safe to assume setting exists. $ttl = $option[self::WIDGET_O_TTL]; defined('LSCWP_LOG') && Debug2::debug('ESI widget render: name ' . $params[self::PARAM_NAME] . ', id ' . $params[self::PARAM_ID] . ', ttl ' . $ttl); if ($ttl == 0) { Control::set_nocache('ESI Widget time to live set to 0'); } else { Control::set_custom_ttl($ttl); if ($option[self::WIDGET_O_ESIENABLE] == Base::VAL_ON2) { Control::set_private(); } Control::set_no_vary(); Tag::add(Tag::TYPE_WIDGET . $params[self::PARAM_ID]); } the_widget($params[self::PARAM_NAME], $params[self::PARAM_INSTANCE], $params[self::PARAM_ARGS]); } /** * Generates the admin bar for esi display. * * @access public * @since 1.1.3 */ public function load_admin_bar_block( $params ) { global $wp_the_query; if (!empty($params['ref'])) { $ref_qs = parse_url($params['ref'], PHP_URL_QUERY); if (!empty($ref_qs)) { parse_str($ref_qs, $ref_qs_arr); if (!empty($ref_qs_arr)) { foreach ($ref_qs_arr as $k => $v) { $_GET[$k] = $v; } } } } // Needed when permalink structure is "Plain" if (!isset($wp_the_query)) { wp(); } wp_admin_bar_render(); if (!$this->conf(Base::O_ESI_CACHE_ADMBAR)) { Control::set_nocache('build-in set to not cacheable'); } else { Control::set_private(); Control::set_no_vary(); } defined('LSCWP_LOG') && Debug2::debug('ESI: adminbar ref: ' . $_SERVER['REQUEST_URI']); } /** * Parses the esi input parameters and generates the comment form for esi display. * * @access public * @since 1.1.3 * @param array $params Input parameters needed to correctly display comment form */ public function load_comment_form_block( $params ) { comment_form($params[self::PARAM_ARGS], $params[self::PARAM_ID]); if (!$this->conf(Base::O_ESI_CACHE_COMMFORM)) { Control::set_nocache('build-in set to not cacheable'); } else { // by default comment form is public if (Vary::has_vary()) { Control::set_private(); Control::set_no_vary(); } } } /** * Generate nonce for certain action * * @access public * @since 2.6 */ public function load_nonce_block( $params ) { $action = $params['action']; Debug2::debug('[ESI] load_nonce_block [action] ' . $action); // set nonce TTL to half day Control::set_custom_ttl(43200); if (Router::is_logged_in()) { Control::set_private(); } if (function_exists('wp_create_nonce_litespeed_esi')) { echo wp_create_nonce_litespeed_esi($action); } else { echo wp_create_nonce($action); } } /** * Show original shortcode * * @access public * @since 2.8 */ public function load_esi_shortcode( $params ) { if (isset($params['ttl'])) { if (!$params['ttl']) { Control::set_nocache('ESI shortcode att ttl=0'); } else { Control::set_custom_ttl($params['ttl']); } unset($params['ttl']); } // Replace to original shortcode $shortcode = $params[0]; $atts_ori = array(); foreach ($params as $k => $v) { if ($k === 0) { continue; } $atts_ori[] = is_string($k) ? "$k='" . addslashes($v) . "'" : $v; } Tag::add(Tag::TYPE_ESI . "esi.$shortcode"); // Output original shortcode final content echo do_shortcode("[$shortcode " . implode(' ', $atts_ori) . ' ]'); } /** * Hooked to the comment_form_defaults filter. * Stores the default comment form settings. * If sub_comment_form_block is triggered, the output buffer is cleared and an esi block is added. The remaining comment form is also buffered and cleared. * Else there is no need to make the comment form ESI. * * @since 1.1.3 * @access public */ public function register_comment_form_actions( $defaults ) { $this->esi_args = $defaults; echo GUI::clean_wrapper_begin(); add_filter('comment_form_submit_button', array( $this, 'sub_comment_form_btn' ), 1000, 2); // To save the params passed in add_action('comment_form', array( $this, 'sub_comment_form_block' ), 1000); return $defaults; } /** * Store the args passed in comment_form for the ESI comment param usage in `$this->sub_comment_form_block()` * * @since 3.4 * @access public */ public function sub_comment_form_btn( $unused, $args ) { if (empty($args) || empty($this->esi_args)) { Debug2::debug('comment form args empty?'); return $unused; } $esi_args = array(); // compare current args with default ones foreach ($args as $k => $v) { if (!isset($this->esi_args[$k])) { $esi_args[$k] = $v; } elseif (is_array($v)) { $diff = array_diff_assoc($v, $this->esi_args[$k]); if (!empty($diff)) { $esi_args[$k] = $diff; } } elseif ($v !== $this->esi_args[$k]) { $esi_args[$k] = $v; } } $this->esi_args = $esi_args; return $unused; } /** * Hooked to the comment_form_submit_button filter. * * This method will compare the used comment form args against the default args. The difference will be passed to the esi request. * * @access public * @since 1.1.3 */ public function sub_comment_form_block( $post_id ) { echo GUI::clean_wrapper_end(); $params = array( self::PARAM_ID => $post_id, self::PARAM_ARGS => $this->esi_args, ); echo $this->sub_esi_block('comment-form', 'comment form', $params); echo GUI::clean_wrapper_begin(); add_action('comment_form_after', array( $this, 'comment_form_sub_clean' )); } /** * Hooked to the comment_form_after action. * Cleans up the remaining comment form output. * * @since 1.1.3 * @access public */ public function comment_form_sub_clean() { echo GUI::clean_wrapper_end(); } /** * Replace preserved blocks * * @since 2.6 * @access public */ public function finalize( $buffer ) { // Prepend combo esi block if (self::$_combine_ids) { Debug2::debug('[ESI] 🍔 Enabled combo'); $esi_block = $this->sub_esi_block(self::COMBO, '__COMBINE_MAIN__', array(), 'no-cache', true); $buffer = $esi_block . $buffer; } // Bypass if no preserved list to be replaced if (!$this->_esi_preserve_list) { return $buffer; } $keys = array_keys($this->_esi_preserve_list); Debug2::debug('[ESI] replacing preserved blocks', $keys); $buffer = str_replace($keys, $this->_esi_preserve_list, $buffer); return $buffer; } /** * Check if the content contains preserved list or not * * @since 3.3 */ public function contain_preserve_esi( $content ) { $hit_list = array(); foreach ($this->_esi_preserve_list as $k => $v) { if (strpos($content, '"' . $k . '"') !== false) { $hit_list[] = '"' . $k . '"'; } if (strpos($content, "'" . $k . "'") !== false) { $hit_list[] = "'" . $k . "'"; } } return $hit_list; } } src/crawler-map.cls.php000064400000046642152075713340011061 0ustar00 */ private $_urls = []; /** * Instantiate the class. * * @since 1.1.0 */ public function __construct() { $this->_site_url = get_site_url(); $this->__data = Data::cls(); $this->_tb = $this->__data->tb( 'crawler' ); $this->_tb_blacklist = $this->__data->tb( 'crawler_blacklist' ); // Specify the timeout while parsing the sitemap. $this->_conf_map_timeout = defined( 'LITESPEED_CRAWLER_MAP_TIMEOUT' ) ? constant( 'LITESPEED_CRAWLER_MAP_TIMEOUT' ) : 180; } /** * Save URLs crawl status into DB. * * @since 3.0 * @access public * * @param array> $items Map of bit => [ id => [url, code] ]. * @param int $curr_crawler Current crawler index (0-based). * @return array */ public function save_map_status( $items, $curr_crawler ) { global $wpdb; Utility::compatibility(); $total_crawler = count( Crawler::cls()->list_crawlers() ); $total_crawler_pos = $total_crawler - 1; // Replace current crawler's position. $curr_crawler = (int) $curr_crawler; foreach ( $items as $bit => $ids ) { // $ids = [ id => [ url, code ], ... ]. if ( ! $ids ) { continue; } self::debug( 'Update map [crawler] ' . $curr_crawler . ' [bit] ' . $bit . ' [count] ' . count( $ids ) ); // Update res first, then reason $right_pos = $total_crawler_pos - $curr_crawler; $id_all = implode(',', array_map('intval', array_keys($ids))); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $wpdb->query("UPDATE `$this->_tb` SET res = CONCAT( LEFT( res, $curr_crawler ), '$bit', RIGHT( res, $right_pos ) ) WHERE id IN ( $id_all )"); // Add blacklist if (Crawler::STATUS_BLACKLIST === $bit || Crawler::STATUS_NOCACHE === $bit) { $q = "SELECT a.id, a.url FROM `$this->_tb_blacklist` a LEFT JOIN `$this->_tb` b ON b.url=a.url WHERE b.id IN ( $id_all )"; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared $existing = $wpdb->get_results($q, ARRAY_A); // Update current crawler status tag in existing blacklist if ($existing) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared $count = $wpdb->query("UPDATE `$this->_tb_blacklist` SET res = CONCAT( LEFT( res, $curr_crawler ), '$bit', RIGHT( res, $right_pos ) ) WHERE id IN ( " . implode(',', array_column($existing, 'id')) . ' )'); self::debug('Update blacklist [count] ' . $count); } // Append new blacklist if (count($ids) > count($existing)) { $new_urls = array_diff(array_column($ids, 'url'), array_column($existing, 'url')); self::debug('Insert into blacklist [count] ' . count($new_urls)); $q = "INSERT INTO `$this->_tb_blacklist` ( url, res, reason ) VALUES " . implode(',', array_fill(0, count($new_urls), '( %s, %s, %s )')); $data = []; $res = array_fill(0, $total_crawler, '-'); $res[$curr_crawler] = $bit; $res = implode('', $res); $default_reason = $total_crawler > 1 ? str_repeat(',', $total_crawler - 1) : ''; // Pre-populate default reason value first, update later foreach ($new_urls as $url) { $data[] = $url; $data[] = $res; $data[] = $default_reason; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared $wpdb->query($wpdb->prepare($q, $data)); } } // Update sitemap reason w/ HTTP code. $reason_array = []; foreach ( $ids as $row_id => $row ) { $code = (int) $row['code']; if ( empty( $reason_array[ $code ] ) ) { $reason_array[ $code ] = []; } $reason_array[ $code ][] = (int) $row_id; } foreach ($reason_array as $code => $v2) { // Complement comma if ($curr_crawler) { $code = ',' . $code; } if ($curr_crawler < $total_crawler_pos) { $code .= ','; } // phpcs:ignore WordPress.DB $count = $wpdb->query( "UPDATE `$this->_tb` SET reason=CONCAT(SUBSTRING_INDEX(reason, ',', $curr_crawler), '$code', SUBSTRING_INDEX(reason, ',', -$right_pos)) WHERE id IN (" . implode(',', $v2) . ')' ); self::debug("Update map reason [code] $code [pos] left $curr_crawler right -$right_pos [count] $count"); // Update blacklist reason if (Crawler::STATUS_BLACKLIST === $bit || Crawler::STATUS_NOCACHE === $bit) { // phpcs:ignore WordPress.DB $count = $wpdb->query( "UPDATE `$this->_tb_blacklist` a LEFT JOIN `$this->_tb` b ON b.url = a.url SET a.reason=CONCAT(SUBSTRING_INDEX(a.reason, ',', $curr_crawler), '$code', SUBSTRING_INDEX(a.reason, ',', -$right_pos)) WHERE b.id IN (" . implode(',', $v2) . ')' ); self::debug("Update blacklist [code] $code [pos] left $curr_crawler right -$right_pos [count] $count"); } } // Reset list. $items[ $bit ] = []; } return $items; } /** * Add one record to blacklist. * NOTE: $id is sitemap table ID. * * @since 3.0 * @access public * * @param int $id Sitemap row ID. * @return void */ public function blacklist_add( $id ) { global $wpdb; $id = (int) $id; // Build res&reason. $total_crawler = count( Crawler::cls()->list_crawlers() ); $res = str_repeat(Crawler::STATUS_BLACKLIST, $total_crawler); $reason = implode(',', array_fill(0, $total_crawler, 'Man')); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $row = $wpdb->get_row("SELECT a.url, b.id FROM `$this->_tb` a LEFT JOIN `$this->_tb_blacklist` b ON b.url = a.url WHERE a.id = '$id'", ARRAY_A); if (!$row) { self::debug('blacklist failed to add [id] ' . $id); return; } self::debug('Add to blacklist [url] ' . $row['url']); $q = "UPDATE `$this->_tb` SET res = %s, reason = %s WHERE id = %d"; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared $wpdb->query($wpdb->prepare($q, [ $res, $reason, $id ])); if ($row['id']) { $q = "UPDATE `$this->_tb_blacklist` SET res = %s, reason = %s WHERE id = %d"; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared $wpdb->query($wpdb->prepare($q, [ $res, $reason, $row['id'] ])); } else { $q = "INSERT INTO `$this->_tb_blacklist` (url, res, reason) VALUES (%s, %s, %s)"; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared $wpdb->query($wpdb->prepare($q, [ $row['url'], $res, $reason ])); } } /** * Delete one record from blacklist. * * @since 3.0 * @access public * * @param int $id Blacklist row ID. * @return void */ public function blacklist_del( $id ) { global $wpdb; if ( ! $this->__data->tb_exist( 'crawler_blacklist' ) ) { return; } $id = (int) $id; self::debug('blacklist delete [id] ' . $id); $sql = sprintf( "UPDATE `%s` SET res=REPLACE(REPLACE(res, '%s', '-'), '%s', '-') WHERE url=(SELECT url FROM `%s` WHERE id=%d)", $this->_tb, Crawler::STATUS_NOCACHE, Crawler::STATUS_BLACKLIST, $this->_tb_blacklist, $id ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared $wpdb->query($sql); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $wpdb->query("DELETE FROM `$this->_tb_blacklist` WHERE id='$id'"); } /** * Empty blacklist. * * @since 3.0 * @access public * @return void */ public function blacklist_empty() { global $wpdb; if ( ! $this->__data->tb_exist( 'crawler_blacklist' ) ) { return; } self::debug('Truncate blacklist'); $sql = sprintf("UPDATE `%s` SET res=REPLACE(REPLACE(res, '%s', '-'), '%s', '-')", $this->_tb, Crawler::STATUS_NOCACHE, Crawler::STATUS_BLACKLIST); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared $wpdb->query($sql); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $wpdb->query("TRUNCATE `$this->_tb_blacklist`"); } /** * List blacklist. * * @since 3.0 * @access public * * @param int|false $limit Number of rows to fetch, or false for all. * @param int|false $offset Offset for pagination, or false to auto-calc. * @return array> */ public function list_blacklist( $limit = false, $offset = false ) { global $wpdb; if ( ! $this->__data->tb_exist( 'crawler_blacklist' ) ) { return []; } $q = "SELECT * FROM `$this->_tb_blacklist` ORDER BY id DESC"; if ( false !== $limit ) { if ( false === $offset ) { $total = $this->count_blacklist(); $offset = Utility::pagination($total, $limit, true); } $q .= ' LIMIT %d, %d'; // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $q = $wpdb->prepare($q, $offset, $limit); } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared return $wpdb->get_results($q, ARRAY_A); } /** * Count blacklist. * * @return int|false */ public function count_blacklist() { global $wpdb; if ( ! $this->__data->tb_exist( 'crawler_blacklist' ) ) { return false; } $q = "SELECT COUNT(*) FROM `$this->_tb_blacklist`"; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared return $wpdb->get_var($q); } /** * Empty sitemap. * * @since 3.0 * @access public * @return void */ public function empty_map() { Data::cls()->tb_del( 'crawler' ); $msg = __( 'Sitemap cleaned successfully', 'litespeed-cache' ); Admin_Display::success( $msg ); } /** * List generated sitemap. * * @since 3.0 * @access public * * @param int $limit Number of rows per page. * @param int|bool $offset Offset for pagination, or false to auto-calc. * @return array> */ public function list_map( $limit, $offset = false ) { global $wpdb; if ( ! $this->__data->tb_exist( 'crawler' ) ) { return []; } if ( false === $offset ) { $total = $this->count_map(); $offset = Utility::pagination($total, $limit, true); } $type = Router::verify_type(); $req_uri_like = ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( ! empty( $_POST['kw'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing $kw = sanitize_text_field( wp_unslash( $_POST['kw'] ) ); $q = "SELECT * FROM `$this->_tb` WHERE url LIKE %s"; if ( 'hit' === $type ) { $q .= " AND res LIKE '%" . Crawler::STATUS_HIT . "%'"; } if ( 'miss' === $type ) { $q .= " AND res LIKE '%" . Crawler::STATUS_MISS . "%'"; } if ( 'blacklisted' === $type ) { $q .= " AND res LIKE '%" . Crawler::STATUS_BLACKLIST . "%'"; } $q .= ' ORDER BY id LIMIT %d, %d'; $req_uri_like = '%' . $wpdb->esc_like( $kw ) . '%'; return $wpdb->get_results( $wpdb->prepare( $q, $req_uri_like, $offset, $limit ), ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared } $q = "SELECT * FROM `$this->_tb`"; if ( 'hit' === $type ) { $q .= " WHERE res LIKE '%" . Crawler::STATUS_HIT . "%'"; } if ( 'miss' === $type ) { $q .= " WHERE res LIKE '%" . Crawler::STATUS_MISS . "%'"; } if ( 'blacklisted' === $type ) { $q .= " WHERE res LIKE '%" . Crawler::STATUS_BLACKLIST . "%'"; } $q .= ' ORDER BY id LIMIT %d, %d'; return $wpdb->get_results( $wpdb->prepare( $q, $offset, $limit ), ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared } /** * Count sitemap. * * @return int|false */ public function count_map() { global $wpdb; if ( ! $this->__data->tb_exist( 'crawler' ) ) { return false; } $q = "SELECT COUNT(*) FROM `$this->_tb`"; $type = Router::verify_type(); if ( 'hit' === $type ) { $q .= " WHERE res LIKE '%" . Crawler::STATUS_HIT . "%'"; } if ( 'miss' === $type ) { $q .= " WHERE res LIKE '%" . Crawler::STATUS_MISS . "%'"; } if ( 'blacklisted' === $type ) { $q .= " WHERE res LIKE '%" . Crawler::STATUS_BLACKLIST . "%'"; } return $wpdb->get_var( $q ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared } /** * Generate sitemap. * * @since 1.1.0 * @access public * * @param bool $manual Whether triggered manually from UI. * @return void */ public function gen( $manual = false ) { $count = $this->_gen(); if ( ! $count ) { Admin_Display::error( __( 'No valid sitemap parsed for crawler.', 'litespeed-cache' ) ); return; } if ( ! wp_doing_cron() && $manual ) { $msg = sprintf( __( 'Sitemap created successfully: %d items', 'litespeed-cache' ), $count ); Admin_Display::success( $msg ); } } /** * Generate the sitemap. * * @since 1.1.0 * @access private * @return int|false Number of URLs generated or false on failure. */ private function _gen() { global $wpdb; if ( ! $this->__data->tb_exist( 'crawler' ) ) { $this->__data->tb_create( 'crawler' ); } if ( ! $this->__data->tb_exist( 'crawler_blacklist' ) ) { $this->__data->tb_create( 'crawler_blacklist' ); } // Use custom sitemap. $sitemap = $this->conf( Base::O_CRAWLER_SITEMAP ); if ( ! $sitemap ) { return false; } $offset = strlen( $this->_site_url ); $sitemap = Utility::sanitize_lines( $sitemap ); try { foreach ( $sitemap as $this_map ) { $this->_parse( $this_map ); } } catch ( \Exception $e ) { self::debug( '❌ failed to parse custom sitemap: ' . $e->getMessage() ); } if ( is_array( $this->_urls ) && ! empty( $this->_urls ) ) { if ( defined( 'LITESPEED_CRAWLER_DROP_DOMAIN' ) && constant( 'LITESPEED_CRAWLER_DROP_DOMAIN' ) ) { foreach ( $this->_urls as $k => $v ) { if ( 0 !== stripos( $v, $this->_site_url ) ) { unset( $this->_urls[ $k ] ); continue; } $this->_urls[ $k ] = substr( $v, $offset ); } } $this->_urls = array_values( array_unique( $this->_urls ) ); } self::debug( 'Truncate sitemap' ); $wpdb->query( "TRUNCATE `$this->_tb`" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery self::debug( 'Generate sitemap' ); // Filter URLs in blacklist. $blacklist = $this->list_blacklist(); $full_blacklisted = []; $partial_blacklisted = []; foreach ( $blacklist as $v ) { if ( false === strpos( $v['res'], '-' ) ) { // Full blacklisted. $full_blacklisted[] = $v['url']; } else { // Replace existing reason. $v['reason'] = explode( ',', $v['reason'] ); $v['reason'] = array_map( function ( $element ) { return $element ? 'Existed' : ''; }, $v['reason'] ); $v['reason'] = implode( ',', $v['reason'] ); $partial_blacklisted[ $v['url'] ] = [ 'res' => $v['res'], 'reason' => $v['reason'], ]; } } // Drop all blacklisted URLs. $this->_urls = array_diff( $this->_urls, $full_blacklisted ); // Default res & reason. $crawler_count = count( Crawler::cls()->list_crawlers() ); $default_res = str_repeat( '-', $crawler_count ); $default_reason = $crawler_count > 1 ? str_repeat( ',', $crawler_count - 1 ) : ''; $data = []; foreach ( $this->_urls as $url ) { $data[] = $url; $data[] = array_key_exists( $url, $partial_blacklisted ) ? $partial_blacklisted[ $url ]['res'] : $default_res; $data[] = array_key_exists( $url, $partial_blacklisted ) ? $partial_blacklisted[ $url ]['reason'] : $default_reason; } foreach ( array_chunk( $data, 300 ) as $data2 ) { $this->_save( $data2 ); } // Reset crawler. Crawler::cls()->reset_pos(); return count( $this->_urls ); } /** * Save data to table. * * @since 3.0 * @access private * * @param array $data Flat array (url,res,reason, url,res,reason, ...). * @param string $fields Fields list for insert (default url,res,reason). * @return void */ private function _save( $data, $fields = 'url,res,reason' ) { global $wpdb; if ( empty( $data ) ) { return; } $q = "INSERT INTO `$this->_tb` ( {$fields} ) VALUES "; // Add placeholder. $q .= Utility::chunk_placeholder( $data, $fields ); // Store data. $wpdb->query( $wpdb->prepare( $q, $data ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared } /** * Parse custom sitemap and collect urls. * * @since 1.1.1 * @access private * * @param string $sitemap Absolute sitemap URL. * @return void * @throws \Exception If remote read or parsing fails. */ private function _parse( $sitemap ) { /** * Read via wp func to avoid allow_url_fopen = off * * @since 2.2.7 */ $response = wp_safe_remote_get( $sitemap, [ 'timeout' => $this->_conf_map_timeout, 'sslverify' => false, ] ); if ( is_wp_error( $response ) ) { $error_message = $response->get_error_message(); self::debug( 'failed to read sitemap: ' . $error_message ); throw new \Exception( 'Failed to remote read ' . esc_url( $sitemap ) ); } $xml_object = simplexml_load_string($response['body'], null, LIBXML_NOCDATA); if (!$xml_object) { if ($this->_urls) { return; } throw new \Exception('Failed to parse xml ' . esc_url( $sitemap )); } // start parsing. $xml_array = (array) $xml_object; if ( ! empty( $xml_array['sitemap'] ) ) { // parse sitemap set. if ( is_object( $xml_array['sitemap'] ) ) { $xml_array['sitemap'] = (array) $xml_array['sitemap']; } if ( ! empty( $xml_array['sitemap']['loc'] ) ) { // is single sitemap. $this->_parse( (string) $xml_array['sitemap']['loc'] ); } else { // parse multiple sitemaps. foreach ( (array) $xml_array['sitemap'] as $val ) { $val = (array) $val; if ( ! empty( $val['loc'] ) ) { $this->_parse( (string) $val['loc'] ); // recursive parse sitemap. } } } } elseif ( ! empty( $xml_array['url'] ) ) { // parse url set. if ( is_object( $xml_array['url'] ) ) { $xml_array['url'] = (array) $xml_array['url']; } // if only 1 element. if ( ! empty( $xml_array['url']['loc'] ) ) { $this->_urls[] = (string) $xml_array['url']['loc']; } else { foreach ( (array) $xml_array['url'] as $val ) { $val = (array) $val; if ( ! empty( $val['loc'] ) ) { $this->_urls[] = (string) $val['loc']; } } } } } } src/import.cls.php000064400000010453152075713340010150 0ustar00_summary = self::get_summary(); } /** * Export settings to file * * @since 1.8.2 * @since 7.3 added download content type * @access public */ public function export( $only_data_return = false ) { $raw_data = $this->get_options(true); $data = array(); foreach ($raw_data as $k => $v) { $data[] = \json_encode(array( $k, $v )); } $data = implode("\n\n", $data); if ($only_data_return) { return $data; } $filename = $this->_generate_filename(); // Update log $this->_summary['export_file'] = $filename; $this->_summary['export_time'] = time(); self::save_summary(); Debug2::debug('Import: Saved to ' . $filename); @header('Content-Type: application/octet-stream'); @header('Content-Disposition: attachment; filename=' . $filename); echo $data; exit(); } /** * Import settings from file * * @since 1.8.2 * @access public */ public function import( $file = false ) { if (!$file) { if (empty($_FILES['ls_file']['name']) || substr($_FILES['ls_file']['name'], -5) != '.data' || empty($_FILES['ls_file']['tmp_name'])) { Debug2::debug('Import: Failed to import, wrong ls_file'); $msg = __('Import failed due to file error.', 'litespeed-cache'); Admin_Display::error($msg); return false; } $this->_summary['import_file'] = $_FILES['ls_file']['name']; $data = file_get_contents($_FILES['ls_file']['tmp_name']); } else { $this->_summary['import_file'] = $file; $data = file_get_contents($file); } // Update log $this->_summary['import_time'] = time(); self::save_summary(); $ori_data = array(); try { // Check if the data is v4+ or not if (strpos($data, '["_version",') === 0) { Debug2::debug('[Import] Data version: v4+'); $data = explode("\n", $data); foreach ($data as $v) { $v = trim($v); if (!$v) { continue; } list($k, $v) = \json_decode($v, true); $ori_data[$k] = $v; } } else { $ori_data = \json_decode(base64_decode($data), true); } } catch (\Exception $ex) { Debug2::debug('[Import] ❌ Failed to parse serialized data'); return false; } if (!$ori_data) { Debug2::debug('[Import] ❌ Failed to import, no data'); return false; } else { Debug2::debug('[Import] Importing data', $ori_data); } $this->cls('Conf')->update_confs($ori_data); if (!$file) { Debug2::debug('Import: Imported ' . $_FILES['ls_file']['name']); $msg = sprintf(__('Imported setting file %s successfully.', 'litespeed-cache'), $_FILES['ls_file']['name']); Admin_Display::success($msg); } else { Debug2::debug('Import: Imported ' . $file); } return true; } /** * Reset all configs to default values. * * @since 2.6.3 * @access public */ public function reset() { $options = $this->cls('Conf')->load_default_vals(); $this->cls('Conf')->update_confs($options); Debug2::debug('[Import] Reset successfully.'); $msg = __('Reset successfully.', 'litespeed-cache'); Admin_Display::success($msg); } /** * Generate the filename to export * * @since 1.8.2 * @access private */ private function _generate_filename() { // Generate filename $parsed_home = parse_url(get_home_url()); $filename = 'LSCWP_cfg-'; if (!empty($parsed_home['host'])) { $filename .= $parsed_home['host'] . '_'; } if (!empty($parsed_home['path'])) { $filename .= $parsed_home['path'] . '_'; } $filename = str_replace('/', '_', $filename); $filename .= '-' . date('Ymd_His') . '.data'; return $filename; } /** * Handle all request actions from main cls * * @since 1.8.2 * @access public */ public function handler() { $type = Router::verify_type(); switch ($type) { case self::TYPE_IMPORT: $this->import(); break; case self::TYPE_EXPORT: $this->export(); break; case self::TYPE_RESET: $this->reset(); break; default: break; } Admin::redirect(); } } src/data.upgrade.func.php000064400000013340152075713340011345 0ustar00suppress_errors; $wpdb->suppress_errors( true ); // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnquotedComplexPlaceholder, WordPress.DB.DirectDatabaseQuery.DirectQuery $tb_exists = $wpdb->get_var( $wpdb->prepare( 'DESCRIBE `%1s`', $table_name ) ); $wpdb->suppress_errors( $save_state ); return null !== $tb_exists; } /** * Migrate v7.0- url_files URL from no trailing slash to trailing slash. * * @since 7.0.1 * @return void */ function litespeed_update_7_0_1() { global $wpdb; Debug2::debug( '[Data] v7.0.1 upgrade started' ); $tb_url = $wpdb->prefix . 'litespeed_url'; if ( ! litespeed_table_exists( $tb_url ) ) { Debug2::debug( '[Data] Table `litespeed_url` not found, bypassed migration' ); return; } // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery $list = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM `{$tb_url}` WHERE url LIKE %s", 'https://%/' ), ARRAY_A ); $existing_urls = array(); if ($list) { foreach ($list as $v) { $existing_urls[] = $v['url']; } } // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery $list = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM `{$tb_url}` WHERE url LIKE %s", 'https://%' ), ARRAY_A ); if ( ! $list ) { return; } foreach ( $list as $v ) { if ( '/' === substr( $v['url'], -1 ) ) { continue; } $new_url = $v['url'] . '/'; if ( in_array( $new_url, $existing_urls, true ) ) { continue; } // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->query( $wpdb->prepare( "UPDATE `{$tb_url}` SET url = %s WHERE id = %d", $new_url, $v['id'] ) ); } } /** * Migrate from domain key to pk/sk for QC * * @since 7.0 */ function litespeed_update_7() { Debug2::debug('[Data] v7 upgrade started'); $__cloud = Cloud::cls(); $domain_key = $__cloud->conf('api_key'); if (!$domain_key) { Debug2::debug('[Data] No domain key, bypassed migration'); return; } $new_prepared = $__cloud->init_qc_prepare(); if (!$new_prepared && $__cloud->activated()) { Debug2::debug('[Data] QC previously activated in v7, bypassed migration'); return; } $data = array( 'domain_key' => $domain_key, ); $resp = $__cloud->post(Cloud::SVC_D_V3UPGRADE, $data); if ( ! empty( $resp['qc_activated'] ) ) { if ( 'deleted' !== $resp['qc_activated'] ) { $cloud_summary_updates = array( 'qc_activated' => $resp['qc_activated'] ); if (!empty($resp['main_domain'])) { $cloud_summary_updates['main_domain'] = $resp['main_domain']; } Cloud::save_summary($cloud_summary_updates); Debug2::debug('[Data] Updated QC activated status to ' . $resp['qc_activated']); } } } /** * Drop deprecated guest_ips and guest_uas from DB options. * Migrate url table to make all links trailing slash for UCSS/CCSS. * * These values are now read from files instead. * * @since 7.7 */ function litespeed_update_7_7() { global $wpdb; Debug2::debug( '[Data] v7.7 upgrade: dropping guest_ips/guest_uas options' ); Conf::delete_option( 'conf.guest_ips' ); Conf::delete_option( 'conf.guest_uas' ); Conf::delete_site_option( 'conf.guest_ips' ); Conf::delete_site_option( 'conf.guest_uas' ); // Normalize all URLs to have trailing slash to match UCSS/CCSS generation logic Debug2::debug( '[Data] v7.7 upgrade: normalizing URL trailing slashes' ); // Skip if plain permalink mode (no trailing slash) $permalink_structure = get_option( 'permalink_structure' ); if ( empty( $permalink_structure ) ) { Debug2::debug( '[Data] Plain permalink mode, bypassed URL trailing slash migration' ); return; } $tb_url = $wpdb->prefix . 'litespeed_url'; if ( ! litespeed_table_exists( $tb_url ) ) { Debug2::debug( '[Data] Table `litespeed_url` not found, bypassed URL migration' ); return; } // Check if there are URLs without trailing slash (exclude URLs with query string) // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery $count = $wpdb->get_var( "SELECT COUNT(*) FROM `{$tb_url}` WHERE url LIKE 'https://%' AND url NOT LIKE '%/' AND url NOT LIKE '%?%'" ); if ( ! $count ) { Debug2::debug( '[Data] No URLs without trailing slash found, bypassed' ); return; } // Append trailing slash to all URLs that don't have one and don't have duplicate with slash (exclude URLs with query string) // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->query( "UPDATE `{$tb_url}` SET url = CONCAT(url, '/') WHERE url LIKE 'https://%' AND url NOT LIKE '%/' AND url NOT LIKE '%?%' AND CONCAT(url, '/') NOT IN (SELECT * FROM (SELECT url FROM `{$tb_url}` WHERE url LIKE '%/') AS tmp)" ); } /** * Append webp/mobile to url_file * * @since 5.3 */ function litespeed_update_5_3() { global $wpdb; Debug2::debug('[Data] Upgrade url_file table'); $tb = $wpdb->prefix . 'litespeed_url_file'; if ( litespeed_table_exists( $tb ) ) { $q = "ALTER TABLE `{$tb}` ADD COLUMN `mobile` tinyint(4) NOT NULL COMMENT 'mobile=1', ADD COLUMN `webp` tinyint(4) NOT NULL COMMENT 'webp=1' "; // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->query( $q ); } } src/crawler.cls.php000064400000131343152075713340010277 0ustar00 [], 'headers' => [], 'ua' => '', ]; /** * Built crawler variants. * * @var array */ private $_crawlers = []; /** * Current allowed worker threads. * * @var int */ private $_cur_threads = -1; /** * Max timestamp to run until. * * @var int */ private $_max_run_time; /** * Last time threads were adjusted. * * @var int */ private $_cur_thread_time; /** * Map-status list to batch-save. * * @var array */ private $_map_status_list = [ 'H' => [], 'M' => [], 'B' => [], 'N' => [], ]; /** * Summary cache. * * @var array */ protected $_summary; /** * Initialize crawler, assign sitemap path. * * @since 1.1.0 */ public function __construct() { if ( is_multisite() ) { $this->_sitemeta = 'meta' . get_current_blog_id() . '.data'; } $this->_resetfile = LITESPEED_STATIC_DIR . '/crawler/' . $this->_sitemeta . '.reset'; $this->_summary = self::get_summary(); $this->_ncpu = $this->_get_server_cpu(); $this->_server_ip = $this->conf( Base::O_SERVER_IP ); self::debug( 'Init w/ CPU cores=' . $this->_ncpu ); } /** * Try get server CPUs. * * @since 5.2 * @return int Number of cores detected. */ private function _get_server_cpu() { $cpuinfo_file = '/proc/cpuinfo'; $setting_open_dir = ini_get( 'open_basedir' ); if ( $setting_open_dir ) { return 1; // Server has limit. } try { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged if (!@is_file($cpuinfo_file)) { return 1; } } catch ( \Exception $e ) { return 1; } // Local system read; no WP alternative. Suppress sniff. // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents $cpuinfo = file_get_contents( $cpuinfo_file ); preg_match_all( '/^processor/m', $cpuinfo, $matches ); $cnt = isset( $matches[0] ) ? count( $matches[0] ) : 0; return $cnt ? $cnt : 1; } /** * Check whether the current crawler is active. * * @since 4.3 * @param int $curr Crawler index. * @return bool Active state. */ public function is_active( $curr ) { $bypass_list = self::get_option( 'bypass_list', [] ); return ! in_array( (int) $curr, $bypass_list, true ); } /** * Toggle the current crawler's active state and return the updated state. * * @since 4.3 * @param int $curr Crawler index. * @return bool True if turned on, false if turned off. */ public function toggle_activeness( $curr ) { $bypass_list = self::get_option( 'bypass_list', [] ); if ( in_array( (int) $curr, $bypass_list, true ) ) { // Remove it. $key = array_search( (int) $curr, $bypass_list, true ); if ( false !== $key ) { unset( $bypass_list[ $key ] ); $bypass_list = array_values( $bypass_list ); self::update_option( 'bypass_list', $bypass_list ); } return true; } // Add it. $bypass_list[] = (int) $curr; self::update_option( 'bypass_list', $bypass_list ); return false; } /** * Clear bypassed list. * * @since 4.3 * @access public * @return void */ public function clear_disabled_list() { self::update_option( 'bypass_list', [] ); $msg = __( 'Crawler disabled list is cleared! All crawlers are set to active! ', 'litespeed-cache' ); Admin_Display::note( $msg ); self::debug( 'All crawlers are set to active...... ' ); } /** * Overwrite get_summary to init elements. * * @since 3.0 * @access public * * @param string|false $field Field name to fetch or false to get all. * @return mixed Summary value/array or null if not found. */ public static function get_summary( $field = false ) { $_default = [ 'list_size' => 0, 'last_update_time' => 0, 'curr_crawler' => 0, 'curr_crawler_beginning_time' => 0, 'last_pos' => 0, 'last_count' => 0, 'last_crawled' => 0, 'last_start_time' => 0, 'last_status' => '', 'is_running' => 0, 'end_reason' => '', 'meta_save_time' => 0, 'pos_reset_check' => 0, 'done' => 0, 'this_full_beginning_time' => 0, 'last_full_time_cost' => 0, 'last_crawler_total_cost' => 0, 'crawler_stats' => [], // this will store all crawlers hit/miss crawl status. ]; wp_cache_delete( 'alloptions', 'options' ); // ensure the summary is current. $summary = parent::get_summary(); $summary = array_merge( $_default, $summary ); if ( false === $field ) { return $summary; } if ( array_key_exists( $field, $summary ) ) { return $summary[ $field ]; } return null; } /** * Overwrite save_summary. * * @since 3.0 * @access public * * @param array|false $data Data to save or false to save current. * @param bool $reload Whether to reload after saving. * @param bool $overwrite Whether to overwrite completely. * @return void */ public static function save_summary( $data = false, $reload = false, $overwrite = false ) { $instance = self::cls(); $instance->_summary['meta_save_time'] = time(); if ( false === $data ) { $data = $instance->_summary; } parent::save_summary( $data, $reload, $overwrite ); File::save( LITESPEED_STATIC_DIR . '/crawler/' . $instance->_sitemeta, wp_json_encode( $data ), true ); } /** * Cron start async crawling. * * @since 5.5 * @return void */ public static function start_async_cron() { Task::async_call( 'crawler' ); } /** * Manually start async crawling. * * @since 5.5 * @return void */ public static function start_async() { Task::async_call( 'crawler_force' ); $msg = __( 'Started async crawling', 'litespeed-cache' ); Admin_Display::success( $msg ); } /** * Ajax crawl handler. * * @since 5.5 * @param bool $manually_run Whether manually triggered. * @return void */ public static function async_handler( $manually_run = false ) { self::debug( '------------async-------------start_async_handler' ); self::start( (bool) $manually_run ); } /** * Proceed crawling. * * @since 1.1.0 * @access public * * @param bool $manually_run Whether manually triggered. * @return bool|void */ public static function start( $manually_run = false ) { if ( ! Router::can_crawl() ) { self::debug( '......crawler is NOT allowed by the server admin......' ); return false; } if ( $manually_run ) { self::debug( '......crawler manually ran......' ); } self::cls()->_crawl_data( (bool) $manually_run ); } /** * Crawling start. * * @since 1.1.0 * @access private * * @param bool $manually_run Whether manually triggered. * @return void */ private function _crawl_data( $manually_run ) { if ( ! defined( 'LITESPEED_LANE_HASH' ) ) { define( 'LITESPEED_LANE_HASH', Str::rrand( 8 ) ); } if ( $this->_check_valid_lane() ) { $this->_take_over_lane(); } else { self::debug( '⚠️ lane in use' ); return; } self::debug( '......crawler started......' ); // for the first time running. if ( ! $this->_summary || ! Data::cls()->tb_exist( 'crawler' ) || ! Data::cls()->tb_exist( 'crawler_blacklist' ) ) { $this->cls( 'Crawler_Map' )->gen(); } // if finished last time, regenerate sitemap. if ( 'touchedEnd' === $this->_summary['done'] ) { // check whole crawling interval. $last_finished_at = (int) $this->_summary['last_full_time_cost'] + (int) $this->_summary['this_full_beginning_time']; if ( ! $manually_run && ( time() - $last_finished_at ) < $this->conf( Base::O_CRAWLER_CRAWL_INTERVAL ) ) { self::debug( 'Cron abort: cache warmed already.' ); $this->Release_lane(); return; } self::debug( 'TouchedEnd. regenerate sitemap....' ); $this->cls( 'Crawler_Map' )->gen(); } $crawlers = $this->list_crawlers(); $crawlers_count = count( $crawlers ); // Skip the crawlers that in bypassed list. while ( ! $this->is_active( $this->_summary['curr_crawler'] ) && $this->_summary['curr_crawler'] < $crawlers_count ) { self::debug( 'Skipped the Crawler #' . $this->_summary['curr_crawler'] . ' ......' ); $this->_summary['curr_crawler'] = (int) $this->_summary['curr_crawler'] + 1; } if ( $this->_summary['curr_crawler'] >= $crawlers_count ) { $this->_end_reason = 'end'; $this->_terminate_running(); $this->Release_lane(); return; } // In case crawlers are all done but not reload, reload it. if ( empty( $this->_summary['curr_crawler'] ) || empty( $this->_crawlers[ $this->_summary['curr_crawler'] ] ) ) { $this->_summary['curr_crawler'] = 0; $this->_summary['crawler_stats'][ $this->_summary['curr_crawler'] ] = []; } $res = $this->load_conf(); if ( ! $res ) { self::debug( 'Load conf failed' ); $this->_terminate_running(); $this->Release_lane(); return; } try { $this->_engine_start(); $this->Release_lane(); } catch ( \Exception $e ) { self::debug( '🛑 ' . $e->getMessage() ); } } /** * Load conf before running crawler. * * @since 3.0 * @access private * @return bool True on success. */ private function load_conf() { $this->_crawler_conf['base'] = site_url(); $current_crawler = $this->_crawlers[ $this->_summary['curr_crawler'] ]; // Cookies. foreach ( $current_crawler as $k => $v ) { if ( 0 !== strpos( $k, 'cookie:' ) ) { continue; } if ( '_null' === $v ) { continue; } $this->_crawler_conf['cookies'][ substr( $k, 7 ) ] = $v; } // WebP/AVIF simulation. if ( ! empty( $current_crawler['webp'] ) ) { $this->_crawler_conf['headers'][] = 'Accept: image/' . ( 2 === (int) $this->conf( Base::O_IMG_OPTM_WEBP ) ? 'avif' : 'webp' ) . ',*/*'; } // Mobile crawler. if ( ! empty( $current_crawler['mobile'] ) ) { $this->_crawler_conf['ua'] = 'Mobile iPhone'; } // Limit delay to use server setting. $this->_crawler_conf['run_delay'] = 500; // microseconds. if ( defined( 'LITESPEED_CRAWLER_USLEEP' ) && constant( 'LITESPEED_CRAWLER_USLEEP' ) > $this->_crawler_conf['run_delay'] ) { $this->_crawler_conf['run_delay'] = (int) constant( 'LITESPEED_CRAWLER_USLEEP' ); } if ( isset( $_SERVER[ Base::ENV_CRAWLER_USLEEP ] ) ) { $env_usleep = absint( wp_unslash( $_SERVER[ Base::ENV_CRAWLER_USLEEP ] ) ); if ( $env_usleep > (int) $this->_crawler_conf['run_delay'] ) { $this->_crawler_conf['run_delay'] = $env_usleep; } } $this->_crawler_conf['run_duration'] = $this->get_crawler_duration(); $this->_crawler_conf['load_limit'] = (int) $this->conf( Base::O_CRAWLER_LOAD_LIMIT ); if ( isset( $_SERVER[ Base::ENV_CRAWLER_LOAD_LIMIT_ENFORCE ] ) ) { $this->_crawler_conf['load_limit'] = absint( wp_unslash( $_SERVER[ Base::ENV_CRAWLER_LOAD_LIMIT_ENFORCE ] ) ); } elseif ( isset( $_SERVER[ Base::ENV_CRAWLER_LOAD_LIMIT ] ) ) { $env_limit = absint( wp_unslash( $_SERVER[ Base::ENV_CRAWLER_LOAD_LIMIT ] ) ); if ( $env_limit < (int) $this->_crawler_conf['load_limit'] ) { $this->_crawler_conf['load_limit'] = $env_limit; } } if ( 0 === (int) $this->_crawler_conf['load_limit'] ) { self::debug( '🛑 Terminated crawler due to load limit set to 0' ); return false; } // Role simulation. if ( ! empty( $current_crawler['uid'] ) ) { if ( empty( $this->_server_ip ) ) { self::debug( '🛑 Terminated crawler due to Server IP not set' ); return false; } $vary_name = $this->cls( 'Vary' )->get_vary_name(); $vary_val = $this->cls( 'Vary' )->finalize_default_vary( $current_crawler['uid'] ); $this->_crawler_conf['cookies'][ $vary_name ] = $vary_val; $this->_crawler_conf['cookies']['litespeed_hash'] = Router::cls()->get_hash( $current_crawler['uid'] ); } return true; } /** * Get crawler duration allowance. * * @since 7.0 * @return int Seconds. */ public function get_crawler_duration() { $run_duration = defined( 'LITESPEED_CRAWLER_DURATION' ) ? (int) constant( 'LITESPEED_CRAWLER_DURATION' ) : 900; if ( $run_duration > 900 ) { $run_duration = 900; // reset to default value if defined higher than 900 seconds. } return $run_duration; } /** * Start crawler. * * @since 1.1.0 * @access private * @return void */ private function _engine_start() { // check current load. $this->_adjust_current_threads(); if ( 0 === (int) $this->_cur_threads ) { $this->_end_reason = 'stopped_highload'; self::debug( 'Stopped due to heavy load.' ); return; } // log started time. self::save_summary( [ 'last_start_time' => time() ] ); // set time limit. $max_time = (int) ini_get( 'max_execution_time' ); self::debug( 'ini_get max_execution_time=' . $max_time ); if ( 0 === $max_time ) { $max_time = 300; // hardlimit. } else { $max_time -= 5; } if ( $max_time >= (int) $this->_crawler_conf['run_duration'] ) { $max_time = (int) $this->_crawler_conf['run_duration']; self::debug( 'Use run_duration setting as max_execution_time=' . $max_time ); // phpcs:ignore WordPress.PHP.IniSet.max_execution_time_Disallowed -- Required for crawler functionality. } elseif ( ini_set( 'max_execution_time', $this->_crawler_conf['run_duration'] + 15 ) !== false ) { $max_time = $this->_crawler_conf['run_duration']; self::debug( 'ini_set max_execution_time=' . $max_time ); } self::debug( 'final max_execution_time=' . $max_time ); $this->_max_run_time = $max_time + time(); // mark running. $this->_prepare_running(); // run crawler. $this->_do_running(); $this->_terminate_running(); } /** * Get server load. * * @since 5.5 * @return int Load or -1 if unsupported. */ public function get_server_load() { if ( ! function_exists( 'sys_getloadavg' ) ) { return -1; } $curload = sys_getloadavg(); $curload = (float) $curload[0]; self::debug( 'Server load: ' . $curload ); return $curload; } /** * Adjust threads dynamically. * * @since 1.1.0 * @access private * @return void */ private function _adjust_current_threads() { $curload = $this->get_server_load(); if ( -1 === (int) $curload ) { self::debug( 'set threads=0 due to func sys_getloadavg not exist!' ); $this->_cur_threads = 0; return; } $curload /= (float) $this->_ncpu; $crawler_threads = defined( 'LITESPEED_CRAWLER_THREADS' ) ? (int) constant( 'LITESPEED_CRAWLER_THREADS' ) : 3; $load_limit = (float) $this->_crawler_conf['load_limit']; $current_threads = (int) $this->_cur_threads; if ( -1 === $current_threads ) { // init. if ( $curload > $load_limit ) { $curthreads = 0; } elseif ( $curload >= ( $load_limit - 1 ) ) { $curthreads = 1; } else { $curthreads = (int) ( $load_limit - $curload ); if ( $curthreads > $crawler_threads ) { $curthreads = $crawler_threads; } } } else { // adjust. $curthreads = $current_threads; if ( $curload >= ( $load_limit + 1 ) ) { sleep( 5 ); // sleep 5 secs. if ( $curthreads >= 1 ) { --$curthreads; } } elseif ( $curload >= $load_limit ) { --$curthreads; } elseif ( ( $curload + 1 ) < $load_limit ) { if ( $curthreads < $crawler_threads ) { ++$curthreads; } } } $this->_cur_threads = (int) $curthreads; $this->_cur_thread_time = time(); } /** * Mark running status. * * @since 1.1.0 * @access private * @return void */ private function _prepare_running() { $this->_summary['is_running'] = time(); $this->_summary['done'] = 0; // reset done status. $this->_summary['last_status'] = 'prepare running'; $this->_summary['last_crawled'] = 0; // Current crawler starttime mark. if ( 0 === (int) $this->_summary['last_pos'] ) { $this->_summary['curr_crawler_beginning_time'] = time(); } if ( 0 === (int) $this->_summary['curr_crawler'] && 0 === (int) $this->_summary['last_pos'] ) { $this->_summary['this_full_beginning_time'] = time(); $this->_summary['list_size'] = $this->cls( 'Crawler_Map' )->count_map(); } if ( 'end' === $this->_summary['end_reason'] && 0 === (int) $this->_summary['last_pos'] ) { $this->_summary['crawler_stats'][ $this->_summary['curr_crawler'] ] = []; } self::save_summary(); } /** * Take over lane. * * @since 6.1 * @return void */ private function _take_over_lane() { self::debug( 'Take over lane as lane is free: ' . $this->json_local_path() . '.pid' ); File::save( $this->json_local_path() . '.pid', LITESPEED_LANE_HASH ); } /** * Update lane file mtime. * * @since 6.1 * @return void */ private function _touch_lane() { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_touch touch( $this->json_local_path() . '.pid' ); } /** * Release lane file. * * @since 6.1 * @return void */ public function Release_lane() { $lane_file = $this->json_local_path() . '.pid'; if ( ! file_exists( $lane_file ) ) { return; } self::debug( 'Release lane' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink unlink( $lane_file ); } /** * Check if lane is used by other crawlers. * * @since 6.1 * @param bool $strict_mode Strict check that file must exist. * @return bool True if valid lane. */ private function _check_valid_lane( $strict_mode = false ) { $lane_file = $this->json_local_path() . '.pid'; if ( $strict_mode ) { if ( ! file_exists( $lane_file ) ) { self::debug( 'lane file not existed, strict mode is false [file] ' . $lane_file ); return false; } } $pid = File::read( $lane_file ); if ( $pid && LITESPEED_LANE_HASH !== $pid ) { // If lane file is older than 1h, ignore. if ( ( time() - filemtime( $lane_file ) ) > 3600 ) { self::debug( 'Lane file is older than 1h, releasing lane' ); $this->Release_lane(); return true; } return false; } return true; } /** * Test port for simulator. * * @since 7.0 * @access private * @return bool true if success and can continue crawling, false otherwise. */ private function _test_port() { if ( empty( $this->_server_ip ) ) { if ( empty( $this->_crawlers[ $this->_summary['curr_crawler'] ]['uid'] ) ) { self::debug( 'Bypass test port as Server IP is not set' ); return true; } self::debug( '❌ Server IP not set' ); return false; } if ( defined( 'LITESPEED_CRAWLER_LOCAL_PORT' ) ) { self::debug( '✅ LITESPEED_CRAWLER_LOCAL_PORT already defined' ); return true; } // Don't repeat testing in 120s. if ( ! empty( $this->_summary['test_port_tts'] ) && ( time() - (int) $this->_summary['test_port_tts'] ) < 120 ) { if ( ! empty( $this->_summary['test_port'] ) ) { self::debug( '✅ Use tested local port: ' . $this->_summary['test_port'] ); define( 'LITESPEED_CRAWLER_LOCAL_PORT', (int) $this->_summary['test_port'] ); return true; } return false; } $this->_summary['test_port_tts'] = time(); self::save_summary(); $options = $this->_get_curl_options(); $home = home_url(); File::save( LITESPEED_STATIC_DIR . '/crawler/test_port.html', $home, true ); $url = LITESPEED_STATIC_URL . '/crawler/test_port.html'; $parsed_url = wp_parse_url( $url ); if ( empty( $parsed_url['host'] ) ) { self::debug( '❌ Test port failed, invalid URL: ' . $url ); return false; } $resolved = $parsed_url['host'] . ':443:' . $this->_server_ip; $options[ CURLOPT_RESOLVE ] = [ $resolved ]; $options[ CURLOPT_DNS_USE_GLOBAL_CACHE ] = false; $options[ CURLOPT_HEADER ] = false; self::debug( 'Test local 443 port for ' . $resolved ); // cURL is intentionally used for speed; suppress sniffs in this method. // phpcs:disable WordPress.WP.AlternativeFunctions $ch = curl_init(); curl_setopt_array( $ch, $options ); curl_setopt( $ch, CURLOPT_URL, $url ); $result = curl_exec( $ch ); $test_result = false; if ( curl_errno( $ch ) || $result !== $home ) { if ( curl_errno( $ch ) ) { self::debug( '❌ Test port curl error: [errNo] ' . curl_errno( $ch ) . ' [err] ' . curl_error( $ch ) ); } elseif ( $result !== $home ) { self::debug( '❌ Test port response is wrong: ' . $result ); } self::debug( '❌ Test local 443 port failed, try port 80' ); // Try port 80. $resolved = $parsed_url['host'] . ':80:' . $this->_server_ip; $options[ CURLOPT_RESOLVE ] = [ $resolved ]; $url = str_replace( 'https://', 'http://', $url ); if ( empty( $options[ CURLOPT_HTTPHEADER ] ) || ! in_array( 'X-Forwarded-Proto: https', $options[ CURLOPT_HTTPHEADER ], true ) ) { $options[ CURLOPT_HTTPHEADER ][] = 'X-Forwarded-Proto: https'; } $ch = curl_init(); curl_setopt_array( $ch, $options ); curl_setopt( $ch, CURLOPT_URL, $url ); $result = curl_exec( $ch ); if ( curl_errno( $ch ) ) { self::debug( '❌ Test port curl error: [errNo] ' . curl_errno( $ch ) . ' [err] ' . curl_error( $ch ) ); } elseif ( $result !== $home ) { self::debug( '❌ Test port response is wrong: ' . $result ); } else { self::debug( '✅ Test local 80 port successfully' ); define( 'LITESPEED_CRAWLER_LOCAL_PORT', 80 ); $this->_summary['test_port'] = 80; $test_result = true; } } else { self::debug( '✅ Tested local 443 port successfully' ); define( 'LITESPEED_CRAWLER_LOCAL_PORT', 443 ); $this->_summary['test_port'] = 443; $test_result = true; } self::save_summary(); unset( $ch ); // phpcs:enable return $test_result; } /** * Run crawler. * * @since 1.1.0 * @access private * @return void * @throws \Exception When lane becomes invalid during run. */ private function _do_running() { $options = $this->_get_curl_options( true ); // If is role simulator and not defined local port, check port once. $test_result = $this->_test_port(); if ( ! $test_result ) { $this->_end_reason = 'port_test_failed'; self::debug( '❌ Test port failed, crawler stopped.' ); return; } while ( true ) { $url_chunks = $this->cls( 'Crawler_Map' )->list_map( self::CHUNKS, $this->_summary['last_pos'] ); if ( empty( $url_chunks ) ) { break; } $url_chunks = array_chunk( $url_chunks, (int) $this->_cur_threads ); foreach ( $url_chunks as $rows ) { if ( ! $this->_check_valid_lane( true ) ) { $this->_end_reason = 'lane_invalid'; self::debug( '🛑 The crawler lane is used by newer crawler.' ); throw new \Exception( 'invalid crawler lane' ); } // Update time. $this->_touch_lane(); // multi curl. $rets = $this->_multi_request( $rows, $options ); // check result headers. foreach ( $rows as $row ) { if ( empty( $rets[ $row['id'] ] ) ) { continue; } if ( 428 === (int) $rets[ $row['id'] ]['code'] ) { // HTTP/1.1 428 Precondition Required (need to test) $this->_end_reason = 'crawler_disabled'; self::debug( 'crawler_disabled' ); return; } $status = $this->_status_parse( $rets[ $row['id'] ]['header'], $rets[ $row['id'] ]['code'], $row['url'] ); // B or H or M or N(nocache). self::debug( '[status] ' . $this->_status2title( $status ) . "\t\t [url] " . $row['url'] ); $this->_map_status_list[ $status ][ $row['id'] ] = [ 'url' => $row['url'], 'code' => (int) $rets[ $row['id'] ]['code'], // 201 or 200 or 404. ]; if ( empty( $this->_summary['crawler_stats'][ $this->_summary['curr_crawler'] ][ $status ] ) ) { $this->_summary['crawler_stats'][ $this->_summary['curr_crawler'] ][ $status ] = 0; } ++$this->_summary['crawler_stats'][ $this->_summary['curr_crawler'] ][ $status ]; } // update offset position. $_time = time(); $this->_summary['last_count'] = count( $rows ); $this->_summary['last_pos'] += $this->_summary['last_count']; $this->_summary['last_crawled'] += $this->_summary['last_count']; $this->_summary['last_update_time'] = $_time; $this->_summary['last_status'] = 'updated position'; // check duration. if ( $this->_summary['last_update_time'] > $this->_max_run_time ) { $this->_end_reason = 'stopped_maxtime'; self::debug( 'Terminated due to maxtime' ); return; } // make sure at least each 10s save meta & map status once. if ( $_time - $this->_summary['meta_save_time'] > 10 ) { $this->_map_status_list = $this->cls( 'Crawler_Map' )->save_map_status( $this->_map_status_list, $this->_summary['curr_crawler'] ); self::save_summary(); } // check if need to reset pos each 5s. if ( $_time > $this->_summary['pos_reset_check'] ) { $this->_summary['pos_reset_check'] = $_time + 5; if ( file_exists( $this->_resetfile ) && unlink( $this->_resetfile ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink self::debug( 'Terminated due to reset file' ); $this->_summary['last_pos'] = 0; $this->_summary['curr_crawler'] = 0; $this->_summary['crawler_stats'][ $this->_summary['curr_crawler'] ] = []; // reset done status. $this->_summary['done'] = 0; $this->_summary['this_full_beginning_time'] = 0; $this->_end_reason = 'stopped_reset'; return; } } // check loads. if ( ( $this->_summary['last_update_time'] - $this->_cur_thread_time ) > 60 ) { $this->_adjust_current_threads(); if ( 0 === (int) $this->_cur_threads ) { $this->_end_reason = 'stopped_highload'; self::debug( '🛑 Terminated due to highload' ); return; } } $this->_summary['last_status'] = 'sleeping ' . (int) $this->_crawler_conf['run_delay'] . 'ms'; usleep( (int) $this->_crawler_conf['run_delay'] ); } } // All URLs are done for current crawler. $this->_end_reason = 'end'; $this->_summary['crawler_stats'][ $this->_summary['curr_crawler'] ]['W'] = 0; self::debug( 'Crawler #' . $this->_summary['curr_crawler'] . ' touched end' ); } /** * If need to resolve DNS or not. * * @since 7.3.0.1 * @return bool */ private function _should_force_resolve_dns() { if ( ! empty( $this->_server_ip ) ) { return true; } if ( ! empty( $this->_crawler_conf['cookies'] ) && ! empty( $this->_crawler_conf['cookies']['litespeed_hash'] ) ) { return true; } return false; } /** * Send multi curl requests. * If res=B/N, bypass request and won't return. * * @since 1.1.0 * @access private * * @param array> $rows Rows to crawl. * @param array $options cURL options. * @return array */ private function _multi_request( $rows, $options ) { if ( ! function_exists( 'curl_multi_init' ) ) { exit( 'curl_multi_init disabled' ); } // phpcs:disable WordPress.WP.AlternativeFunctions $mh = curl_multi_init(); $crawler_drop_domain = defined( 'LITESPEED_CRAWLER_DROP_DOMAIN' ) ? (bool) constant( 'LITESPEED_CRAWLER_DROP_DOMAIN' ) : false; $curls = []; foreach ( $rows as $row ) { if ( self::STATUS_BLACKLIST === substr( $row['res'], $this->_summary['curr_crawler'], 1 ) ) { continue; } if ( self::STATUS_NOCACHE === substr( $row['res'], $this->_summary['curr_crawler'], 1 ) ) { continue; } if (!function_exists('curl_init')) { exit('curl_init disabled'); } $curls[$row['id']] = curl_init(); // Append URL. $url = $row['url']; if ( $crawler_drop_domain ) { $url = $this->_crawler_conf['base'] . $row['url']; } // IP resolve. if ( $this->_should_force_resolve_dns() ) { $parsed_url = wp_parse_url( $url ); if ( ! empty( $parsed_url['host'] ) ) { $dom = $parsed_url['host']; $port = defined( 'LITESPEED_CRAWLER_LOCAL_PORT' ) ? (int) LITESPEED_CRAWLER_LOCAL_PORT : 443; $resolved = $dom . ':' . $port . ':' . $this->_server_ip; $options[ CURLOPT_RESOLVE ] = [ $resolved ]; $options[ CURLOPT_DNS_USE_GLOBAL_CACHE ] = false; if ( 80 === $port ) { $url = str_replace( 'https://', 'http://', $url ); if ( empty( $options[ CURLOPT_HTTPHEADER ] ) || ! in_array( 'X-Forwarded-Proto: https', $options[ CURLOPT_HTTPHEADER ], true ) ) { $options[ CURLOPT_HTTPHEADER ][] = 'X-Forwarded-Proto: https'; } } self::debug( 'Resolved DNS for ' . $resolved ); } } curl_setopt( $curls[ $row['id'] ], CURLOPT_URL, $url ); self::debug( 'Crawling [url] ' . $url . ( $url === $row['url'] ? '' : ' [ori] ' . $row['url'] ) ); curl_setopt_array( $curls[ $row['id'] ], $options ); curl_multi_add_handle( $mh, $curls[ $row['id'] ] ); } // execute curl. if ( $curls ) { do { $status = curl_multi_exec( $mh, $active ); if ( $active ) { curl_multi_select( $mh ); } } while ( $active && CURLM_OK === $status ); } // curl done. $ret = []; foreach ( $rows as $row ) { if ( self::STATUS_BLACKLIST === substr( $row['res'], $this->_summary['curr_crawler'], 1 ) ) { continue; } if ( self::STATUS_NOCACHE === substr( $row['res'], $this->_summary['curr_crawler'], 1 ) ) { continue; } $ch = $curls[ $row['id'] ]; // Parse header. $header_size = curl_getinfo( $ch, CURLINFO_HEADER_SIZE ); $content = curl_multi_getcontent( $ch ); $header = substr( $content, 0, $header_size ); $ret[ $row['id'] ] = [ 'header' => $header, 'code' => (int) curl_getinfo( $ch, CURLINFO_HTTP_CODE ), ]; curl_multi_remove_handle( $mh, $ch ); unset( $ch ); } curl_multi_close( $mh ); // phpcs:enable return $ret; } /** * Translate the status to title. * * @since 6.0 * @param string $status Status char. * @return string Human title. */ private function _status2title( $status ) { if ( self::STATUS_HIT === $status ) { return '✅ Hit'; } if ( self::STATUS_MISS === $status ) { return '😊 Miss'; } if ( self::STATUS_BLACKLIST === $status ) { return '😅 Blacklisted'; } if ( self::STATUS_NOCACHE === $status ) { return '😅 Blacklisted'; } return '🛸 Unknown'; } /** * Check returned curl header to find if cached or not. * * @since 2.0 * @access private * * @param string $header Response header. * @param int $code HTTP code. * @param string $url URL. * @return string One of status chars. */ private function _status_parse( $header, $code, $url ) { if ( 201 === (int) $code ) { return self::STATUS_HIT; } if ( false !== stripos( $header, 'X-Litespeed-Cache-Control: no-cache' ) ) { // If is from DIVI, taken as miss. if ( defined( 'LITESPEED_CRAWLER_IGNORE_NONCACHEABLE' ) && constant( 'LITESPEED_CRAWLER_IGNORE_NONCACHEABLE' ) ) { return self::STATUS_MISS; } // If blacklist is disabled. if ( ( defined( 'LITESPEED_CRAWLER_DISABLE_BLOCKLIST' ) && constant( 'LITESPEED_CRAWLER_DISABLE_BLOCKLIST' ) ) || apply_filters( 'litespeed_crawler_disable_blocklist', false, $url ) ) { return self::STATUS_MISS; } return self::STATUS_NOCACHE; // Blacklist. } $_cache_headers = [ 'x-litespeed-cache', 'x-qc-cache', 'x-lsadc-cache' ]; foreach ( $_cache_headers as $_header ) { if ( false !== stripos( $header, $_header ) ) { if ( false !== stripos( $header, $_header . ': bkn' ) ) { return self::STATUS_HIT; // Hit. } if ( false !== stripos( $header, $_header . ': miss' ) ) { return self::STATUS_MISS; // Miss. } return self::STATUS_HIT; // Hit. } } // If blacklist is disabled. if ( ( defined( 'LITESPEED_CRAWLER_DISABLE_BLOCKLIST' ) && constant( 'LITESPEED_CRAWLER_DISABLE_BLOCKLIST' ) ) || apply_filters( 'litespeed_crawler_disable_blocklist', false, $url ) ) { return self::STATUS_MISS; } return self::STATUS_BLACKLIST; // Blacklist. } /** * Get curl options. * * @since 1.1.0 * @access private * * @param bool $crawler_only Whether crawler-only UA. * @return array */ private function _get_curl_options( $crawler_only = false ) { $crawler_timeout = defined( 'LITESPEED_CRAWLER_TIMEOUT' ) ? (int) constant( 'LITESPEED_CRAWLER_TIMEOUT' ) : 30; $options = [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADER => true, CURLOPT_CUSTOMREQUEST => 'GET', CURLOPT_FOLLOWLOCATION => false, CURLOPT_ENCODING => 'gzip', CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => $crawler_timeout, // Larger timeout to avoid incorrect blacklist addition #900171. CURLOPT_SSL_VERIFYHOST => 0, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_NOBODY => false, CURLOPT_HTTPHEADER => $this->_crawler_conf['headers'], ]; $options[ CURLOPT_HTTPHEADER ][] = 'Cache-Control: max-age=0'; $options[ CURLOPT_HTTP_VERSION ] = CURL_HTTP_VERSION_1_1; // if is walker // $options[ CURLOPT_FRESH_CONNECT ] = true; // Referer. if ( isset( $_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI'] ) ) { $host = sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ); $uri = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ); $options[ CURLOPT_REFERER ] = 'http://' . $host . $uri; } // User Agent. if ( $crawler_only ) { if ( 0 !== strpos( (string) $this->_crawler_conf['ua'], self::FAST_USER_AGENT ) ) { $this->_crawler_conf['ua'] = self::FAST_USER_AGENT . ' ' . (string) $this->_crawler_conf['ua']; } } $options[ CURLOPT_USERAGENT ] = (string) $this->_crawler_conf['ua']; // Cookies. $cookies = []; foreach ( $this->_crawler_conf['cookies'] as $k => $v ) { if ( ! $v ) { continue; } $cookies[] = $k . '=' . rawurlencode( $v ); } if ( $cookies ) { $options[ CURLOPT_COOKIE ] = implode( '; ', $cookies ); } return $options; } /** * Self curl to get HTML content. * * @since 3.3 * * @param string $url URL. * @param string $ua User agent. * @param int|false $uid Optional user ID for simulation. * @param string|false $accept Optional Accept header value. * @return string|false HTML on success, false on failure. */ public function self_curl( $url, $ua, $uid = false, $accept = false ) { $this->_crawler_conf['base'] = site_url(); $this->_crawler_conf['ua'] = $ua; if ( $accept ) { $this->_crawler_conf['headers'] = [ 'Accept: ' . $accept ]; } $options = $this->_get_curl_options(); if ( $uid ) { $this->_crawler_conf['cookies']['litespeed_flash_hash'] = Router::cls()->get_flash_hash( $uid ); $parsed_url = wp_parse_url( $url ); if ( ! empty( $parsed_url['host'] ) ) { $dom = $parsed_url['host']; $port = defined( 'LITESPEED_CRAWLER_LOCAL_PORT' ) ? (int) LITESPEED_CRAWLER_LOCAL_PORT : 443; $resolved = $dom . ':' . $port . ':' . $this->_server_ip; $options[ CURLOPT_RESOLVE ] = [ $resolved ]; $options[ CURLOPT_DNS_USE_GLOBAL_CACHE ] = false; $options[ CURLOPT_PORT ] = $port; self::debug( 'Resolved DNS for ' . $resolved ); } } $options[ CURLOPT_HEADER ] = false; $options[ CURLOPT_FOLLOWLOCATION ] = true; // phpcs:disable WordPress.WP.AlternativeFunctions $ch = curl_init(); curl_setopt_array( $ch, $options ); curl_setopt( $ch, CURLOPT_URL, $url ); $result = curl_exec( $ch ); $code = (int) curl_getinfo( $ch, CURLINFO_HTTP_CODE ); unset( $ch ); // phpcs:enable if ( 200 !== $code ) { self::debug( '❌ Response code is not 200 in self_curl() [code] ' . $code ); return false; } return $result; } /** * Terminate crawling. * * @since 1.1.0 * @access private * @return void */ private function _terminate_running() { $this->_map_status_list = $this->cls( 'Crawler_Map' )->save_map_status( $this->_map_status_list, $this->_summary['curr_crawler'] ); if ( 'end' === $this->_end_reason ) { $this->_summary['curr_crawler'] = (int) $this->_summary['curr_crawler'] + 1; // Jump to next crawler. $this->_summary['last_pos'] = 0; // reset last position. $this->_summary['last_crawler_total_cost'] = time() - (int) $this->_summary['curr_crawler_beginning_time']; $count_crawlers = count( $this->list_crawlers() ); if ( $this->_summary['curr_crawler'] >= $count_crawlers ) { self::debug( '_terminate_running Touched end, whole crawled. Reload crawler!' ); $this->_summary['curr_crawler'] = 0; $this->_summary['done'] = 'touchedEnd'; // log done status. $this->_summary['last_full_time_cost'] = time() - (int) $this->_summary['this_full_beginning_time']; } } $this->_summary['last_status'] = 'stopped'; $this->_summary['is_running'] = 0; $this->_summary['end_reason'] = $this->_end_reason; self::save_summary(); } /** * List all crawlers ( tagA => [ valueA => titleA, ... ] ... ). * * @since 1.9.1 * @access public * @return array> */ public function list_crawlers() { if ( $this->_crawlers ) { return $this->_crawlers; } $crawler_factors = []; // Add default Guest crawler. $crawler_factors['uid'] = [ 0 => __( 'Guest', 'litespeed-cache' ) ]; // WebP on/off. if ( $this->conf( Base::O_IMG_OPTM_WEBP ) ) { $crawler_factors['webp'] = [ 1 => $this->cls( 'Media' )->next_gen_image_title() ]; if ( apply_filters( 'litespeed_crawler_webp', false ) ) { $crawler_factors['webp'][0] = ''; } } // Guest Mode on/off. if ( $this->conf( Base::O_GUEST ) ) { $vary_name = $this->cls( 'Vary' )->get_vary_name(); $vary_val = 'guest_mode:1'; if ( ! defined( 'LSCWP_LOG' ) ) { $vary_val = md5( $this->conf( Base::HASH ) . $vary_val ); } $crawler_factors[ 'cookie:' . $vary_name ] = [ $vary_val => '', '_null' => '👒', ]; } // Mobile crawler. if ( $this->conf( Base::O_CACHE_MOBILE ) ) { $crawler_factors['mobile'] = [ 1 => '📱', 0 => '', ]; } // Get roles set. foreach ( $this->conf( Base::O_CRAWLER_ROLES ) as $v ) { $role_title = ''; $udata = get_userdata( $v ); if ( isset( $udata->roles ) && is_array( $udata->roles ) ) { $tmp = array_values( $udata->roles ); $role_title = array_shift( $tmp ); } if ( ! $role_title ) { continue; } $crawler_factors['uid'][ $v ] = ucfirst( $role_title ); } // Cookie crawler. foreach ( $this->conf( Base::O_CRAWLER_COOKIES ) as $v ) { if ( empty( $v['name'] ) ) { continue; } $this_cookie_key = 'cookie:' . $v['name']; $crawler_factors[ $this_cookie_key ] = []; foreach ( $v['vals'] as $v2 ) { $crawler_factors[ $this_cookie_key ][ $v2 ] = ( '_null' === $v2 ? '' : '🍪' . esc_html( $v['name'] ) . '=' . esc_html( $v2 ) ); } } // Crossing generate the crawler list. $this->_crawlers = $this->_recursive_build_crawler( $crawler_factors ); return $this->_crawlers; } /** * Build a crawler list recursively. * * @since 2.8 * @access private * * @param array $crawler_factors Factors. * @param array $group Current group. * @param int $i Factor index. * @return array */ private function _recursive_build_crawler( $crawler_factors, $group = [], $i = 0 ) { $current_factor_keys = array_keys( $crawler_factors ); $current_factor = $current_factor_keys[ $i ]; $if_touch_end = ( $i + 1 ) >= count( $crawler_factors ); $final_list = []; foreach ( $crawler_factors[ $current_factor ] as $k => $v ) { $item = $group; // Don't alter $group bcos of loop usage. $item['title'] = ! empty( $group['title'] ) ? $group['title'] : ''; if ( $v ) { if ( $item['title'] ) { $item['title'] .= ' - '; } $item['title'] .= $v; } $item[ $current_factor ] = $k; if ( $if_touch_end ) { $final_list[] = $item; } else { // Inception: next layer. $final_list = array_merge( $final_list, $this->_recursive_build_crawler( $crawler_factors, $item, $i + 1 ) ); } } return $final_list; } /** * Return crawler meta file local path. * * @since 6.1 * @access public * @return string */ public function json_local_path() { return LITESPEED_STATIC_DIR . '/crawler/' . $this->_sitemeta; } /** * Return crawler meta file URL. * * @since 1.1.0 * @access public * @return string|false */ public function json_path() { if ( ! file_exists( LITESPEED_STATIC_DIR . '/crawler/' . $this->_sitemeta ) ) { return false; } return LITESPEED_STATIC_URL . '/crawler/' . $this->_sitemeta; } /** * Create reset pos file. * * @since 1.1.0 * @access public * @return void */ public function reset_pos() { File::save( $this->_resetfile, time(), true ); self::save_summary( [ 'is_running' => 0 ] ); } /** * Display status based by matching crawlers order. * * @since 3.0 * @access public * * @param string $status_row Status string. * @param string $reason_set Comma separated reasons. * @return string HTML dots. */ public function display_status( $status_row, $reason_set ) { if ( ! $status_row ) { return ''; } $_status_list = [ '-' => 'default', self::STATUS_MISS => 'primary', self::STATUS_HIT => 'success', self::STATUS_BLACKLIST => 'danger', self::STATUS_NOCACHE => 'warning', ]; $reason_set = explode( ',', $reason_set ); $status = ''; foreach ( str_split( $status_row ) as $k => $v ) { $reason = isset( $reason_set[ $k ] ) ? $reason_set[ $k ] : ''; if ( 'Man' === $reason ) { $reason = __( 'Manually added to blocklist', 'litespeed-cache' ); } if ( 'Existed' === $reason ) { $reason = __( 'Previously existed in blocklist', 'litespeed-cache' ); } $reason_attr = $reason ? 'data-balloon-pos="up" aria-label="' . esc_attr( $reason ) . '"' : ''; $status .= '' . ( $k + 1 ) . ''; } return $status; } /** * Handle all request actions from main cls. * * @since 3.0 * @access public * @return void */ public function handler() { $type = Router::verify_type(); switch ( $type ) { case self::TYPE_REFRESH_MAP: $this->cls( 'Crawler_Map' )->gen( true ); break; case self::TYPE_EMPTY: $this->cls( 'Crawler_Map' )->empty_map(); break; case self::TYPE_BLACKLIST_EMPTY: $this->cls( 'Crawler_Map' )->blacklist_empty(); break; case self::TYPE_BLACKLIST_DEL: // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized if (!empty($_GET['id'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $id = absint( wp_unslash( $_GET['id'] ) ); $this->cls( 'Crawler_Map' )->blacklist_del( $id ); } break; case self::TYPE_BLACKLIST_ADD: // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized if (!empty($_GET['id'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $id = absint( wp_unslash( $_GET['id'] ) ); $this->cls( 'Crawler_Map' )->blacklist_add( $id ); } break; case self::TYPE_START: // Handle the ajax request to proceed crawler manually by admin. self::start_async(); break; case self::TYPE_RESET: $this->reset_pos(); break; default: break; } Admin::redirect(); } } src/object-cache.cls.php000064400000051712152075713340011150 0ustar00 Redis, false => Memcached. * * @var bool */ private $_cfg_method; /** * Host name. * * @var string */ private $_cfg_host; /** * Port number. * * @var int|string */ private $_cfg_port; /** * TTL in seconds. * * @var int */ private $_cfg_life; /** * Use persistent connection. * * @var bool */ private $_cfg_persistent; /** * Cache admin pages. * * @var bool */ private $_cfg_admin; /** * Redis DB index. * * @var int */ private $_cfg_db; /** * Auth username. * * @var string */ private $_cfg_user; /** * Auth password. * * @var string */ private $_cfg_pswd; /** * Default TTL in seconds. * * @var int */ private $_default_life = 360; /** * 'Redis' or 'Memcached'. * * @var string */ private $_oc_driver = 'Memcached'; // Redis or Memcached. /** * Global groups. * * @var array */ private $_global_groups = []; /** * Non-persistent groups. * * @var array */ private $_non_persistent_groups = []; /** * Init. * * NOTE: this class may be included without initialized core. * * @since 1.8 * * @param array|false $cfg Optional configuration to bootstrap without core. */ public function __construct( $cfg = false ) { if ( $cfg ) { if ( ! is_array( $cfg[ Base::O_OBJECT_GLOBAL_GROUPS ] ) ) { $cfg[ Base::O_OBJECT_GLOBAL_GROUPS ] = explode( "\n", $cfg[ Base::O_OBJECT_GLOBAL_GROUPS ] ); } if ( ! is_array( $cfg[ Base::O_OBJECT_NON_PERSISTENT_GROUPS ] ) ) { $cfg[ Base::O_OBJECT_NON_PERSISTENT_GROUPS ] = explode( "\n", $cfg[ Base::O_OBJECT_NON_PERSISTENT_GROUPS ] ); } $this->_cfg_debug = $cfg[ Base::O_DEBUG ] ? $cfg[ Base::O_DEBUG ] : false; $this->_cfg_method = $cfg[ Base::O_OBJECT_KIND ] ? true : false; $this->_cfg_host = $cfg[ Base::O_OBJECT_HOST ]; $this->_cfg_port = $cfg[ Base::O_OBJECT_PORT ]; $this->_cfg_life = $cfg[ Base::O_OBJECT_LIFE ]; $this->_cfg_persistent = $cfg[ Base::O_OBJECT_PERSISTENT ]; $this->_cfg_admin = $cfg[ Base::O_OBJECT_ADMIN ]; $this->_cfg_db = $cfg[ Base::O_OBJECT_DB_ID ]; $this->_cfg_user = $cfg[ Base::O_OBJECT_USER ]; $this->_cfg_pswd = $cfg[ Base::O_OBJECT_PSWD ]; $this->_global_groups = $cfg[ Base::O_OBJECT_GLOBAL_GROUPS ]; $this->_non_persistent_groups = $cfg[ Base::O_OBJECT_NON_PERSISTENT_GROUPS ]; if ( $this->_cfg_method ) { $this->_oc_driver = 'Redis'; } $this->_cfg_enabled = $cfg[ Base::O_OBJECT ] && class_exists( $this->_oc_driver ) && $this->_cfg_host; } elseif ( defined( 'LITESPEED_CONF_LOADED' ) ) { // If OC is OFF, will hit here to init OC after conf initialized $this->_cfg_debug = $this->conf( Base::O_DEBUG ) ? $this->conf( Base::O_DEBUG ) : false; $this->_cfg_method = $this->conf( Base::O_OBJECT_KIND ) ? true : false; $this->_cfg_host = $this->conf( Base::O_OBJECT_HOST ); $this->_cfg_port = $this->conf( Base::O_OBJECT_PORT ); $this->_cfg_life = $this->conf( Base::O_OBJECT_LIFE ); $this->_cfg_persistent = $this->conf( Base::O_OBJECT_PERSISTENT ); $this->_cfg_admin = $this->conf( Base::O_OBJECT_ADMIN ); $this->_cfg_db = $this->conf( Base::O_OBJECT_DB_ID ); $this->_cfg_user = $this->conf( Base::O_OBJECT_USER ); $this->_cfg_pswd = $this->conf( Base::O_OBJECT_PSWD ); $this->_global_groups = $this->conf( Base::O_OBJECT_GLOBAL_GROUPS ); $this->_non_persistent_groups = $this->conf( Base::O_OBJECT_NON_PERSISTENT_GROUPS ); if ( $this->_cfg_method ) { $this->_oc_driver = 'Redis'; } $this->_cfg_enabled = $this->conf( Base::O_OBJECT ) && class_exists( $this->_oc_driver ) && $this->_cfg_host; } elseif ( defined( 'self::CONF_FILE' ) && file_exists( WP_CONTENT_DIR . '/' . self::CONF_FILE ) ) { // Get cfg from _data_file. // Use self::const to avoid loading more classes. $cfg = \json_decode( file_get_contents( WP_CONTENT_DIR . '/' . self::CONF_FILE ), true ); if ( ! empty( $cfg[ self::O_OBJECT_HOST ] ) ) { $this->_cfg_debug = ! empty( $cfg[ Base::O_DEBUG ] ) ? $cfg[ Base::O_DEBUG ] : false; $this->_cfg_method = ! empty( $cfg[ self::O_OBJECT_KIND ] ) ? $cfg[ self::O_OBJECT_KIND ] : false; $this->_cfg_host = $cfg[ self::O_OBJECT_HOST ]; $this->_cfg_port = $cfg[ self::O_OBJECT_PORT ]; $this->_cfg_life = ! empty( $cfg[ self::O_OBJECT_LIFE ] ) ? $cfg[ self::O_OBJECT_LIFE ] : $this->_default_life; $this->_cfg_persistent = ! empty( $cfg[ self::O_OBJECT_PERSISTENT ] ) ? $cfg[ self::O_OBJECT_PERSISTENT ] : false; $this->_cfg_admin = ! empty( $cfg[ self::O_OBJECT_ADMIN ] ) ? $cfg[ self::O_OBJECT_ADMIN ] : false; $this->_cfg_db = ! empty( $cfg[ self::O_OBJECT_DB_ID ] ) ? $cfg[ self::O_OBJECT_DB_ID ] : 0; $this->_cfg_user = ! empty( $cfg[ self::O_OBJECT_USER ] ) ? $cfg[ self::O_OBJECT_USER ] : ''; $this->_cfg_pswd = ! empty( $cfg[ self::O_OBJECT_PSWD ] ) ? $cfg[ self::O_OBJECT_PSWD ] : ''; $this->_global_groups = ! empty( $cfg[ self::O_OBJECT_GLOBAL_GROUPS ] ) ? $cfg[ self::O_OBJECT_GLOBAL_GROUPS ] : []; $this->_non_persistent_groups = ! empty( $cfg[ self::O_OBJECT_NON_PERSISTENT_GROUPS ] ) ? $cfg[ self::O_OBJECT_NON_PERSISTENT_GROUPS ] : []; if ( $this->_cfg_method ) { $this->_oc_driver = 'Redis'; } $this->_cfg_enabled = class_exists( $this->_oc_driver ) && $this->_cfg_host; } else { $this->_cfg_enabled = false; } } else { $this->_cfg_enabled = false; } // If OC not available, mark failure so OC methods return false early. // NOTE: Do NOT call wp_using_ext_object_cache(false) here — it causes // "Cannot redeclare wp_cache_init()" fatal on multisite (second call // to wp_start_object_cache() would load cache.php again). if ( ! $this->_cfg_enabled ) { ! defined( 'LITESPEED_OC_FAILURE' ) && define( 'LITESPEED_OC_FAILURE', true ); } } /** * Add debug. * * @since 6.3 * @access private * * @param string $text Log text. * @return void */ private function debug_oc( $text ) { if ( defined( 'LSCWP_LOG' ) ) { self::debug( $text ); return; } if ( Base::VAL_ON2 !== $this->_cfg_debug ) { return; } $litespeed_data_folder = defined( 'LITESPEED_DATA_FOLDER' ) ? LITESPEED_DATA_FOLDER : 'litespeed'; $lscwp_content_dir = defined( 'LSCWP_CONTENT_DIR' ) ? LSCWP_CONTENT_DIR : WP_CONTENT_DIR; $litespeed_static_dir = $lscwp_content_dir . '/' . $litespeed_data_folder; $log_path_prefix = $litespeed_static_dir . '/debug/'; $log_file = $log_path_prefix . Debug2::FilePath( 'debug' ); if ( file_exists( $log_path_prefix . 'index.php' ) && file_exists( $log_file ) ) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log error_log(gmdate('m/d/y H:i:s') . ' - OC - ' . $text . PHP_EOL, 3, $log_file); } } /** * Check if the group belongs to transients or not. * * @since 1.8.3 * @access private * * @param string $group Group name. * @return bool */ private function _is_transients_group( $group ) { return in_array( $group, [ 'transient', 'site-transient' ], true ); } /** * Update WP object cache file config. * * @since 1.8 * @access public * * @param array $options Options to apply after update. * @return void */ public function update_file( $options ) { $changed = false; // NOTE: When included in oc.php, `LSCWP_DIR` will show undefined, so this must be assigned/generated when used. $_oc_ori_file = LSCWP_DIR . 'lib/object-cache.php'; $_oc_wp_file = WP_CONTENT_DIR . '/object-cache.php'; // Update cls file. if ( ! file_exists( $_oc_wp_file ) || md5_file( $_oc_wp_file ) !== md5_file( $_oc_ori_file ) ) { $this->debug_oc( 'copying object-cache.php file to ' . $_oc_wp_file ); copy( $_oc_ori_file, $_oc_wp_file ); $changed = true; } /** * Clear object cache. */ if ( $changed ) { $this->_reconnect( $options ); } } /** * Remove object cache file. * * @since 1.8.2 * @access public * * @return void */ public function del_file() { // NOTE: When included in oc.php, `LSCWP_DIR` will show undefined, so this must be assigned/generated when used. $_oc_ori_file = LSCWP_DIR . 'lib/object-cache.php'; $_oc_wp_file = WP_CONTENT_DIR . '/object-cache.php'; if ( file_exists( $_oc_wp_file ) && md5_file( $_oc_wp_file ) === md5_file( $_oc_ori_file ) ) { $this->debug_oc( 'removing ' . $_oc_wp_file ); wp_delete_file( $_oc_wp_file ); } } /** * Try to build connection. * * @since 1.8 * @access public * * @return bool|null False on failure, true on success, null if unsupported. */ public function test_connection() { return $this->_connect(); } /** * Force to connect with this setting. * * @since 1.8 * @access private * * @param array $cfg Reconnect configuration. * @return void */ private function _reconnect( $cfg ) { $this->debug_oc( 'Reconnecting' ); if ( isset( $this->_conn ) ) { // error_log( 'Object: Quitting existing connection!' ); $this->debug_oc( 'Quitting existing connection' ); $this->flush(); $this->_conn = null; $this->cls( false, true ); } $cls = $this->cls( false, false, $cfg ); $cls->_connect(); if ( isset( $cls->_conn ) ) { $cls->flush(); } } /** * Connect to Memcached/Redis server. * * @since 1.8 * @access private * * @return bool|null False on failure, true on success, null if driver missing. */ private function _connect() { if ( isset( $this->_conn ) ) { // error_log( 'Object: _connected' ); return true; } if ( ! class_exists( $this->_oc_driver ) || ! $this->_cfg_host ) { $this->debug_oc( '_oc_driver cls non existed or _cfg_host missed: ' . $this->_oc_driver . ' [_cfg_host] ' . $this->_cfg_host . ':' . $this->_cfg_port ); return false; } if ( defined( 'LITESPEED_OC_FAILURE' ) ) { $this->debug_oc( 'LITESPEED_OC_FAILURE const defined' ); return false; } $this->debug_oc( 'Init ' . $this->_oc_driver . ' connection to ' . $this->_cfg_host . ':' . $this->_cfg_port ); $failed = false; /** * Connect to Redis. * * @since 1.8.1 * @see https://github.com/phpredis/phpredis/#example-1 */ if ( 'Redis' === $this->_oc_driver ) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler set_error_handler( 'litespeed_exception_handler' ); try { $this->_conn = new \Redis(); // error_log( 'Object: _connect Redis' ); if ( $this->_cfg_persistent ) { if ( $this->_cfg_port ) { $this->_conn->pconnect( $this->_cfg_host, $this->_cfg_port ); } else { $this->_conn->pconnect( $this->_cfg_host ); } } elseif ( $this->_cfg_port ) { $this->_conn->connect( $this->_cfg_host, $this->_cfg_port ); } else { $this->_conn->connect( $this->_cfg_host ); } if ( $this->_cfg_pswd ) { if ( $this->_cfg_user ) { $this->_conn->auth( [ $this->_cfg_user, $this->_cfg_pswd ] ); } else { $this->_conn->auth( $this->_cfg_pswd ); } } if (defined('Redis::OPT_REPLY_LITERAL')) { $this->debug_oc( 'Redis set OPT_REPLY_LITERAL' ); $this->_conn->setOption(\Redis::OPT_REPLY_LITERAL, true); } if ( $this->_cfg_db ) { if ( ! $this->_conn->select( $this->_cfg_db ) ) { $this->debug_oc( 'Database ID is invalid' ); $failed = true; } } $res = $this->_conn->rawCommand('PING'); if ( 'PONG' !== $res ) { $this->debug_oc( 'Redis resp is wrong: ' . $res ); $failed = true; } } catch ( \Exception $e ) { $this->debug_oc( 'Redis connect exception: ' . $e->getMessage() ); $failed = true; } catch ( \ErrorException $e ) { $this->debug_oc( 'Redis connect error: ' . $e->getMessage() ); $failed = true; } restore_error_handler(); } else { // Connect to Memcached. if ( $this->_cfg_persistent ) { $this->_conn = new \Memcached( $this->_get_mem_id() ); // Check memcached persistent connection. if ( $this->_validate_mem_server() ) { // error_log( 'Object: _validate_mem_server' ); $this->debug_oc( 'Got persistent ' . $this->_oc_driver . ' connection' ); return true; } $this->debug_oc( 'No persistent ' . $this->_oc_driver . ' server list!' ); } else { // error_log( 'Object: new memcached!' ); $this->_conn = new \Memcached(); } $this->_conn->addServer( $this->_cfg_host, (int) $this->_cfg_port ); /** * Add SASL auth. * * @since 1.8.1 * @since 2.9.6 Fixed SASL connection @see https://www.litespeedtech.com/support/wiki/doku.php/litespeed_wiki:lsmcd:new_sasl */ if ( $this->_cfg_user && $this->_cfg_pswd && method_exists( $this->_conn, 'setSaslAuthData' ) ) { $this->_conn->setOption( \Memcached::OPT_BINARY_PROTOCOL, true ); $this->_conn->setOption( \Memcached::OPT_COMPRESSION, false ); $this->_conn->setSaslAuthData( $this->_cfg_user, $this->_cfg_pswd ); } // Check connection. if ( ! $this->_validate_mem_server() ) { $failed = true; } } // If failed to connect. if ( $failed ) { $this->debug_oc( '❌ Failed to connect ' . $this->_oc_driver . ' server!' ); $this->_conn = null; $this->_cfg_enabled = false; ! defined( 'LITESPEED_OC_FAILURE' ) && define( 'LITESPEED_OC_FAILURE', true ); // Disable ext OC flag so WP transients fall back to wp_options table. // After muplugins_loaded, all wp_start_object_cache() calls are done — safe to call directly. // Before that (early bootstrap), defer via hook to avoid multisite "Cannot redeclare" fatal. if ( function_exists( 'did_action' ) && did_action( 'muplugins_loaded' ) ) { litespeed_oc_disable_ext_cache(); } elseif ( function_exists( 'add_action' ) ) { add_action( 'muplugins_loaded', 'litespeed_oc_disable_ext_cache', -999 ); } return false; } $this->debug_oc( '✅ Connected to ' . $this->_oc_driver . ' server.' ); return true; } /** * Check if the connected memcached host is the one in cfg. * * @since 1.8 * @access private * * @return bool */ private function _validate_mem_server() { $mem_list = $this->_conn->getStats(); if ( empty( $mem_list ) ) { return false; } foreach ( $mem_list as $k => $v ) { if ( substr( $k, 0, strlen( $this->_cfg_host ) ) !== $this->_cfg_host ) { continue; } if ( ! empty( $v['pid'] ) || ! empty( $v['curr_connections'] ) ) { return true; } } return false; } /** * Get memcached unique id to be used for connecting. * * @since 1.8 * @access private * * @return string */ private function _get_mem_id() { $mem_id = 'litespeed'; if ( is_multisite() ) { $mem_id .= '_' . get_current_blog_id(); } return $mem_id; } /** * Get cache. * * @since 1.8 * @access public * * @param string $key Cache key. * @param string $group Optional. Cache group name. * @return mixed|false */ public function get( $key, $group = '' ) { if ( ! $this->_cfg_enabled ) { return false; } if ( ! $this->_can_cache( $group ) ) { return false; } if ( ! $this->_connect() ) { return false; } $res = $this->_conn->get( $key ); return $res; } /** * Set cache. * * @since 1.8 * @access public * * @param string $key Cache key. * @param mixed $data Data to store. * @param int $expire TTL seconds. * @return bool */ public function set( $key, $data, $expire ) { if ( ! $this->_cfg_enabled ) { return false; } /** * To fix the Cloud callback cached as its frontend call but the hash is generated in backend * Bug found by Stan at Jan/10/2020 */ // if ( ! $this->_can_cache() ) { // return false; // } if ( ! $this->_connect() ) { return false; } // Per WP Object Cache API, expire=0 means "no expiration". // Key eviction is handled by the cache backend (Redis maxmemory / Memcached LRU). $ttl = (int) $expire; if ( 'Redis' === $this->_oc_driver ) { try { $options = ( $ttl > 0 ) ? [ 'ex' => $ttl ] : []; $res = $this->_conn->set( $key, $data, $options ); } catch ( \RedisException $ex ) { $res = false; $msg = sprintf( __( 'Redis encountered a fatal error: %1$s (code: %2$d)', 'litespeed-cache' ), $ex->getMessage(), $ex->getCode() ); $this->debug_oc( $msg ); Admin_Display::error( $msg ); } } else { $res = $this->_conn->set( $key, $data, $ttl ); } return $res; } /** * Check if can cache or not. * * @since 1.8 * @access private * * @param string $group Optional. Cache group name. * @return bool */ private function _can_cache( $group = '' ) { // Transients always use OC regardless of Cache WP-Admin setting if ( $this->_is_transients_group( $group ) ) { return true; } if ( ! $this->_cfg_admin && defined( 'WP_ADMIN' ) ) { return false; } return true; } /** * Delete cache. * * @since 1.8 * @access public * * @param string $key Cache key. * @return bool */ public function delete( $key ) { if ( ! $this->_cfg_enabled ) { return false; } if ( ! $this->_connect() ) { return false; } if ( 'Redis' === $this->_oc_driver ) { $res = $this->_conn->del( $key ); } else { $res = $this->_conn->delete( $key ); } return (bool) $res; } /** * Clear all cache. * * @since 1.8 * @access public * * @return bool */ public function flush() { if ( ! $this->_cfg_enabled ) { $this->debug_oc( 'bypass flushing' ); return false; } if ( ! $this->_connect() ) { return false; } $this->debug_oc( 'flush!' ); if ( 'Redis' === $this->_oc_driver ) { $res = $this->_conn->flushDb(); } else { $res = $this->_conn->flush(); $this->_conn->resetServerList(); } return $res; } /** * Add global groups. * * @since 1.8 * @access public * * @param string|string[] $groups Group(s) to add. * @return void */ public function add_global_groups( $groups ) { if ( ! is_array( $groups ) ) { $groups = [ $groups ]; } $this->_global_groups = array_merge( $this->_global_groups, $groups ); $this->_global_groups = array_unique( $this->_global_groups ); } /** * Check if is in global groups or not. * * @since 1.8 * @access public * * @param string $group Group name. * @return bool */ public function is_global( $group ) { return in_array( $group, $this->_global_groups, true ); } /** * Add non persistent groups. * * @since 1.8 * @access public * * @param string|string[] $groups Group(s) to add. * @return void */ public function add_non_persistent_groups( $groups ) { if ( ! is_array( $groups ) ) { $groups = [ $groups ]; } $this->_non_persistent_groups = array_merge( $this->_non_persistent_groups, $groups ); $this->_non_persistent_groups = array_unique( $this->_non_persistent_groups ); } /** * Check if is in non persistent groups or not. * * @since 1.8 * @access public * * @param string $group Group name. * @return bool */ public function is_non_persistent( $group ) { return in_array( $group, $this->_non_persistent_groups, true ); } } src/data.cls.php000064400000054322152075713340007552 0ustar00> */ private $_db_updater = [ '5.3-a5' => [ 'litespeed_update_5_3' ], '7.0-b26' => [ 'litespeed_update_7' ], '7.0.1-b1' => [ 'litespeed_update_7_0_1' ], '7.7-b28' => [ 'litespeed_update_7_7' ], ]; /** * Versioned DB updaters for per-site options in multisite. * * @var array> */ private $_db_site_updater = [ // '2.0' => [ 'litespeed_update_site_2_0' ], ]; /** * Map from URL-file type to integer code. * * @var array */ private $_url_file_types = [ 'css' => 1, 'js' => 2, 'ccss' => 3, 'ucss' => 4, ]; /** Table: image optimization results. */ const TB_IMG_OPTM = 'litespeed_img_optm'; /** Table: image optimization working queue. */ const TB_IMG_OPTMING = 'litespeed_img_optming'; /** Table: cached avatars. */ const TB_AVATAR = 'litespeed_avatar'; /** Table: crawler URLs. */ const TB_CRAWLER = 'litespeed_crawler'; /** Table: crawler blacklist. */ const TB_CRAWLER_BLACKLIST = 'litespeed_crawler_blacklist'; /** Table: logical URLs. */ const TB_URL = 'litespeed_url'; /** Table: URL → generated file mapping. */ const TB_URL_FILE = 'litespeed_url_file'; /** * Constructor. * * @since 1.3.1 */ public function __construct() {} /** * Ensure required tables exist based on current configuration. * * Called on activation and when options are (re)loaded. * * @since 3.0 * @access public * @return void */ public function correct_tb_existence() { // Gravatar. if ( $this->conf( Base::O_DISCUSS_AVATAR_CACHE ) ) { $this->tb_create( 'avatar' ); } // Crawler. if ( $this->conf( Base::O_CRAWLER ) ) { $this->tb_create( 'crawler' ); $this->tb_create( 'crawler_blacklist' ); } // URL mapping. $this->tb_create( 'url' ); $this->tb_create( 'url_file' ); // Image optm tables are managed on-demand. } /** * Upgrade global configuration/data to match plugin version. * * @since 3.0 * @access public * * @param string $ver Currently stored version string. * @return string|void 'upgrade' on success, or void if no-op. */ public function conf_upgrade( $ver ) { // Skip count check if `Use Primary Site Configurations` is on (deprecated note kept intentionally). if ( $this->_get_upgrade_lock() ) { return; } $this->_set_upgrade_lock( true ); require_once LSCWP_DIR . 'src/data.upgrade.func.php'; // Init log manually. if ( $this->conf( Base::O_DEBUG ) ) { $this->cls( 'Debug2' )->init(); } foreach ( $this->_db_updater as $k => $v ) { if ( version_compare( $ver, $k, '<' ) ) { foreach ( $v as $v2 ) { self::debug( "Updating [ori_v] $ver \t[to] $k \t[func] $v2" ); call_user_func( $v2 ); } } } // Reload options. $this->cls( 'Conf' )->load_options(); $this->correct_tb_existence(); // Update related files. $this->cls( 'Activation' )->update_files(); // Update version to latest. Conf::delete_option( Base::_VER ); Conf::add_option( Base::_VER, Core::VER ); self::debug( 'Updated version to ' . Core::VER ); $this->_set_upgrade_lock( false ); if ( ! defined( 'LSWCP_EMPTYCACHE' ) ) { define( 'LSWCP_EMPTYCACHE', true ); } Purge::purge_all(); return 'upgrade'; } /** * Upgrade per-site configuration/data to match plugin version (multisite). * * @since 3.0 * @access public * * @param string $ver Currently stored version string. * @return void */ public function conf_site_upgrade( $ver ) { if ( $this->_get_upgrade_lock() ) { return; } $this->_set_upgrade_lock( true ); require_once LSCWP_DIR . 'src/data.upgrade.func.php'; foreach ( $this->_db_site_updater as $k => $v ) { if ( version_compare( $ver, $k, '<' ) ) { foreach ( $v as $v2 ) { self::debug( "Updating site [ori_v] $ver \t[to] $k \t[func] $v2" ); call_user_func( $v2 ); } } } // Reload options. $this->cls( 'Conf' )->load_site_options(); Conf::delete_site_option( Base::_VER ); Conf::add_site_option( Base::_VER, Core::VER ); self::debug( 'Updated site_version to ' . Core::VER ); $this->_set_upgrade_lock( false ); if ( ! defined( 'LSWCP_EMPTYCACHE' ) ) { define( 'LSWCP_EMPTYCACHE', true ); } Purge::purge_all(); } /** * Whether an upgrade lock is in effect. * * @since 3.0.1 * @return int|false Timestamp if locked and recent, false otherwise. */ private function _get_upgrade_lock() { $is_upgrading = (int) get_option( 'litespeed.data.upgrading' ); if ( ! $is_upgrading ) { $this->_set_upgrade_lock( false ); // Seed option to avoid repeated DB reads later. } if ( $is_upgrading && ( time() - $is_upgrading ) < 3600 ) { return $is_upgrading; } return false; } /** * Show the upgrading banner if upgrade script is running. * * @since 3.0.1 * @return void */ public function check_upgrading_msg() { $is_upgrading = $this->_get_upgrade_lock(); if ( ! $is_upgrading ) { return; } Admin_Display::info( sprintf( /* translators: %s: time string */ __( 'The database has been upgrading in the background since %s. This message will disappear once upgrade is complete.', 'litespeed-cache' ), '' . Utility::readable_time( $is_upgrading ) . '' ) . ' [LiteSpeed]', true ); } /** * Set/clear the upgrade process lock. * * @since 3.0.1 * * @param bool $lock True to set, false to clear. * @return void */ private function _set_upgrade_lock( $lock ) { if ( ! $lock ) { update_option( 'litespeed.data.upgrading', -1 ); } else { update_option( 'litespeed.data.upgrading', time() ); } } /** * Get a fully-qualified table name by slug. * * @since 3.0 * @access public * * @param string $tb Table slug (e.g., 'url_file'). * @return string|null */ public function tb( $tb ) { global $wpdb; switch ( $tb ) { case 'img_optm': return $wpdb->prefix . self::TB_IMG_OPTM; case 'img_optming': return $wpdb->prefix . self::TB_IMG_OPTMING; case 'avatar': return $wpdb->prefix . self::TB_AVATAR; case 'crawler': return $wpdb->prefix . self::TB_CRAWLER; case 'crawler_blacklist': return $wpdb->prefix . self::TB_CRAWLER_BLACKLIST; case 'url': return $wpdb->prefix . self::TB_URL; case 'url_file': return $wpdb->prefix . self::TB_URL_FILE; default: return null; } } /** * Check if a table exists. * * @since 3.0 * @access public * * @param string $tb Table slug. * @return bool */ public function tb_exist( $tb ) { global $wpdb; $save_state = $wpdb->suppress_errors; $wpdb->suppress_errors( true ); $describe = $wpdb->get_var( 'DESCRIBE `' . $this->tb( $tb ) . '`' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared $wpdb->suppress_errors( $save_state ); return null !== $describe; } /** * Get the SQL structure (columns/indexes) for a given table slug. * * @since 2.0 * @access private * * @param string $tb Table slug. * @return string SQL columns/indexes definition. */ private function _tb_structure( $tb ) { return File::read( LSCWP_DIR . 'src/data_structure/' . $tb . '.sql' ); } /** * Create a table by slug if it doesn't exist. * * @since 3.0 * @access public * * @param string $tb Table slug. * @return void */ public function tb_create( $tb ) { global $wpdb; self::debug2( '[Data] Checking table ' . $tb ); // Check if table exists first. if ( $this->tb_exist( $tb ) ) { self::debug2( '[Data] Existed' ); return; } self::debug( 'Creating ' . $tb ); $sql = sprintf( 'CREATE TABLE IF NOT EXISTS `%1$s` (%2$s) %3$s;', $this->tb( $tb ), $this->_tb_structure( $tb ), $wpdb->get_charset_collate() ); $res = $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared if ( false === $res ) { self::debug( 'Warning! Creating table failed!', $sql ); Admin_Display::error( Error::msg( 'failed_tb_creation', [ '' . $tb . '', '' . $sql . '' ] ) ); } } /** * Drop a table by slug. * * @since 3.0 * @access public * * @param string $tb Table slug. * @return void */ public function tb_del( $tb ) { global $wpdb; if ( ! $this->tb_exist( $tb ) ) { return; } self::debug( 'Deleting table ' . $tb ); $q = 'DROP TABLE IF EXISTS ' . $this->tb( $tb ); $wpdb->query( $q ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared } /** * Drop all generated tables (except image optimization working tables). * * @since 3.0 * @access public * @return void */ public function tables_del() { $this->tb_del( 'avatar' ); $this->tb_del( 'crawler' ); $this->tb_del( 'crawler_blacklist' ); $this->tb_del( 'url' ); $this->tb_del( 'url_file' ); // Deleting img_optm only can be done when destroy all optm images } /** * TRUNCATE a table by slug. * * @since 4.0 * @access public * * @param string $tb Table slug. * @return void */ public function table_truncate( $tb ) { global $wpdb; $q = 'TRUNCATE TABLE ' . $this->tb( $tb ); $wpdb->query( $q ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared } /** * Clean URL-file rows for a given file type and prune orphaned URLs. * * @since 4.0 * @access public * * @param string $file_type One of 'css','js','ccss','ucss'. * @return void */ public function url_file_clean( $file_type ) { global $wpdb; if ( ! $this->tb_exist( 'url_file' ) ) { return; } if ( ! isset( $this->_url_file_types[ $file_type ] ) ) { return; } $type = $this->_url_file_types[ $file_type ]; $tb_url = $this->tb( 'url' ); $tb_url_file = $this->tb( 'url_file' ); // Delete all of this type. $q = "DELETE FROM `$tb_url_file` WHERE `type` = %d"; $wpdb->query( $wpdb->prepare( $q, $type ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared // Prune orphaned rows in URL table. $sql = "DELETE d FROM `{$tb_url}` AS d LEFT JOIN `{$tb_url_file}` AS f ON d.`id` = f.`url_id` WHERE f.`url_id` IS NULL"; $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared } /** * Persist (or rotate) the mapping from URL+vary to a generated file. * * @since 4.0 * @access public * * @param string $request_url Full request URL. * @param string $vary Vary string (may be long; will be md5 if >32). * @param string $file_type One of 'css','js','ccss','ucss'. * @param string $filecon_md5 MD5 of the generated file content. * @param string $path Base path where files live. * @param bool $mobile Whether mapping is for mobile. * @param bool $webp Whether mapping is for webp. * @return void */ public function save_url( $request_url, $vary, $file_type, $filecon_md5, $path, $mobile = false, $webp = false ) { global $wpdb; if ( strlen( $vary ) > 32 ) { $vary = md5( $vary ); } if ( ! isset( $this->_url_file_types[ $file_type ] ) ) { return; } $type = $this->_url_file_types[ $file_type ]; $tb_url = $this->tb( 'url' ); $tb_url_file = $this->tb( 'url_file' ); // Ensure URL row exists. $q = "SELECT * FROM `$tb_url` WHERE url=%s"; $url_row = $wpdb->get_row( $wpdb->prepare( $q, $request_url ), ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared if ( ! $url_row ) { $q = "INSERT INTO `$tb_url` SET url=%s"; $wpdb->query( $wpdb->prepare( $q, $request_url ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared $url_id = (int) $wpdb->insert_id; } else { $url_id = (int) $url_row['id']; } // Active mapping (not expired). $q = "SELECT * FROM `$tb_url_file` WHERE url_id=%d AND vary=%s AND type=%d AND expired=0"; $file_row = $wpdb->get_row( $wpdb->prepare( $q, [ $url_id, $vary, $type ] ), ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared // No change needed if filename matches. if ( $file_row && $file_row['filename'] === $filecon_md5 ) { return; } // If the new file MD5 is currently marked expired elsewhere, clear those records. $q = "DELETE FROM `$tb_url_file` WHERE filename = %s AND expired > 0"; $wpdb->query( $wpdb->prepare( $q, $filecon_md5 ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared // If another live row already uses the same filename, switch current row to that filename. if ( $file_row ) { $q = "SELECT id FROM `$tb_url_file` WHERE filename = %s AND expired = 0 AND id != %d LIMIT 1"; $exists_id = $wpdb->get_var( $wpdb->prepare( $q, [ $file_row['filename'], (int) $file_row['id'] ] ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared if ( $exists_id ) { $q = "UPDATE `$tb_url_file` SET filename=%s WHERE id=%d"; $wpdb->query( $wpdb->prepare( $q, [ $filecon_md5, (int) $file_row['id'] ] ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared return; } } // Insert a new mapping row. $q = "INSERT INTO `$tb_url_file` SET url_id=%d, vary=%s, filename=%s, type=%d, mobile=%d, webp=%d, expired=0"; $wpdb->query( $wpdb->prepare( $q, [ $url_id, $vary, $filecon_md5, $type, $mobile ? 1 : 0, $webp ? 1 : 0 ] ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared // Mark previous mapping as expiring (to be deleted later). if ( $file_row ) { $q = "UPDATE `$tb_url_file` SET expired=%d WHERE id=%d"; $expired = time() + ( 86400 * apply_filters( 'litespeed_url_file_expired_days', 20 ) ); $wpdb->query( $wpdb->prepare( $q, [ $expired, (int) $file_row['id'] ] ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared // Delete already-expired files for this URL. $q = "SELECT * FROM `$tb_url_file` WHERE url_id = %d AND expired BETWEEN 1 AND %d"; $q = $wpdb->prepare( $q, [ $url_id, time() ] ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $list = $wpdb->get_results( $q, ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared if ( $list ) { foreach ( $list as $v ) { $ext = 'js' === $file_type ? 'js' : 'css'; $file_to_del = trailingslashit( $path ) . $v['filename'] . '.' . $ext; if ( file_exists( $file_to_del ) ) { self::debug( 'Delete expired unused file: ' . $file_to_del ); wp_delete_file( $file_to_del ); } } $q = "DELETE FROM `$tb_url_file` WHERE url_id = %d AND expired BETWEEN 1 AND %d"; $wpdb->query( $wpdb->prepare( $q, [ $url_id, time() ] ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared } } } /** * Load the stored filename (md5) for a given URL/vary/type, if active. * * @since 4.0 * @access public * * @param string $request_url Full request URL or tag. * @param string $vary Vary string (may be md5 if previously stored). * @param string $file_type One of 'css','js','ccss','ucss'. * @return string|false Filename md5 (without extension) or false if none. */ public function load_url_file( $request_url, $vary, $file_type ) { global $wpdb; if ( strlen( $vary ) > 32 ) { $vary = md5( $vary ); } if ( ! isset( $this->_url_file_types[ $file_type ] ) ) { return false; } $type = $this->_url_file_types[ $file_type ]; self::debug2( 'load url file: ' . $request_url ); $tb_url = $this->tb( 'url' ); $q = "SELECT * FROM `$tb_url` WHERE url=%s"; $url_row = $wpdb->get_row( $wpdb->prepare( $q, $request_url ), ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared if ( ! $url_row ) { return false; } $url_id = (int) $url_row['id']; $tb_url_file = $this->tb( 'url_file' ); $q = "SELECT * FROM `$tb_url_file` WHERE url_id=%d AND vary=%s AND type=%d AND expired=0"; $file_row = $wpdb->get_row( $wpdb->prepare( $q, [ $url_id, $vary, $type ] ), ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared if ( ! $file_row ) { return false; } return $file_row['filename']; } /** * Mark all UCSS entries of one URL as expired (optionally return existing rows). * * @since 4.5 * @access public * * @param string $request_url Target URL. * @param bool $auto_q If true, return existing active rows before expiring. * @return array Existing rows if $auto_q, otherwise empty array. */ public function mark_as_expired( $request_url, $auto_q = false ) { global $wpdb; $tb_url = $this->tb( 'url' ); self::debug( 'Try to mark as expired: ' . $request_url ); $q = "SELECT * FROM `$tb_url` WHERE url=%s"; $url_row = $wpdb->get_row( $wpdb->prepare( $q, $request_url ), ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared if ( ! $url_row ) { return []; } self::debug( 'Mark url_id=' . $url_row['id'] . ' as expired' ); $tb_url_file = $this->tb( 'url_file' ); $existing_url_files = []; if ( $auto_q ) { $q = "SELECT a.*, b.url FROM `$tb_url_file` a LEFT JOIN `$tb_url` b ON b.id=a.url_id WHERE a.url_id=%d AND a.type=%d AND a.expired=0"; $q = $wpdb->prepare( $q, [ (int) $url_row['id'], $this->_url_file_types['ucss'] ] ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $existing_url_files = $wpdb->get_results( $q, ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared } $q = "UPDATE `$tb_url_file` SET expired=%d WHERE url_id=%d AND type=%d AND expired=0"; $expired = time() + 86400 * apply_filters( 'litespeed_url_file_expired_days', 20 ); $wpdb->query( $wpdb->prepare( $q, [ $expired, (int) $url_row['id'], $this->_url_file_types['ucss'] ] ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared return $existing_url_files; } /** * Merge CSS excludes from file into the given list. * * @since 3.6 * * @param array $list_in Existing list. * @return array */ public function load_css_exc( $list_in ) { $data = $this->_load_per_line( 'css_excludes.txt' ); if ( $data ) { $list_in = array_unique( array_filter( array_merge( $list_in, $data ) ) ); } return $list_in; } /** * Merge CCSS selector whitelist from file into the given list. * * @since 7.1 * * @param array $list_in Existing list. * @return array */ public function load_ccss_whitelist( $list_in ) { $data = $this->_load_per_line( 'ccss_whitelist.txt' ); if ( $data ) { $list_in = array_unique( array_filter( array_merge( $list_in, $data ) ) ); } return $list_in; } /** * Merge UCSS whitelist from file into the given list. * * @since 4.0 * * @param array $list_in Existing list. * @return array */ public function load_ucss_whitelist( $list_in ) { $data = $this->_load_per_line( 'ucss_whitelist.txt' ); if ( $data ) { $list_in = array_unique( array_filter( array_merge( $list_in, $data ) ) ); } return $list_in; } /** * Merge JS excludes from file into the given list. * * @since 3.5 * * @param array $list_in Existing list. * @return array */ public function load_js_exc( $list_in ) { $data = $this->_load_per_line( 'js_excludes.txt' ); if ( $data ) { $list_in = array_unique( array_filter( array_merge( $list_in, $data ) ) ); } return $list_in; } /** * Merge JS defer excludes from file into the given list. * * @since 3.6 * * @param array $list_in Existing list. * @return array */ public function load_js_defer_exc( $list_in ) { $data = $this->_load_per_line( 'js_defer_excludes.txt' ); if ( $data ) { $list_in = array_unique( array_filter( array_merge( $list_in, $data ) ) ); } return $list_in; } /** * Merge OPTM URI excludes from file into the given list. * * @since 5.4 * * @param array $list_in Existing list. * @return array */ public function load_optm_uri_exc( $list_in ) { $data = $this->_load_per_line( 'optm_uri_exc.txt' ); if ( $data ) { $list_in = array_unique( array_filter( array_merge( $list_in, $data ) ) ); } return $list_in; } /** * Merge ESI nonces from file into the given list. * * @since 3.5 * * @param array $list_in Existing list. * @return array */ public function load_esi_nonces( $list_in ) { $data = $this->_load_per_line( 'esi.nonces.txt' ); if ( $data ) { $list_in = array_unique( array_filter( array_merge( $list_in, $data ) ) ); } return $list_in; } /** * Merge "nocacheable" cache keys from file into the given list. * * @since 6.3.0.1 * * @param array $list_in Existing list. * @return array */ public function load_cache_nocacheable( $list_in ) { $data = $this->_load_per_line( 'cache_nocacheable.txt' ); if ( $data ) { $list_in = array_unique( array_filter( array_merge( $list_in, $data ) ) ); } return $list_in; } /** * Load a data file and return non-empty lines, stripping comments. * * Supports: * - `# comment` * - `##comment` * * @since 3.5 * @access private * * @param string $file Relative filename under the plugin /data directory. * @return array */ private function _load_per_line( $file ) { $data = File::read( LSCWP_DIR . 'data/' . $file ); $data = explode( PHP_EOL, $data ); $list = []; foreach ( $data as $v ) { // Drop two kinds of comments. if ( false !== strpos( $v, '##' ) ) { $v = trim( substr( $v, 0, strpos( $v, '##' ) ) ); } if ( false !== strpos( $v, '# ' ) ) { $v = trim( substr( $v, 0, strpos( $v, '# ' ) ) ); } if ( ! $v ) { continue; } $list[] = $v; } return $list; } } src/cdn/cloudflare.cls.php000064400000020360152075713340011520 0ustar00 */ namespace LiteSpeed\CDN; use LiteSpeed\Base; use LiteSpeed\Debug2; use LiteSpeed\Router; use LiteSpeed\Admin; use LiteSpeed\Admin_Display; defined('WPINC') || exit(); /** * Class Cloudflare * * @since 2.1 */ class Cloudflare extends Base { const TYPE_PURGE_ALL = 'purge_all'; const TYPE_GET_DEVMODE = 'get_devmode'; const TYPE_SET_DEVMODE_ON = 'set_devmode_on'; const TYPE_SET_DEVMODE_OFF = 'set_devmode_off'; const ITEM_STATUS = 'status'; /** * Update zone&name based on latest settings * * @since 3.0 * @access public */ public function try_refresh_zone() { if (!$this->conf(self::O_CDN_CLOUDFLARE)) { return; } $zone = $this->fetch_zone(); if ($zone) { $this->cls('Conf')->update(self::O_CDN_CLOUDFLARE_NAME, $zone['name']); $this->cls('Conf')->update(self::O_CDN_CLOUDFLARE_ZONE, $zone['id']); Debug2::debug("[Cloudflare] Get zone successfully \t\t[ID] " . $zone['id']); } else { $this->cls('Conf')->update(self::O_CDN_CLOUDFLARE_ZONE, ''); Debug2::debug('[Cloudflare] ❌ Get zone failed, clean zone'); } } /** * Get Cloudflare development mode * * @since 1.7.2 * @access private * @param bool $show_msg Whether to show success/error message. */ private function get_devmode( $show_msg = true ) { Debug2::debug('[Cloudflare] get_devmode'); $zone = $this->zone(); if (!$zone) { return; } $url = 'https://api.cloudflare.com/client/v4/zones/' . $zone . '/settings/development_mode'; $res = $this->cloudflare_call($url, 'GET', false, $show_msg); if (!$res) { return; } Debug2::debug('[Cloudflare] get_devmode result ', $res); // Make sure is array: #992174 $curr_status = self::get_option(self::ITEM_STATUS, array()); if ( ! is_array( $curr_status ) ) { $curr_status = array(); } $curr_status['devmode'] = $res['value']; $curr_status['devmode_expired'] = (int) $res['time_remaining'] + time(); // update status self::update_option(self::ITEM_STATUS, $curr_status); } /** * Set Cloudflare development mode * * @since 1.7.2 * @access private * @param string $type The type of development mode to set (on/off). */ private function set_devmode( $type ) { Debug2::debug('[Cloudflare] set_devmode'); $zone = $this->zone(); if (!$zone) { return; } $url = 'https://api.cloudflare.com/client/v4/zones/' . $zone . '/settings/development_mode'; $new_val = self::TYPE_SET_DEVMODE_ON === $type ? 'on' : 'off'; $data = array( 'value' => $new_val ); $res = $this->cloudflare_call($url, 'PATCH', $data); if (!$res) { return; } $res = $this->get_devmode(false); if ($res) { $msg = sprintf(__('Notified Cloudflare to set development mode to %s successfully.', 'litespeed-cache'), strtoupper($new_val)); Admin_Display::success($msg); } } /** * Shortcut to purge Cloudflare * * @since 7.1 * @access public * @param string|bool $reason The reason for purging, or false if none. */ public static function purge_all( $reason = false ) { if ($reason) { Debug2::debug('[Cloudflare] purge call because: ' . $reason); } self::cls()->purge_all_private(); } /** * Purge Cloudflare cache * * @since 1.7.2 * @access private */ private function purge_all_private() { Debug2::debug('[Cloudflare] purge_all_private'); $cf_on = $this->conf(self::O_CDN_CLOUDFLARE); if (!$cf_on) { $msg = __('Cloudflare API is set to off.', 'litespeed-cache'); Admin_Display::error($msg); return; } $zone = $this->zone(); if (!$zone) { return; } $url = 'https://api.cloudflare.com/client/v4/zones/' . $zone . '/purge_cache'; $data = array( 'purge_everything' => true ); $res = $this->cloudflare_call($url, 'DELETE', $data); if ($res) { $msg = __('Notified Cloudflare to purge all successfully.', 'litespeed-cache'); Admin_Display::success($msg); } } /** * Get current Cloudflare zone from cfg * * @since 1.7.2 * @access private */ private function zone() { $zone = $this->conf(self::O_CDN_CLOUDFLARE_ZONE); if (!$zone) { $msg = __('No available Cloudflare zone', 'litespeed-cache'); Admin_Display::error($msg); return false; } return $zone; } /** * Get Cloudflare zone settings * * @since 1.7.2 * @access private */ private function fetch_zone() { $kw = $this->conf(self::O_CDN_CLOUDFLARE_NAME); $url = 'https://api.cloudflare.com/client/v4/zones?status=active&match=all'; // Try exact match first if ($kw && false !== strpos($kw, '.')) { $zones = $this->cloudflare_call($url . '&name=' . $kw, 'GET', false, false); if ($zones) { Debug2::debug('[Cloudflare] fetch_zone exact matched'); return $zones[0]; } } // Can't find, try to get default one $zones = $this->cloudflare_call($url, 'GET', false, false); if (!$zones) { Debug2::debug('[Cloudflare] fetch_zone no zone'); return false; } if (!$kw) { Debug2::debug('[Cloudflare] fetch_zone no set name, use first one by default'); return $zones[0]; } foreach ($zones as $v) { if (false !== strpos($v['name'], $kw)) { Debug2::debug('[Cloudflare] fetch_zone matched ' . $kw . ' [name] ' . $v['name']); return $v; } } // Can't match current name, return default one Debug2::debug('[Cloudflare] fetch_zone failed match name, use first one by default'); return $zones[0]; } /** * Cloudflare API * * @since 1.7.2 * @access private * @param string $url The API URL to call. * @param string $method The HTTP method to use (GET, POST, etc.). * @param array|bool $data The data to send with the request, or false if none. * @param bool $show_msg Whether to show success/error message. */ private function cloudflare_call( $url, $method = 'GET', $data = false, $show_msg = true ) { Debug2::debug("[Cloudflare] cloudflare_call \t\t[URL] $url"); /** * Detect key type: Global API Key (37-char hex) vs API Token (Bearer) * @since 1.9.0 */ $cf_key = $this->conf( self::O_CDN_CLOUDFLARE_KEY ); if ( strlen( $cf_key ) === 37 && preg_match( '/^[0-9a-f]+$/', $cf_key ) ) { $headers = [ 'Content-Type' => 'application/json', 'X-Auth-Email' => $this->conf( self::O_CDN_CLOUDFLARE_EMAIL ), 'X-Auth-Key' => $cf_key, ]; } else { $headers = [ 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $cf_key, ]; } $wp_args = array( 'method' => $method, 'headers' => $headers, ); if ($data) { if (is_array($data)) { $data = wp_json_encode($data); } $wp_args['body'] = $data; } add_filter( 'http_api_curl', $fn = function ( $handle ) { defined( 'CURLOPT_SSL_ENABLE_ALPN' ) && \curl_setopt( $handle, CURLOPT_SSL_ENABLE_ALPN, false ); return $handle; }, 9999 ); $resp = wp_remote_request( $url, $wp_args ); remove_filter( 'http_api_curl', $fn, 9999 ); if (is_wp_error($resp)) { Debug2::debug('[Cloudflare] error in response'); if ($show_msg) { $msg = __('Failed to communicate with Cloudflare', 'litespeed-cache'); Admin_Display::error($msg); } return false; } $result = wp_remote_retrieve_body($resp); $json = \json_decode($result, true); if ($json && $json['success'] && $json['result']) { Debug2::debug('[Cloudflare] cloudflare_call called successfully'); if ($show_msg) { $msg = __('Communicated with Cloudflare successfully.', 'litespeed-cache'); Admin_Display::success($msg); } return $json['result']; } Debug2::debug("[Cloudflare] cloudflare_call called failed: $result"); if ($show_msg) { $msg = __('Failed to communicate with Cloudflare', 'litespeed-cache'); Admin_Display::error($msg); } return false; } /** * Handle all request actions from main cls * * @since 1.7.2 * @access public */ public function handler() { $type = Router::verify_type(); switch ($type) { case self::TYPE_PURGE_ALL: $this->purge_all_private(); break; case self::TYPE_GET_DEVMODE: $this->get_devmode(); break; case self::TYPE_SET_DEVMODE_ON: case self::TYPE_SET_DEVMODE_OFF: $this->set_devmode($type); break; default: break; } Admin::redirect(); } } src/cdn/quic.cls.php000064400000006022152075713340010340 0ustar00force = $force; } if (!$this->conf(self::O_CDN_QUIC)) { if (!empty($cloud_summary['conf_md5'])) { self::debug('❌ No QC CDN, clear conf md5!'); Cloud::save_summary(array( 'conf_md5' => '' )); } return false; } // Notice: Sync conf must be after `wp_loaded` hook, to get 3rd party vary injected (e.g. `woocommerce_cart_hash`). if (!did_action('wp_loaded')) { add_action('wp_loaded', array( $this, 'try_sync_conf' ), 999); self::debug('WP not loaded yet, delay sync to wp_loaded:999'); return; } $options = $this->get_options(); $options['_tp_cookies'] = apply_filters('litespeed_vary_cookies', array()); // Build necessary options only $options_needed = array( self::O_CACHE_DROP_QS, self::O_CACHE_EXC_COOKIES, self::O_CACHE_EXC_USERAGENTS, self::O_CACHE_LOGIN_COOKIE, self::O_CACHE_VARY_COOKIES, self::O_CACHE_MOBILE_RULES, self::O_CACHE_MOBILE, self::O_CACHE_BROWSER, self::O_CACHE_TTL_BROWSER, self::O_IMG_OPTM_WEBP, self::O_GUEST, '_tp_cookies', ); $consts_needed = array( 'LSWCP_TAG_PREFIX' ); $options_for_md5 = array(); foreach ($options_needed as $v) { if (isset($options[$v])) { $options_for_md5[$v] = $options[$v]; // Remove overflow multi lines fields if (is_array($options_for_md5[$v]) && count($options_for_md5[$v]) > 30) { $options_for_md5[$v] = array_slice($options_for_md5[$v], 0, 30); } } } $server_vars = $this->server_vars(); foreach ($consts_needed as $v) { if (isset($server_vars[$v])) { if (empty($options_for_md5['_server'])) { $options_for_md5['_server'] = array(); } $options_for_md5['_server'][$v] = $server_vars[$v]; } } $conf_md5 = md5(wp_json_encode($options_for_md5)); if (!empty($cloud_summary['conf_md5'])) { if ($conf_md5 === $cloud_summary['conf_md5']) { if (!$this->force) { self::debug('Bypass sync conf to QC due to same md5', $conf_md5); return; } self::debug('!!!Force sync conf even same md5'); } else { self::debug('[conf_md5] ' . $conf_md5 . ' [existing_conf_md5] ' . $cloud_summary['conf_md5']); } } Cloud::save_summary(array( 'conf_md5' => $conf_md5 )); self::debug('sync conf to QC'); Cloud::post(Cloud::SVC_D_SYNC_CONF, $options_for_md5); } } src/conf.cls.php000064400000047040152075713340007565 0ustar00 */ private $_updated_ids = []; /** * Whether current blog is the network primary site. * * @var bool */ private $_is_primary = false; /** * Specify init logic to avoid infinite loop when calling conf.cls instance * * @since 3.0 * @access public * @return void */ public function init() { // Check if conf exists or not. If not, create them in DB (won't change version if is converting v2.9- data) // Conf may be stale, upgrade later $this->_conf_db_init(); /** * Detect if has quic.cloud set * * @since 2.9.7 */ if ( $this->conf( self::O_CDN_QUIC ) ) { if ( ! defined( 'LITESPEED_ALLOWED' ) ) { define( 'LITESPEED_ALLOWED', true ); } } add_action( 'litespeed_conf_append', [ $this, 'option_append' ], 10, 2 ); add_action( 'litespeed_conf_force', [ $this, 'force_option' ], 10, 2 ); $this->define_cache(); } /** * Init conf related data * * @since 3.0 * @access private * @return void */ private function _conf_db_init() { /** * Try to load options first, network sites can override this later * * NOTE: Load before run `conf_upgrade()` to avoid infinite loop when getting conf in `conf_upgrade()` */ $this->load_options(); // Check if debug is on // Init debug as early as possible if ( $this->conf( Base::O_DEBUG ) ) { $this->cls( 'Debug2' )->init(); } $ver = $this->conf( self::_VER ); /** * Version is less than v3.0, or, is a new installation */ $ver_check_tag = 'new'; if ( $ver ) { if ( ! defined( 'LSCWP_CUR_V' ) ) { define( 'LSCWP_CUR_V', $ver ); } /** * Upgrade conf */ if ( Core::VER !== $ver ) { // Plugin version will be set inside // Site plugin upgrade & version change will do in load_site_conf $ver_check_tag = Data::cls()->conf_upgrade( $ver ); } } /** * Sync latest new options */ if ( ! $ver || Core::VER !== $ver ) { // Load default values $this->load_default_vals(); if ( ! $ver ) { // New install $this->set_conf( self::$_default_options ); $ver_check_tag .= ' activate' . ( defined( 'LSCWP_REF' ) ? '_' . constant( 'LSCWP_REF' ) : '' ); } // Init new default/missing options foreach ( self::$_default_options as $k => $v ) { // If the option existed, bypass updating // Bcos we may ask clients to deactivate for debug temporarily, we need to keep the current cfg in deactivation, hence we need to only try adding default cfg when activating. self::add_option( $k, $v ); } // Force correct version in case a rare unexpected case that `_ver` exists but empty self::update_option( Base::_VER, Core::VER ); if ( $ver_check_tag ) { Cloud::version_check( $ver_check_tag ); } } /** * Network sites only * * Override conf if is network subsites and chose `Use Primary Config` */ $this->_try_load_site_options(); // Check if debug is on // Init debug as early as possible if ( $this->conf( Base::O_DEBUG ) ) { $this->cls( 'Debug2' )->init(); } // Mark as conf loaded if ( ! defined( 'LITESPEED_CONF_LOADED' ) ) { define( 'LITESPEED_CONF_LOADED', true ); } if ( ! $ver || Core::VER !== $ver ) { // Only trigger once in upgrade progress, don't run always $this->update_confs(); // Files only get corrected in activation or saving settings actions. } } /** * Load all latest options from DB * * @since 3.0 * @access public * * @param int|null $blog_id Blog ID to load from. Null for current. * @param bool $dry_run Return options instead of setting them. * @return array|void */ public function load_options( $blog_id = null, $dry_run = false ) { $options = []; foreach ( self::$_default_options as $k => $v ) { if ( null !== $blog_id ) { $options[ $k ] = self::get_blog_option( $blog_id, $k, $v ); } else { $options[ $k ] = self::get_option( $k, $v ); } // Correct value type. $options[ $k ] = $this->type_casting( $options[ $k ], $k ); } if ( $dry_run ) { return $options; } // Bypass site special settings if ( null !== $blog_id ) { // This is to load the primary settings ONLY // These options are the ones that can be overwritten by primary $options = array_diff_key( $options, array_flip( self::$single_site_options ) ); $this->set_primary_conf( $options ); } else { $this->set_conf( $options ); } // Append const options if ( defined( 'LITESPEED_CONF' ) && LITESPEED_CONF ) { foreach ( self::$_default_options as $k => $v ) { $const = Base::conf_const( $k ); if ( defined( $const ) ) { $this->set_const_conf( $k, $this->type_casting( constant( $const ), $k ) ); } } } } /** * For multisite installations, the single site options need to be updated with the network wide options. * * @since 1.0.13 * @access private * @return void */ private function _try_load_site_options() { if ( ! $this->_if_need_site_options() ) { return; } $this->_conf_site_db_init(); $this->_is_primary = BLOG_ID_CURRENT_SITE === get_current_blog_id(); // If network set to use primary setting if ( $this->network_conf( self::NETWORK_O_USE_PRIMARY ) && ! $this->_is_primary ) { // subsites or network admin // Get the primary site settings // If it's just upgraded, 2nd blog is being visited before primary blog, can just load default config (won't hurt as this could only happen shortly) $this->load_options( BLOG_ID_CURRENT_SITE ); } // Overwrite single blog options with site options foreach ( self::$_default_options as $k => $v ) { if ( ! $this->has_network_conf( $k ) ) { continue; } // $this->_options[ $k ] = $this->_network_options[ $k ]; // Special handler to `Enable Cache` option if the value is set to OFF if ( self::O_CACHE === $k ) { if ( $this->_is_primary ) { if ( $this->conf( $k ) !== $this->network_conf( $k ) ) { if ( self::VAL_ON2 !== $this->conf( $k ) ) { continue; } } } elseif ( $this->network_conf( self::NETWORK_O_USE_PRIMARY ) ) { if ( $this->has_primary_conf( $k ) && self::VAL_ON2 !== $this->primary_conf( $k ) ) { // This case will use primary_options override always continue; } } elseif ( self::VAL_ON2 !== $this->conf( $k ) ) { continue; } } // primary_options will store primary settings + network settings, OR, store the network settings for subsites $this->set_primary_conf( $k, $this->network_conf( $k ) ); } // var_dump($this->_options); } /** * Check if needs to load site_options for network sites * * @since 3.0 * @access private * @return bool */ private function _if_need_site_options() { if ( ! is_multisite() ) { return false; } // Check if needs to use site_options or not // todo: check if site settings are separate bcos it will affect .htaccess /** * In case this is called outside the admin page * * @see https://codex.wordpress.org/Function_Reference/is_plugin_active_for_network * @since 2.0 */ if ( ! function_exists( 'is_plugin_active_for_network' ) ) { require_once ABSPATH . '/wp-admin/includes/plugin.php'; } // If is not activated on network, it will not have site options if ( ! is_plugin_active_for_network( Core::PLUGIN_FILE ) ) { if ( self::VAL_ON2 === (int) $this->conf( self::O_CACHE ) ) { // Default to cache on $this->set_conf( self::_CACHE, true ); } return false; } return true; } /** * Init site conf and upgrade if necessary * * @since 3.0 * @access private * @return void */ private function _conf_site_db_init() { $this->load_site_options(); $ver = $this->network_conf( self::_VER ); /** * Don't upgrade or run new installations other than from backend visit * In this case, just use default conf */ if ( ! $ver || Core::VER !== $ver ) { if ( ! is_admin() && ! defined( 'LITESPEED_CLI' ) ) { $this->set_network_conf( $this->load_default_site_vals() ); return; } } /** * Upgrade conf */ if ( $ver && Core::VER !== $ver ) { // Site plugin version will change inside Data::cls()->conf_site_upgrade( $ver ); } /** * Is a new installation */ if ( ! $ver || Core::VER !== $ver ) { // Load default values $this->load_default_site_vals(); // Init new default/missing options foreach ( self::$_default_site_options as $k => $v ) { // If the option existed, bypass updating self::add_site_option( $k, $v ); } } } /** * Get the plugin's site wide options. * * If the site wide options are not set yet, set it to default. * * @since 1.0.2 * @access public * @return null|void */ public function load_site_options() { if ( ! is_multisite() ) { return null; } // Load all site options foreach ( self::$_default_site_options as $k => $v ) { $val = self::get_site_option( $k, $v ); $val = $this->type_casting( $val, $k, true ); $this->set_network_conf( $k, $val ); } } /** * Append a 3rd party option to default options * * This will not be affected by network use primary site setting. * * NOTE: If it is a multi switch option, need to call `_conf_multi_switch()` first * * @since 3.0 * @access public * * @param string $name Option name. * @param mixed $default_val Default value. * @return void */ public function option_append( $name, $default_val ) { self::$_default_options[ $name ] = $default_val; $this->set_conf( $name, self::get_option( $name, $default_val ) ); $this->set_conf( $name, $this->type_casting( $this->conf( $name ), $name ) ); } /** * Force an option to a certain value * * @since 2.6 * @access public * * @param string $k Option key. * @param mixed $v Option value. * @return void */ public function force_option( $k, $v ) { if ( ! $this->has_conf( $k ) ) { return; } $v = $this->type_casting( $v, $k ); if ( $this->conf( $k ) === $v ) { return; } // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export Debug2::debug( '[Conf] ** ' . $k . ' forced from ' . var_export( $this->conf( $k ), true ) . ' to ' . var_export( $v, true ) ); $this->set_conf( $k, $v ); } /** * Define `_CACHE` const in options ( for both single and network ) * * @since 3.0 * @access public * @return void */ public function define_cache() { // Init global const cache on setting $this->set_conf( self::_CACHE, false ); if ( self::VAL_ON === (int) $this->conf( self::O_CACHE ) || $this->conf( self::O_CDN_QUIC ) ) { $this->set_conf( self::_CACHE, true ); } // Check network if ( ! $this->_if_need_site_options() ) { // Set cache on $this->_define_cache_on(); return; } // If use network setting if ( self::VAL_ON2 === (int) $this->conf( self::O_CACHE ) && $this->network_conf( self::O_CACHE ) ) { $this->set_conf( self::_CACHE, true ); } $this->_define_cache_on(); } /** * Define `LITESPEED_ON` * * @since 2.1 * @access private * @return void */ private function _define_cache_on() { if ( ! $this->conf( self::_CACHE ) ) { return; } if ( defined( 'LITESPEED_ALLOWED' ) && ! defined( 'LITESPEED_ON' ) ) { define( 'LITESPEED_ON', true ); } } /** * Save option * * @since 3.0 * @access public * * @param array $the_matrix Option-value map. * @return void */ public function update_confs( $the_matrix = [] ) { if ( $the_matrix ) { foreach ( $the_matrix as $id => $val ) { $this->update( $id, $val ); } } if ( $this->_updated_ids ) { foreach ( $this->_updated_ids as $id ) { // Check if need to do a purge all or not if ( $this->_conf_purge_all( $id ) ) { Purge::purge_all( 'conf changed [id] ' . $id ); } // Check if need to purge a tag $tag = $this->_conf_purge_tag( $id ); if ( $tag ) { Purge::add( $tag ); } // Update cron if ( $this->_conf_cron( $id ) ) { $this->cls( 'Task' )->try_clean( $id ); } // Reset crawler bypassed list when any of the options WebP replace, guest mode, or cache mobile got changed if ( self::O_IMG_OPTM_WEBP === $id || self::O_GUEST === $id || self::O_CACHE_MOBILE === $id ) { $this->cls( 'Crawler' )->clear_disabled_list(); } } } do_action( 'litespeed_update_confs', $the_matrix ); // Update related tables $this->cls( 'Data' )->correct_tb_existence(); // Update related files $this->cls( 'Activation' )->update_files(); /** * CDN related actions - Cloudflare */ $this->cls( 'CDN\Cloudflare' )->try_refresh_zone(); // If Server IP changed, must test echo if ( in_array( self::O_SERVER_IP, $this->_updated_ids, true ) ) { $this->cls( 'Cloud' )->init_qc_cli(); } // CDN related actions - QUIC.cloud $this->cls( 'CDN\Quic' )->try_sync_conf(); } /** * Save option * * Note: this is direct save, won't trigger corresponding file update or data sync. To save settings normally, always use `Conf->update_confs()` * * @since 3.0 * @access public * * @param string $id Option ID. * @param mixed $val Option value. * @return void */ public function update( $id, $val ) { // Bypassed this bcos $this->_options could be changed by force_option() // if ( $this->_options[ $id ] === $val ) { // return; // } if ( self::_VER === $id ) { return; } if ( self::O_SERVER_IP === $id ) { if ( $val && ! Utility::valid_ipv4( $val ) ) { $msg = sprintf( __( 'Saving option failed. IPv4 only for %s.', 'litespeed-cache' ), Lang::title( Base::O_SERVER_IP ) ); Admin_Display::error( $msg ); return; } } if ( ! array_key_exists( $id, self::$_default_options ) ) { if ( defined( 'LSCWP_LOG' ) ) { Debug2::debug( '[Conf] Invalid option ID ' . $id ); } return; } if ( $val && $this->_conf_pswd( $id ) && ! preg_match( '/[^\*]/', (string) $val ) ) { return; } // Special handler for CDN Original URLs if ( self::O_CDN_ORI === $id && ! $val ) { $site_url = site_url( '/' ); $parsed = wp_parse_url( $site_url ); if ( !empty( $parsed['scheme'] ) ) { $site_url = str_replace( $parsed['scheme'] . ':', '', $site_url ); } $val = $site_url; } // Validate type $val = $this->type_casting( $val, $id ); // Save data self::update_option( $id, $val ); // Handle purge if setting changed if ( $this->conf( $id ) !== $val ) { $this->_updated_ids[] = $id; // Check if need to fire a purge or not (Here has to stay inside `update()` bcos need comparing old value) if ( $this->_conf_purge( $id ) ) { $old = (array) $this->conf( $id ); $new = (array) $val; $diff = array_merge( array_diff( $new, $old ), array_diff( $old, $new ) ); // If has difference foreach ( $diff as $v ) { $v = ltrim( (string) $v, '^' ); $v = rtrim( (string) $v, '$' ); $this->cls( 'Purge' )->purge_url( $v ); } } } // Update in-memory data $this->set_conf( $id, $val ); } /** * Save network option * * @since 3.0 * @access public * * @param string $id Option ID. * @param mixed $val Option value. * @return void */ public function network_update( $id, $val ) { if ( ! array_key_exists( $id, self::$_default_site_options ) ) { if ( defined( 'LSCWP_LOG' ) ) { Debug2::debug( '[Conf] Invalid network option ID ' . $id ); } return; } if ( $val && $this->_conf_pswd( $id ) && ! preg_match( '/[^\*]/', (string) $val ) ) { return; } // Validate type if ( is_bool( self::$_default_site_options[ $id ] ) ) { $max = $this->_conf_multi_switch( $id ); if ( $max && $val > 1 ) { $val %= ( $max + 1 ); } else { $val = (bool) $val; } } elseif ( is_array( self::$_default_site_options[ $id ] ) ) { // from textarea input if ( ! is_array( $val ) ) { $val = Utility::sanitize_lines( $val, $this->_conf_filter( $id ) ); } } elseif ( ! is_string( self::$_default_site_options[ $id ] ) ) { $val = (int) $val; } else { // Check if the string has a limit set $val = $this->_conf_string_val( $id, $val ); } // Save data self::update_site_option( $id, $val ); // Handle purge if setting changed if ( $this->network_conf( $id ) !== $val ) { // Check if need to do a purge all or not if ( $this->_conf_purge_all( $id ) ) { Purge::purge_all( '[Conf] Network conf changed [id] ' . $id ); } // Update in-memory data $this->set_network_conf( $id, $val ); } // No need to update cron here, Cron will register in each init if ( $this->has_conf( $id ) ) { $this->set_conf( $id, $val ); } } /** * Check if one user role is in exclude optimization group settings * * @since 1.6 * @access public * * @param string|null $role The user role. * @return string|false The set value if already set, otherwise false. */ public function in_optm_exc_roles( $role = null ) { // Get user role if ( null === $role ) { $role = Router::get_role(); } if ( ! $role ) { return false; } $roles = explode( ',', $role ); $found = array_intersect( $roles, $this->conf( self::O_OPTM_EXC_ROLES ) ); return $found ? implode( ',', $found ) : false; } /** * Set one config value directly * * @since 2.9 * @access private * @return void */ private function _set_conf() { /** * NOTE: For URL Query String setting, * 1. If append lines to an array setting e.g. `cache-force_uri`, use `set[cache-force_uri][]=the_url`. * 2. If replace the array setting with one line, use `set[cache-force_uri]=the_url`. * 3. If replace the array setting with multi lines value, use 2 then 1. */ // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput $raw = !empty( $_GET[ self::TYPE_SET ] ) ? $_GET[ self::TYPE_SET ] : false; if ( !$raw || ! is_array( $raw ) ) { return; } // Sanitize the incoming matrix. $the_matrix = []; foreach ( $raw as $id => $v ) { if ( ! $this->has_conf( $id ) ) { continue; } // Append new item to array type settings if ( is_array( $v ) && is_array( $this->conf( $id ) ) ) { $v = array_merge( $this->conf( $id ), $v ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export Debug2::debug( '[Conf] Appended to settings [' . $id . ']: ' . var_export( $v, true ) ); } else { // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export Debug2::debug( '[Conf] Set setting [' . $id . ']: ' . var_export( $v, true ) ); } $the_matrix[ $id ] = $v; } if ( !$the_matrix ) { return; } $this->update_confs( $the_matrix ); $msg = __( 'Changed setting successfully.', 'litespeed-cache' ); Admin_Display::success( $msg ); // Redirect if changed frontend URL // phpcs:ignore WordPress.Security.NonceVerification.Recommended $redirect = ! empty( $_GET['redirect'] ) ? sanitize_text_field( wp_unslash( $_GET['redirect'] ) ) : ''; if ( $redirect ) { wp_safe_redirect( $redirect ); exit; } } /** * Handle all request actions from main cls * * @since 2.9 * @access public * @return void */ public function handler() { $type = Router::verify_type(); switch ( $type ) { case self::TYPE_SET: $this->_set_conf(); break; default: break; } Admin::redirect(); } } src/doc.cls.php000064400000012711152075713340007402 0ustar00'; echo wp_kses_post( '⚠️ ' . sprintf( __( 'This setting is %1$s for certain qualifying requests due to %2$s!', 'litespeed-cache' ), '' . esc_html__( 'ON', 'litespeed-cache' ) . '', esc_html( Lang::title( Base::O_GUEST_OPTM ) ) ) ); self::learn_more( 'https://docs.litespeedtech.com/lscache/lscwp/general/#guest-optimization' ); echo ''; } /** * Warn that changes affect the crawler list. * * @since 4.3 * @return void */ public static function crawler_affected() { echo ''; echo '⚠️ ' . esc_html__( 'This setting will regenerate crawler list and clear the disabled list!', 'litespeed-cache' ); echo ''; } /** * Privacy policy text for front-end disclosure. * * @since 2.2.7 * * @return string Safe HTML string. */ public static function privacy_policy() { $text = esc_html__( 'This site utilizes caching in order to facilitate a faster response time and better user experience. Caching potentially stores a duplicate copy of every web page that is on display on this site. All cache files are temporary, and are never accessed by any third party, except as necessary to obtain technical support from the cache plugin vendor. Cache files expire on a schedule set by the site administrator, but may easily be purged by the admin before their natural expiration, if necessary. We may use QUIC.cloud services to process & cache your data temporarily.', 'litespeed-cache' ); $link = sprintf( /* translators: %s: QUIC.cloud privacy policy URL */ esc_html__( 'Please see %s for more details.', 'litespeed-cache' ), sprintf( '
%1$s', esc_url( 'https://quic.cloud/privacy-policy/' ) ) ); // Return as HTML (link already escaped). return $text . ' ' . $link; } /** * Render (or return) a "Learn more" link. * * @since 2.4.2 * * @param string $url Destination URL. * @param string $title Optional link text. Defaults to "Learn More". * @param bool $self_tab Open in self tab or new tab (adds target/_blank + rel). * @param string $css_class CSS class for the anchor. * @param bool $return_output Return instead of echo. * @return string|void */ public static function learn_more( $url, $title = '', $self_tab = false, $css_class = '', $return_output = false ) { $css_class = $css_class ? $css_class : 'litespeed-learn-more'; $title = $title ? $title : esc_html__( 'Learn More', 'litespeed-cache' ); $target_rel = $self_tab ? '' : ' target="_blank" rel="noopener noreferrer"'; $anchor = sprintf( ' %s', esc_url( $url ), $target_rel, // Already hardcoded/safe. esc_attr( $css_class ), wp_kses_post( $title ) ); if ( $return_output ) { return $anchor; } echo wp_kses_post( $anchor ); } /** * Output "One per line." helper text. * * @since 3.0 * * @param bool $return_output Return the string instead of echoing. * @return string|void */ public static function one_per_line( $return_output = false ) { $str = esc_html__( 'One per line.', 'litespeed-cache' ); if ( $return_output ) { return $str; } echo esc_html( $str ); } /** * Output helper text about full/partial URL support. * * @since 3.4 * * @param bool $string_only If true, say "strings" only; otherwise specify URLs/strings. * @return void */ public static function full_or_partial_url( $string_only = false ) { if ( $string_only ) { echo esc_html__( 'Both full and partial strings can be used.', 'litespeed-cache' ); } else { echo esc_html__( 'Both full URLs and partial strings can be used.', 'litespeed-cache' ); } } /** * Notice that a setting will edit .htaccess. * * @since 3.0 * @return void */ public static function notice_htaccess() { echo ''; echo '⚠️ ' . esc_html__( 'This setting will edit the .htaccess file.', 'litespeed-cache' ) . ' '; self::learn_more( 'https://docs.litespeedtech.com/lscache/lscwp/toolbox/#edit-htaccess-tab' ); echo ''; } /** * Gentle reminder that QUIC.cloud queues are asynchronous. * * @since 5.3.1 * * @param bool $return_output Return the HTML instead of echoing. * @return string|void */ public static function queue_issues( $return_output = false ) { $link = self::learn_more( 'https://docs.litespeedtech.com/lscache/lscwp/troubleshoot/#quiccloud-queue-issues', '', false, '', true ); $html = sprintf( '
%s %s
', esc_html__( 'The queue is processed asynchronously. It may take time.', 'litespeed-cache' ), $link // already escaped. ); if ( $return_output ) { return $html; } echo wp_kses_post( $html ); } } composer.lock000064400000054727152075713340007273 0ustar00{ "_readme": [ "This file locks the dependencies of your project to a known state", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], "content-hash": "8c6cb907d697cb733facab6d72af1add", "packages": [], "packages-dev": [ { "name": "dealerdirect/phpcodesniffer-composer-installer", "version": "v1.0.0", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/composer-installer.git", "reference": "4be43904336affa5c2f70744a348312336afd0da" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da", "reference": "4be43904336affa5c2f70744a348312336afd0da", "shasum": "" }, "require": { "composer-plugin-api": "^1.0 || ^2.0", "php": ">=5.4", "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" }, "require-dev": { "composer/composer": "*", "ext-json": "*", "ext-zip": "*", "php-parallel-lint/php-parallel-lint": "^1.3.1", "phpcompatibility/php-compatibility": "^9.0", "yoast/phpunit-polyfills": "^1.0" }, "type": "composer-plugin", "extra": { "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" }, "autoload": { "psr-4": { "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Franck Nijhof", "email": "franck.nijhof@dealerdirect.com", "homepage": "http://www.frenck.nl", "role": "Developer / IT Manager" }, { "name": "Contributors", "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" } ], "description": "PHP_CodeSniffer Standards Composer Installer Plugin", "homepage": "http://www.dealerdirect.com", "keywords": [ "PHPCodeSniffer", "PHP_CodeSniffer", "code quality", "codesniffer", "composer", "installer", "phpcbf", "phpcs", "plugin", "qa", "quality", "standard", "standards", "style guide", "stylecheck", "tests" ], "support": { "issues": "https://github.com/PHPCSStandards/composer-installer/issues", "source": "https://github.com/PHPCSStandards/composer-installer" }, "time": "2023-01-05T11:28:13+00:00" }, { "name": "php-stubs/wordpress-stubs", "version": "v6.8.1", "source": { "type": "git", "url": "https://github.com/php-stubs/wordpress-stubs.git", "reference": "92e444847d94f7c30f88c60004648f507688acd5" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/92e444847d94f7c30f88c60004648f507688acd5", "reference": "92e444847d94f7c30f88c60004648f507688acd5", "shasum": "" }, "conflict": { "phpdocumentor/reflection-docblock": "5.6.1" }, "require-dev": { "dealerdirect/phpcodesniffer-composer-installer": "^1.0", "nikic/php-parser": "^5.4", "php": "^7.4 || ^8.0", "php-stubs/generator": "^0.8.3", "phpdocumentor/reflection-docblock": "^5.4.1", "phpstan/phpstan": "^2.1", "phpunit/phpunit": "^9.5", "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^1.1.1", "wp-coding-standards/wpcs": "3.1.0 as 2.3.0" }, "suggest": { "paragonie/sodium_compat": "Pure PHP implementation of libsodium", "symfony/polyfill-php80": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", "szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan" }, "type": "library", "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "description": "WordPress function and class declaration stubs for static analysis.", "homepage": "https://github.com/php-stubs/wordpress-stubs", "keywords": [ "PHPStan", "static analysis", "wordpress" ], "support": { "issues": "https://github.com/php-stubs/wordpress-stubs/issues", "source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.8.1" }, "time": "2025-05-02T12:33:34+00:00" }, { "name": "php-stubs/wp-cli-stubs", "version": "v2.12.0", "source": { "type": "git", "url": "https://github.com/php-stubs/wp-cli-stubs.git", "reference": "af16401e299a3fd2229bd0fa9a037638a4174a9d" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/php-stubs/wp-cli-stubs/zipball/af16401e299a3fd2229bd0fa9a037638a4174a9d", "reference": "af16401e299a3fd2229bd0fa9a037638a4174a9d", "shasum": "" }, "require": { "php-stubs/wordpress-stubs": "^4.7 || ^5.0 || ^6.0" }, "require-dev": { "php": "~7.3 || ~8.0", "php-stubs/generator": "^0.8.0" }, "suggest": { "symfony/polyfill-php73": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", "szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan" }, "type": "library", "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "description": "WP-CLI function and class declaration stubs for static analysis.", "homepage": "https://github.com/php-stubs/wp-cli-stubs", "keywords": [ "PHPStan", "static analysis", "wordpress", "wp-cli" ], "support": { "issues": "https://github.com/php-stubs/wp-cli-stubs/issues", "source": "https://github.com/php-stubs/wp-cli-stubs/tree/v2.12.0" }, "time": "2025-06-10T09:58:05+00:00" }, { "name": "phpcompatibility/php-compatibility", "version": "9.3.5", "source": { "type": "git", "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", "reference": "9fb324479acf6f39452e0655d2429cc0d3914243" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243", "reference": "9fb324479acf6f39452e0655d2429cc0d3914243", "shasum": "" }, "require": { "php": ">=5.3", "squizlabs/php_codesniffer": "^2.3 || ^3.0.2" }, "conflict": { "squizlabs/php_codesniffer": "2.6.2" }, "require-dev": { "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0" }, "suggest": { "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.", "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." }, "type": "phpcodesniffer-standard", "notification-url": "https://packagist.org/downloads/", "license": [ "LGPL-3.0-or-later" ], "authors": [ { "name": "Wim Godden", "homepage": "https://github.com/wimg", "role": "lead" }, { "name": "Juliette Reinders Folmer", "homepage": "https://github.com/jrfnl", "role": "lead" }, { "name": "Contributors", "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors" } ], "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.", "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", "keywords": [ "compatibility", "phpcs", "standards" ], "support": { "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues", "source": "https://github.com/PHPCompatibility/PHPCompatibility" }, "time": "2019-12-27T09:44:58+00:00" }, { "name": "phpcsstandards/phpcsextra", "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", "reference": "46d08eb86eec622b96c466adec3063adfed280dd" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/46d08eb86eec622b96c466adec3063adfed280dd", "reference": "46d08eb86eec622b96c466adec3063adfed280dd", "shasum": "" }, "require": { "php": ">=5.4", "phpcsstandards/phpcsutils": "^1.0.9", "squizlabs/php_codesniffer": "^3.12.1" }, "require-dev": { "php-parallel-lint/php-console-highlighter": "^1.0", "php-parallel-lint/php-parallel-lint": "^1.3.2", "phpcsstandards/phpcsdevcs": "^1.1.6", "phpcsstandards/phpcsdevtools": "^1.2.1", "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" }, "type": "phpcodesniffer-standard", "extra": { "branch-alias": { "dev-stable": "1.x-dev", "dev-develop": "1.x-dev" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "LGPL-3.0-or-later" ], "authors": [ { "name": "Juliette Reinders Folmer", "homepage": "https://github.com/jrfnl", "role": "lead" }, { "name": "Contributors", "homepage": "https://github.com/PHPCSStandards/PHPCSExtra/graphs/contributors" } ], "description": "A collection of sniffs and standards for use with PHP_CodeSniffer.", "keywords": [ "PHP_CodeSniffer", "phpcbf", "phpcodesniffer-standard", "phpcs", "standards", "static analysis" ], "support": { "issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues", "security": "https://github.com/PHPCSStandards/PHPCSExtra/security/policy", "source": "https://github.com/PHPCSStandards/PHPCSExtra" }, "funding": [ { "url": "https://github.com/PHPCSStandards", "type": "github" }, { "url": "https://github.com/jrfnl", "type": "github" }, { "url": "https://opencollective.com/php_codesniffer", "type": "open_collective" }, { "url": "https://thanks.dev/u/gh/phpcsstandards", "type": "thanks_dev" } ], "time": "2025-04-20T23:35:32+00:00" }, { "name": "phpcsstandards/phpcsutils", "version": "1.0.12", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", "reference": "87b233b00daf83fb70f40c9a28692be017ea7c6c" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/87b233b00daf83fb70f40c9a28692be017ea7c6c", "reference": "87b233b00daf83fb70f40c9a28692be017ea7c6c", "shasum": "" }, "require": { "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0", "php": ">=5.4", "squizlabs/php_codesniffer": "^3.10.0 || 4.0.x-dev@dev" }, "require-dev": { "ext-filter": "*", "php-parallel-lint/php-console-highlighter": "^1.0", "php-parallel-lint/php-parallel-lint": "^1.3.2", "phpcsstandards/phpcsdevcs": "^1.1.6", "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0" }, "type": "phpcodesniffer-standard", "extra": { "branch-alias": { "dev-stable": "1.x-dev", "dev-develop": "1.x-dev" } }, "autoload": { "classmap": [ "PHPCSUtils/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "LGPL-3.0-or-later" ], "authors": [ { "name": "Juliette Reinders Folmer", "homepage": "https://github.com/jrfnl", "role": "lead" }, { "name": "Contributors", "homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors" } ], "description": "A suite of utility functions for use with PHP_CodeSniffer", "homepage": "https://phpcsutils.com/", "keywords": [ "PHP_CodeSniffer", "phpcbf", "phpcodesniffer-standard", "phpcs", "phpcs3", "standards", "static analysis", "tokens", "utility" ], "support": { "docs": "https://phpcsutils.com/", "issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues", "security": "https://github.com/PHPCSStandards/PHPCSUtils/security/policy", "source": "https://github.com/PHPCSStandards/PHPCSUtils" }, "funding": [ { "url": "https://github.com/PHPCSStandards", "type": "github" }, { "url": "https://github.com/jrfnl", "type": "github" }, { "url": "https://opencollective.com/php_codesniffer", "type": "open_collective" } ], "time": "2024-05-20T13:34:27+00:00" }, { "name": "squizlabs/php_codesniffer", "version": "3.13.0", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", "reference": "65ff2489553b83b4597e89c3b8b721487011d186" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/65ff2489553b83b4597e89c3b8b721487011d186", "reference": "65ff2489553b83b4597e89c3b8b721487011d186", "shasum": "" }, "require": { "ext-simplexml": "*", "ext-tokenizer": "*", "ext-xmlwriter": "*", "php": ">=5.4.0" }, "require-dev": { "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" }, "bin": [ "bin/phpcbf", "bin/phpcs" ], "type": "library", "extra": { "branch-alias": { "dev-master": "3.x-dev" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { "name": "Greg Sherwood", "role": "Former lead" }, { "name": "Juliette Reinders Folmer", "role": "Current lead" }, { "name": "Contributors", "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" } ], "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", "keywords": [ "phpcs", "standards", "static analysis" ], "support": { "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" }, "funding": [ { "url": "https://github.com/PHPCSStandards", "type": "github" }, { "url": "https://github.com/jrfnl", "type": "github" }, { "url": "https://opencollective.com/php_codesniffer", "type": "open_collective" }, { "url": "https://thanks.dev/u/gh/phpcsstandards", "type": "thanks_dev" } ], "time": "2025-05-11T03:36:00+00:00" }, { "name": "wp-coding-standards/wpcs", "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", "reference": "9333efcbff231f10dfd9c56bb7b65818b4733ca7" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/9333efcbff231f10dfd9c56bb7b65818b4733ca7", "reference": "9333efcbff231f10dfd9c56bb7b65818b4733ca7", "shasum": "" }, "require": { "ext-filter": "*", "ext-libxml": "*", "ext-tokenizer": "*", "ext-xmlreader": "*", "php": ">=5.4", "phpcsstandards/phpcsextra": "^1.2.1", "phpcsstandards/phpcsutils": "^1.0.10", "squizlabs/php_codesniffer": "^3.9.0" }, "require-dev": { "php-parallel-lint/php-console-highlighter": "^1.0.0", "php-parallel-lint/php-parallel-lint": "^1.3.2", "phpcompatibility/php-compatibility": "^9.0", "phpcsstandards/phpcsdevtools": "^1.2.0", "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" }, "suggest": { "ext-iconv": "For improved results", "ext-mbstring": "For improved results" }, "type": "phpcodesniffer-standard", "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Contributors", "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors" } ], "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions", "keywords": [ "phpcs", "standards", "static analysis", "wordpress" ], "support": { "issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues", "source": "https://github.com/WordPress/WordPress-Coding-Standards", "wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki" }, "funding": [ { "url": "https://opencollective.com/php_codesniffer", "type": "custom" } ], "time": "2024-03-25T16:39:00+00:00" } ], "aliases": [], "minimum-stability": "stable", "stability-flags": {}, "prefer-stable": true, "prefer-lowest": false, "platform": {}, "platform-dev": {}, "plugin-api-version": "2.6.0" } thirdparty/wplister.cls.php000064400000001546152075713340012115 0ustar00' . __('Please consider disabling the following detected plugins, as they may conflict with LiteSpeed Cache:', 'litespeed-cache') . '

' . 'PageSpeed Ninja' . '

' . '
' ); } } /** * Handle plugin activation. * * @since 4.7 * @param string $plugin Plugin path. * @param bool $network_wide Whether activated network-wide. * @return void */ public static function activated_plugin( $plugin, $network_wide ) { self::incompatible_plugin_notice($plugin, $network_wide, 'activated'); } /** * Handle plugin deactivation. * * @since 4.7 * @param string $plugin Plugin path. * @param bool $network_wide Whether deactivated network-wide. * @return void */ public static function deactivated_plugin( $plugin, $network_wide ) { self::incompatible_plugin_notice($plugin, $network_wide, 'deactivated'); } /** * Detect any incompatible plugins that are currently `active` and `valid`. * Show a notification if there are any. * * @since 4.7 * @param string $plugin Plugin path. * @param bool $_network_wide Whether action is network-wide. * @param string $action Action type (activated|deactivated). * @return void */ public static function incompatible_plugin_notice( $plugin, $_network_wide, $action ) { self::update_messages(); $deactivated = 'deactivated' === $action ? array( $plugin ) : array(); $incompatible_plugins = array_map(function ( $plugin ) { return WP_PLUGIN_DIR . '/' . $plugin; }, array_diff(self::$_incompatible_plugins, $deactivated)); $active_incompatible_plugins = array_map(function ( $plugin ) { $plugin = get_plugin_data($plugin, false, true); return $plugin['Name']; }, array_intersect($incompatible_plugins, wp_get_active_and_valid_plugins())); if (empty($active_incompatible_plugins)) { return; } \LiteSpeed\Admin_Display::error( '
' . __('Please consider disabling the following detected plugins, as they may conflict with LiteSpeed Cache:', 'litespeed-cache') . '

' . implode(', ', $active_incompatible_plugins) . '

' . '
', false, true ); } /** * Prevent multiple incompatible plugin notices. * * @since 4.7 * @return void */ private static function update_messages() { $messages = \LiteSpeed\Admin_Display::get_option(\LiteSpeed\Admin_Display::DB_MSG_PIN, array()); if (is_array($messages)) { foreach ($messages as $index => $message) { if (strpos($message, self::$_msg_id) !== false) { unset($messages[$index]); if (!$messages) { $messages = -1; } \LiteSpeed\Admin_Display::update_option(\LiteSpeed\Admin_Display::DB_MSG_PIN, $messages); break; } } } } } thirdparty/wcml.cls.php000064400000003367152075713340011211 0ustar00 class Caldera_Forms_Render_Nonce do_action('litespeed_nonce', 'caldera_forms_front_*'); } } thirdparty/beaver-builder.cls.php000064400000002167152075713340013134 0ustar00 0) { add_filter( 'litespeed_vary_check_commenter_pending', '__return_false' ); } } } thirdparty/wptouch.cls.php000064400000001557152075713340011737 0ustar00is_mobile_device ) ) { add_filter( 'litespeed_is_mobile', '__return_true' ); } } } thirdparty/woocommerce.content.tpl.php000064400000007637152075713340014261 0ustar00

', '' ); ?>

esc_html__( 'Purge product on changes to the quantity or stock status.', 'litespeed-cache' ) . ' ' . esc_html__( 'Purge categories only when stock status changes.', 'litespeed-cache' ), self::O_PS_CS => esc_html__( 'Purge product and categories only when the stock status changes.', 'litespeed-cache' ), self::O_PS_CN => esc_html__( 'Purge product only when the stock status changes.', 'litespeed-cache' ) . ' ' . esc_html__( 'Do not purge categories on changes to the quantity or stock status.', 'litespeed-cache' ), self::O_PQS_CQS => esc_html__( 'Always purge both product and categories on changes to the quantity or stock status.', 'litespeed-cache' ), ]; $conf = (int) apply_filters( 'litespeed_conf', $setting_id ); do_action( 'litespeed_setting_enroll', $setting_id ); foreach ( $options as $k => $v ) : $input_id = 'conf_' . $setting_id . '_' . $k; ?>
/>
cls( 'Admin_Display' )->build_switch( $setting_id ); ?>

thirdparty/amp.cls.php000064400000004704152075713340011020 0ustar00add_hooks(); } /** * Add hooks to woo actions. * * @since 1.6.3 * @access public * @return void */ public function add_hooks() { $this->_option_append(); $this->esi_enabled = (bool) apply_filters('litespeed_esi_status', false); add_action('litespeed_control_finalize', [ $this, 'set_control' ]); add_action('litespeed_tag_finalize', [ $this, 'set_tag' ]); // Purge affected product caches when WooCommerce stock changes, including cancellation restocks. add_action('woocommerce_product_set_stock', [ $this, 'purge_product' ]); add_action('woocommerce_variation_set_stock', [ $this, 'purge_product' ]); // #984479 Update variations stock add_action( 'woocommerce_product_set_stock_status', [ $this, 'purge_product_stock_status' ], 10, 3 ); add_action( 'woocommerce_variation_set_stock_status', [ $this, 'purge_product_stock_status' ], 10, 3 ); add_action( 'woocommerce_order_status_cancelled', [ $this, 'purge_cancelled_order_products' ], 20 ); add_action('comment_post', [ $this, 'add_review' ], 10, 3); if ( $this->esi_enabled ) { if ( function_exists('is_shop') && ! is_shop() ) { add_action('litespeed_tpl_normal', [ $this, 'set_block_template' ]); // No need for add-to-cart button // add_action( 'litespeed_esi_load-wc-add-to-cart-form', [ $this, 'load_add_to_cart_form_block' ] ) ; add_action('litespeed_esi_load-storefront-cart-header', [ $this, 'load_cart_header' ]); add_action('litespeed_esi_load-widget', [ $this, 'register_post_view' ]); } if ( function_exists('is_product') && is_product() ) { add_filter('litespeed_esi_params', [ $this, 'add_post_id' ], 10, 2); } // #612331 - remove WooCommerce geolocation redirect on ESI page (PR#708) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- ESI parameter detection, not processing user input. if (!empty($_GET[ESI::QS_ACTION]) && !empty($_GET[ESI::QS_PARAMS])) { remove_action( 'template_redirect', [ 'WC_Cache_Helper', 'geolocation_ajax_redirect' ], 10 ); } } if ( is_admin() ) { add_action('litespeed_api_purge_post', [ $this, 'backend_purge' ]); // todo add_action('delete_term_relationships', [ $this, 'delete_rel' ], 10, 2); add_action('litespeed_settings_tab', [ $this, 'settings_add_tab' ]); add_action('litespeed_settings_content', [ $this, 'settings_add_content' ]); add_filter('litespeed_widget_default_options', [ $this, 'wc_widget_default' ], 10, 2); } if ( apply_filters('litespeed_conf', self::O_CART_VARY) ) { add_filter( 'litespeed_vary_cookies', /** * Modify list of vary cookies. * * @param string[] $cookie_list List of vary cookies. * @return string[] */ function ( $cookie_list ) { $cookie_list[] = 'woocommerce_cart_hash'; return array_unique( $cookie_list ); } ); } } /** * Purge esi private tag. * * @since 1.6.3 * @access public * @return void */ public function purge_esi() { do_action('litespeed_debug', '3rd woo purge ESI in action: ' . current_filter()); do_action('litespeed_purge_private_esi', 'storefront-cart-header'); } /** * Purge private all. * * @since 3.0 * @access public * @return void */ public function purge_private_all() { do_action('litespeed_purge_private_all'); } /** * Check if need to give an ESI block for cart. * * @since 1.7.2 * @access public * * @param string $template Template path. * @return string Template path. */ public function check_if_need_esi( $template ) { if ( $this->vary_needed() ) { do_action('litespeed_debug', 'API: 3rd woo added ESI'); add_action('litespeed_tpl_normal', [ $this, 'set_swap_header_cart' ]); } return $template; } /** * Keep vary on if cart is not empty. * * @since 1.7.2 * @access public * * @param array $vary Vary array. * @return array */ public function vary_maintain( $vary ) { if ( $this->vary_needed() ) { do_action('litespeed_debug', 'API: 3rd woo added vary due to cart not empty'); $vary['woo_cart'] = 1; } return $vary; } /** * Check if vary need to be on based on cart. * * @since 1.7.2 * @access private * @return bool True when cart has items. */ private function vary_needed() { if ( ! function_exists('WC') ) { return false; } $woocom = WC(); if ( ! $woocom ) { return false; } if ( is_null( $woocom->cart ) ) { return false; } return $woocom->cart->get_cart_contents_count() > 0; } /** * Hooked to the litespeed_is_not_esi_template action. * If the request is not an esi request, set a hook in woocommerce_before_template_part to see if it's something we can ESI. * * @since 1.1.0 * @access public * @return void */ public function set_block_template() { add_action('woocommerce_before_template_part', [ $this, 'block_template' ], 999, 4); } /** * Hooked to the litespeed_is_not_esi_template action. * If the request is not an esi request, set a hook in storefront_header to see if it's something we can ESI. * * Will remove storefront_header_cart in storefront_header. * * @since 1.1.0 * @since 1.6.3 Removed static * @access public * @return void */ public function set_swap_header_cart() { $priority = has_action('storefront_header', 'storefront_header_cart'); if ( false !== $priority ) { remove_action('storefront_header', 'storefront_header_cart', $priority); add_action('storefront_header', [ $this, 'esi_cart_header' ], $priority); } } /** * Hooked to the woocommerce_before_template_part action. * Checks if the template contains 'add-to-cart'. If so, and if we want to ESI the request, block it and build the ESI code block. * * The function parameters will be passed to the ESI request. * * @since 1.1.0 * @access public * * @param string $template_name Template name. * @param string $template_path Template path. * @param string $located Located template path. * @param array $args Template args. * @return void */ public function block_template( $template_name, $template_path, $located, $args ) { if ( false === strpos( $template_name, 'add-to-cart' ) ) { if ( false !== strpos( $template_name, 'related.php' ) ) { remove_action('woocommerce_before_template_part', [ $this, 'block_template' ], 999); add_filter('woocommerce_related_products_args', [ $this, 'add_related_tags' ]); add_action('woocommerce_after_template_part', [ $this, 'end_template' ], 999); } return; } // Build ESI block for add-to-cart. // $post_obj = get_post( get_the_ID() ); // if ( ! $post_obj ) { // return; // } // $params = [ // self::ESI_PARAM_ARGS => $args, // self::ESI_PARAM_NAME => $template_name, // self::ESI_PARAM_POSTID => $post_obj->ID, // self::ESI_PARAM_PATH => $template_path, // self::ESI_PARAM_LOCATED => $located, // ]; // add_action('woocommerce_after_add_to_cart_form', [ $this, 'end_form' ]); // add_action('woocommerce_after_template_part', [ $this, 'end_form' ], 999); // // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Filter returns a URL; escaped below. // echo esc_url( apply_filters( 'litespeed_esi_url', 'wc-add-to-cart-form', 'WC_CART_FORM', $params ) ); // // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Allow basic wrapper markup. // echo wp_kses_post( apply_filters( 'litespeed_clean_wrapper_begin', '' ) ); } /** * Hooked to the woocommerce_after_add_to_cart_form action. * If this is hit first, clean the buffer and remove this function and end_template. * * @since 1.1.0 * @since 1.6.3 Removed static * @access public * * @param string $template_name Template name. * @return void */ public function end_form( $template_name = '' ) { if ( ! empty( $template_name ) && false === strpos( $template_name, 'add-to-cart' ) ) { return; } // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Allow basic wrapper markup. echo wp_kses_post( apply_filters( 'litespeed_clean_wrapper_end', '' ) ); remove_action('woocommerce_after_add_to_cart_form', [ $this, 'end_form' ]); remove_action('woocommerce_after_template_part', [ $this, 'end_form' ], 999); } /** * If related products are loaded, need to add the extra product ids. * * The page will be purged if any of the products are changed. * * @since 1.1.0 * @since 1.6.3 Removed static * @access public * * @param array $args The arguments used to build the related products section. * @return array The unchanged arguments. */ public function add_related_tags( $args ) { if ( empty( $args ) || ! isset( $args['post__in'] ) ) { return $args; } $related_posts = $args['post__in']; foreach ( $related_posts as $related ) { do_action('litespeed_tag_add_post', $related); } return $args; } /** * Hooked to the woocommerce_after_template_part action. * If the template contains 'related.php', restore block hook. * * @since 1.1.0 * @since 1.6.3 Removed static * @access public * * @param string $template_name Template name. * @return void */ public function end_template( $template_name ) { if ( false !== strpos( $template_name, 'related.php' ) ) { remove_action('woocommerce_after_template_part', [ $this, 'end_template' ], 999); $this->set_block_template(); } } /** * Hooked to the storefront_header header. * If we want to ESI the request, block it and build the ESI code block. * * @since 1.1.0 * @since 1.6.3 Removed static * @access public * @return void */ public function esi_cart_header() { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Filter returns URL. echo apply_filters( 'litespeed_esi_url', 'storefront-cart-header', 'STOREFRONT_CART_HEADER' ); } /** * Hooked to the litespeed_esi_load-storefront-cart-header action. * Generates the cart header for ESI display. * * @since 1.1.0 * @since 1.6.3 Removed static * @access public * @return void */ public function load_cart_header() { storefront_header_cart(); } /** * Hooked to the litespeed_esi_load-wc-add-to-cart-form action. * Parses the ESI input parameters and generates the add to cart form for ESI display. * * @since 1.1.0 * @since 1.6.3 Removed static * @access public * * @param array $params ESI parameters. * @return void */ public function load_add_to_cart_form_block( $params ) { global $post, $wp_query; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Intentional for WooCommerce template setup. $post = get_post($params[self::ESI_PARAM_POSTID]); $wp_query->setup_postdata($post); function_exists('wc_get_template') && wc_get_template($params[self::ESI_PARAM_NAME], $params[self::ESI_PARAM_ARGS], $params[self::ESI_PARAM_PATH]); } /** * Update WooCommerce when someone visits a product and has the recently viewed products widget. * * Currently, this widget should not be cached. * * @since 1.1.0 * @since 1.6.3 Removed static * @access public * * @param array $params Widget parameter array. * @return void */ public function register_post_view( $params ) { if ( 'WC_Widget_Recently_Viewed' !== $params[ API::PARAM_NAME ] ) { return; } if ( ! isset( $params[ self::ESI_PARAM_POSTID ] ) ) { return; } $id = (int) $params[ self::ESI_PARAM_POSTID ]; $esi_post = get_post( $id ); $product = function_exists('wc_get_product') ? wc_get_product( $esi_post ) : false; if ( empty( $product ) ) { return; } // Setup post data for tracking view without directly overriding $post. if ( $esi_post ) { setup_postdata( $esi_post ); } if ( function_exists('wc_track_product_view') ) { wc_track_product_view(); } } /** * Adds the post id to the widget ESI parameters for the Recently Viewed widget. * * This is needed in the ESI request to update the cookie properly. * * @since 1.1.0 * @access public * * @param array $params Existing ESI params. * @param string $block_id Block identifier. * @return array */ public function add_post_id( $params, $block_id ) { if ( 'widget' === $block_id ) { if ( 'WC_Widget_Recently_Viewed' === $params[ API::PARAM_NAME ] ) { $params[ self::ESI_PARAM_POSTID ] = get_the_ID(); } } return $params; } /** * Hooked to the litespeed_widget_default_options filter. * * The recently viewed widget must be ESI to function properly. * This function will set it to enable and no cache by default. * * @since 1.1.0 * @access public * * @param array $options Widget options. * @param mixed $widget Widget instance. * @return array */ public function wc_widget_default( $options, $widget ) { if ( ! is_array( $options ) ) { return $options; } $widget_name = get_class( $widget ); if ( 'WC_Widget_Recently_Viewed' === $widget_name ) { $options[ API::WIDGET_O_ESIENABLE ] = API::VAL_ON2; $options[ API::WIDGET_O_TTL ] = 0; } elseif ( 'WC_Widget_Recent_Reviews' === $widget_name ) { $options[ API::WIDGET_O_ESIENABLE ] = API::VAL_ON; $options[ API::WIDGET_O_TTL ] = 86400; } return $options; } /** * Set WooCommerce cache tags based on page type. * * @since 1.0.9 * @since 1.6.3 Removed static * @access public * @return void */ public function set_tag() { $id = get_the_ID(); if ( false === $id ) { return; } // Check if product has a cache ttl limit or not. $sale_from = (int) get_post_meta( $id, '_sale_price_dates_from', true ); $sale_to = (int) get_post_meta( $id, '_sale_price_dates_to', true ); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested -- Need local timestamp for WooCommerce sale dates. $now = current_time('timestamp'); $ttl = false; if ( $sale_from && $now < $sale_from ) { $ttl = $sale_from - $now; } elseif ( $sale_to && $now < $sale_to ) { $ttl = $sale_to - $now; } if ( $ttl && $ttl < apply_filters( 'litespeed_control_ttl', 0 ) ) { do_action( 'litespeed_control_set_ttl', $ttl, "WooCommerce set scheduled TTL to $ttl" ); } if ( function_exists('is_shop') && is_shop() ) { do_action('litespeed_tag_add', self::CACHETAG_SHOP); } if ( function_exists('is_product_taxonomy') && ! is_product_taxonomy() ) { return; } if ( isset( $GLOBALS['product_cat'] ) && is_string( $GLOBALS['product_cat'] ) ) { // todo: need to check previous woo version to find if its from old woo versions or not! $term = get_term_by( 'slug', $GLOBALS['product_cat'], 'product_cat' ); } elseif ( isset( $GLOBALS['product_tag'] ) && is_string( $GLOBALS['product_tag'] ) ) { $term = get_term_by( 'slug', $GLOBALS['product_tag'], 'product_tag' ); } else { $term = false; } if ( false === $term ) { return; } while ( isset( $term ) ) { do_action( 'litespeed_tag_add', self::CACHETAG_TERM . $term->term_id ); if ( 0 === (int) $term->parent ) { break; } $term = get_term( $term->parent ); } } /** * Check if the page is cacheable according to WooCommerce. * * @since 1.0.5 * @since 1.6.3 Removed static * @access public * * @param string $esi_id The ESI block id if a request is an ESI request. * @return void */ public function set_control( $esi_id ) { if ( ! apply_filters( 'litespeed_control_cacheable', false ) ) { return; } /** * Avoid possible 500 issue. * * @since 1.6.2.1 */ if ( ! function_exists( 'WC' ) ) { return; } $woocom = WC(); if ( ! $woocom || empty( $woocom->session ) ) { return; } // For later versions, DONOTCACHEPAGE should be set. // No need to check uri/qs. if ( version_compare( $woocom->version, '1.4.2', '>=' ) ) { if ( version_compare( $woocom->version, '3.2.0', '<' ) && defined('DONOTCACHEPAGE') && DONOTCACHEPAGE ) { do_action( 'litespeed_control_set_nocache', '3rd party woocommerce not cache by constant' ); return; } elseif ( version_compare( $woocom->version, '2.1.0', '>=' ) ) { $err = false; if ( ! function_exists( 'wc_get_page_id' ) ) { return; } /** * From woo/inc/class-wc-cache-helper.php:prevent_caching() * * @since 1.4 */ $page_ids = array_filter( [ wc_get_page_id( 'cart' ), wc_get_page_id( 'checkout' ), wc_get_page_id( 'myaccount' ) ] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( isset( $_GET['download_file'] ) || isset( $_GET['add-to-cart'] ) || is_page( $page_ids ) ) { $err = 'woo non cacheable pages'; } elseif ( function_exists( 'wc_notice_count' ) && wc_notice_count() > 0 ) { $err = 'has wc notice'; } if ( $err ) { do_action( 'litespeed_control_set_nocache', '3rd party woocommerce not cache due to ' . $err ); return; } } return; } if ( ! isset( $_SERVER['REQUEST_URI'] ) ) { return; } $uri = esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ); $uri_len = strlen( $uri ); if ( $uri_len < 5 ) { return; } if ( in_array( $uri, [ 'cart/', 'checkout/', 'my-account/', 'addons/', 'logout/', 'lost-password/', 'product/' ], true ) ) { // why contains `product`? do_action( 'litespeed_control_set_nocache', 'uri in cart/account/user pages' ); return; } if ( ! isset( $_SERVER['QUERY_STRING'] ) ) { return; } $qs = sanitize_text_field( wp_unslash( $_SERVER['QUERY_STRING'] ) ); $qs_len = strlen( $qs ); if ( ! empty( $qs ) && $qs_len >= 12 && 0 === strpos( $qs, 'add-to-cart=' ) ) { do_action( 'litespeed_control_set_nocache', 'qs contains add-to-cart' ); } } /** * Purge a product when WooCommerce stock status changes. * * Variation "Save changes" updates can trigger status-based hooks. Route those * events to the same purge path, but mark this as a stock-status change so * config logic handles restock (out-of-stock -> in-stock) correctly. * * @since 7.7 * @param int $product_id Product ID. * @param string|null $stock_status New stock status. * @param \WC_Product $product Product object. * @return void */ public function purge_product_stock_status( $product_id, $stock_status = null, $product = null ) { if ( ! $product && function_exists( 'wc_get_product' ) ) { $product = wc_get_product( $product_id ); } if ( ! $product ) { return; } do_action( 'litespeed_debug', '[3rd] Woo Purge stock status changed [pid] ' . $product->get_id() . ' [status] ' . $stock_status ); $this->purge_product( $product, true ); } /** * Purge managed-stock products when a WooCommerce order is cancelled. * * Cancellation restores stock quantities. Purge affected product pages to * prevent stale frontend stock display. * * @since 7.7 * @param int $order_id WooCommerce order ID. * @return void */ public function purge_cancelled_order_products( $order_id ) { if ( ! function_exists( 'wc_get_order' ) ) { return; } $order = wc_get_order( $order_id ); if ( ! $order ) { return; } $products = []; foreach ( $order->get_items( 'line_item' ) as $item ) { $product = $item->get_product(); if ( ! $product || ! $product->managing_stock() ) { continue; } $products[ $product->get_id() ] = $product; } foreach ( $products as $product ) { do_action( 'litespeed_debug', '[3rd] Woo Purge cancelled order managed stock [pid] ' . $product->get_id() ); $this->purge_product( $product ); } } /** * Purge a product page and related pages (based on settings) on checkout. * * @since 1.0.9 * @since 1.6.3 Removed static * @access public * * @param \WC_Product $product Product object. * @param bool $stock_status_changed Whether this call is due to stock status transition. * @return void */ public function purge_product( $product, $stock_status_changed = false ) { do_action( 'litespeed_debug', '[3rd] Woo Purge [pid] ' . $product->get_id() ); $do_purge = function ( $action, $debug = '' ) use ( $product, $stock_status_changed ) { $config = apply_filters( 'litespeed_conf', self::O_UPDATE_INTERVAL ); if ( is_null( $config ) ) { $config = self::O_PQS_CS; } if ( self::O_PQS_CQS === $config ) { $action(); if ( $debug ) { do_action( 'litespeed_debug', $debug ); } } elseif ( ! $stock_status_changed && self::O_PQS_CS !== $config && $product->is_in_stock() ) { do_action( 'litespeed_debug', '[3rd] Woo No purge needed [option] ' . $config ); return false; } elseif ( self::O_PS_CN !== $config && ( $stock_status_changed || ! $product->is_in_stock() ) ) { $action(); if ( $debug ) { do_action( 'litespeed_debug', $debug ); } } return true; }; if ( ! $do_purge( function () use ( $product ) { $this->backend_purge( $product->get_id() ); } ) ) { return; } do_action( 'litespeed_purge_post', $product->get_id() ); // Check if is variation, purge stock too #984479. if ( $product->is_type( 'variation' ) ) { do_action( 'litespeed_purge_post', $product->get_parent_id() ); } // Check if WPML is enabled ##972971. if ( defined( 'WPML_PLUGIN_BASENAME' ) ) { // Check if it is a variable product and get post/parent ID. $wpml_purge_id = $product->is_type( 'variation' ) ? $product->get_parent_id() : $product->get_id(); $type = apply_filters( 'wpml_element_type', get_post_type( $wpml_purge_id ) ); $trid = apply_filters( 'wpml_element_trid', false, $wpml_purge_id, $type ); $translations = apply_filters( 'wpml_get_element_translations', [], $trid, $type ); foreach ( $translations as $lang => $translation ) { do_action( 'litespeed_debug', '[3rd] Woo WPML purge language: ' . $translation->language_code . ' , post ID: ' . $translation->element_id ); do_action( 'litespeed_purge_post', $translation->element_id ); // use the $translation->element_id as it is post ID of other languages. } // Check other languages category and purge if configured. // wp_get_post_terms() only returns default language category ID. $default_cats = wp_get_post_terms( $wpml_purge_id, 'product_cat' ); $languages = apply_filters( 'wpml_active_languages', null ); foreach ( $default_cats as $default_cat ) { foreach ( $languages as $language ) { $tr_cat_id = icl_object_id( $default_cat->term_id, 'product_cat', false, $language['code'] ); $do_purge( function () use ( $tr_cat_id ) { do_action( 'litespeed_purge', self::CACHETAG_TERM . $tr_cat_id ); }, '[3rd] Woo Purge WPML category [language] ' . $language['code'] . ' [cat] ' . $tr_cat_id ); } } } } /** * Delete object-term relationship. If the post is a product and the term ids array is not empty, * will add purge tags to the deleted terms. * * @since 1.0.9 * @since 1.6.3 Removed static * @access public * * @param int $post_id Object ID. * @param array $term_ids An array of term taxonomy IDs. * @return void */ public function delete_rel( $post_id, $term_ids ) { if ( ! function_exists( 'wc_get_product' ) ) { return; } if ( empty( $term_ids ) || false === wc_get_product( $post_id ) ) { return; } foreach ( $term_ids as $term_id ) { do_action( 'litespeed_purge', self::CACHETAG_TERM . $term_id ); } } /** * Purge a product's categories and tags pages in case they are affected. * * @since 1.0.9 * @since 1.6.3 Removed static * @access public * * @param int $post_id Post id that is about to be purged. * @return void */ public function backend_purge( $post_id ) { if ( ! function_exists( 'wc_get_product' ) ) { return; } if ( ! isset( $post_id ) || false === wc_get_product( $post_id ) ) { return; } $cats = $this->get_cats( $post_id ); if ( ! empty( $cats ) ) { foreach ( $cats as $cat ) { do_action( 'litespeed_purge', self::CACHETAG_TERM . $cat ); } } if ( ! function_exists( 'wc_get_product_terms' ) ) { return; } $tags = wc_get_product_terms( $post_id, 'product_tag', [ 'fields' => 'ids' ] ); if ( ! empty( $tags ) ) { foreach ( $tags as $tag ) { do_action( 'litespeed_purge', self::CACHETAG_TERM . $tag ); } } } /** * When a product has a new review added, purge the recent reviews widget. * * @since 1.1.0 * @since 1.6.3 Removed static * @access public * * @param mixed $unused Unused. * @param integer $comment_approved Whether the comment is approved or not. * @param array $commentdata Information about the comment. * @return void */ public function add_review( $unused, $comment_approved, $commentdata ) { if ( ! function_exists( 'wc_get_product' ) ) { return; } $post_id = $commentdata['comment_post_ID']; if ( 1 !== (int) $comment_approved || ! isset( $post_id ) || false === wc_get_product( $post_id ) ) { return; } global $wp_widget_factory; if ( ! isset( $wp_widget_factory->widgets['WC_Widget_Recent_Reviews'] ) ) { return; } $recent_reviews = $wp_widget_factory->widgets['WC_Widget_Recent_Reviews']; if ( ! is_null( $recent_reviews ) ) { do_action( 'litespeed_tag_add_widget', $recent_reviews->id ); } } /** * Append new options. * * @since 1.6.3 Removed static * @since 3.0 new API * @return void */ private function _option_append() { // Append option save value filter. do_action( 'litespeed_conf_multi_switch', self::O_UPDATE_INTERVAL, 3 ); // This need to be before conf_append. do_action( 'litespeed_conf_append', self::O_UPDATE_INTERVAL, false ); do_action( 'litespeed_conf_append', self::O_CART_VARY, false ); } /** * Hooked to `litespeed_settings_tab` action. * Adds the integration configuration options (currently, to determine purge rules). * * @since 1.6.3 Removed static * * @param string $setting_page Setting page slug. * @return void */ public function settings_add_tab( $setting_page ) { if ( 'cache' !== $setting_page ) { return; } require 'woocommerce.tab.tpl.php'; } /** * Hook to show config content. * * @since 3.0 * * @param string $setting_page Setting page slug. * @return void */ public function settings_add_content( $setting_page ) { if ( 'cache' !== $setting_page ) { return; } require 'woocommerce.content.tpl.php'; } /** * Helper function to select the function(s) to use to get the product category ids. * * @since 1.0.10 * @since 1.6.3 Removed static * @access private * * @param int $product_id The product id. * @return array An array of category ids. */ private function get_cats( $product_id ) { if ( ! function_exists( 'WC' ) ) { return []; } $woocom = WC(); if ( isset( $woocom ) && version_compare( $woocom->version, '2.5.0', '>=' ) && function_exists( 'wc_get_product_cat_ids' ) ) { return wc_get_product_cat_ids( $product_id ); } $product_cats = wp_get_post_terms( $product_id, 'product_cat', [ 'fields' => 'ids' ] ); foreach ( $product_cats as $product_cat ) { $product_cats = array_merge( $product_cats, get_ancestors( $product_cat, 'product_cat' ) ); } return $product_cats; } /** * 3rd party preload. * * @since 2.9.8.4 * @return void */ public static function preload() { /** * Auto purge for WooCommerce Advanced Bulk Edit plugin, * Bulk edit hook needs to be added to preload as it will die before detect. */ add_action( 'wp_ajax_wpmelon_adv_bulk_edit', __CLASS__ . '::bulk_edit_purge', 1 ); } /** * Auto purge for WooCommerce Advanced Bulk Edit plugin. * * @since 2.9.8.4 * @return void */ public static function bulk_edit_purge() { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Third-party plugin action; cannot enforce here. if ( empty( $_POST['type'] ) || 'saveproducts' !== $_POST['type'] || empty( $_POST['data'] ) ) { return; } /* * admin-ajax form-data structure * array( * "type" => "saveproducts", * "data" => array( * "column1" => "464$###0$###2#^#463$###0$###4#^#462$###0$###6#^#", * "column2" => "464$###0$###2#^#463$###0$###4#^#462$###0$###6#^#" * ) * ) */ $stock_string_arr = []; // Ensure data is unslashed before processing. // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Third-party plugin may not send nonce. Data format defined by third-party plugin. $data = isset( $_POST['data'] ) ? wp_unslash( $_POST['data'] ) : []; foreach ( (array) $data as $stock_value ) { $stock_string_arr = array_merge( $stock_string_arr, explode( '#^#', (string) $stock_value ) ); } $lscwp_3rd_woocommerce = new self(); if ( count( $stock_string_arr ) < 1 ) { return; } foreach ( $stock_string_arr as $edited_stock ) { $product_id = (int) strtok( (string) $edited_stock, '$' ); $product = wc_get_product( $product_id ); if ( empty( $product ) ) { do_action( 'litespeed_debug', '3rd woo purge: ' . $product_id . ' not found.' ); continue; } $lscwp_3rd_woocommerce->purge_product( $product ); } } } thirdparty/wp-postratings.cls.php000064400000001604152075713340013240 0ustar00get_gallery(); if ($gallery && $gallery->pageid) { do_action('litespeed_purge', self::CACHETAG_GALLERIES . $gallery->pageid); } } /** * Purge cache when an image is updated. * * @since 1.0.5 * @return void */ public static function update_image() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( isset( $_REQUEST['gallery_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended do_action( 'litespeed_purge', self::CACHETAG_GALLERIES . sanitize_key( wp_unslash( $_REQUEST['gallery_id'] ) ) ); return; } // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( isset( $_POST['task_list'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing $task_list = str_replace( '\\', '', wp_unslash( $_POST['task_list'] ) ); $task_list = json_decode( $task_list, true ); if ( ! empty( $task_list[0]['query']['id'] ) ) { do_action( 'litespeed_purge', self::CACHETAG_GALLERIES . sanitize_key( $task_list[0]['query']['id'] ) ); return; } } // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( isset( $_POST['id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing $id = (int) $_POST['id']; // phpcs:ignore WordPress.Security.NonceVerification.Missing } elseif ( isset( $_POST['image'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing $id = (int) $_POST['image']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended } elseif ( isset( $_GET['pid'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $id = (int) $_GET['pid']; } else { error_log( 'LiteSpeed_Cache hit ngg_ajax_image_save with no post image id.' ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log return; } $image = \C_Image_Mapper::get_instance()->find($id); if ($image) { do_action('litespeed_purge', self::CACHETAG_GALLERIES . $image->galleryid); } } /** * Purge cache when an image is deleted. * * @since 1.0.5 * @return void */ public static function delete_image() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( isset( $_GET['gid'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended do_action( 'litespeed_purge', self::CACHETAG_GALLERIES . sanitize_key( wp_unslash( $_GET['gid'] ) ) ); } } /** * Purge cache when an image is moved. * * @since 1.0.8 * @param array $images Unused. * @param array $old_gallery_ids Source gallery IDs. * @param int $new_gallery_id Destination gallery ID. * @return void */ public static function move_image( $images, $old_gallery_ids, $new_gallery_id ) { foreach ($old_gallery_ids as $gid) { do_action('litespeed_purge', self::CACHETAG_GALLERIES . $gid); } do_action('litespeed_purge', self::CACHETAG_GALLERIES . $new_gallery_id); } /** * Purge cache when an image is copied. * * @since 1.0.8 * @param array $image_pid_map Unused. * @param array $old_gallery_ids Unused. * @param int $new_gallery_id Destination gallery ID. * @return void */ public static function copy_image( $image_pid_map, $old_gallery_ids, $new_gallery_id ) { do_action('litespeed_purge', self::CACHETAG_GALLERIES . $new_gallery_id); } /** * Purge cache when an image is regenerated or recovered. * * @since 1.0.8 * @param object $image The regenerated image object. * @return void */ public static function gen_image( $image ) { do_action('litespeed_purge', self::CACHETAG_GALLERIES . $image->galleryid); } /** * Purge cache when a gallery is updated. * * @since 1.0.5 * @param int|object $gid Gallery ID or object with gid. * @return void */ public static function update_gallery( $gid ) { if (is_object($gid) && !empty($gid->gid)) { $gid = $gid->gid; } do_action('litespeed_purge', self::CACHETAG_GALLERIES . $gid); } /** * Purge cache when an album is updated. * * @since 1.0.5 * @param int $aid Album ID. * @return void */ public static function update_album( $aid ) { do_action('litespeed_purge', self::CACHETAG_ALBUMS . $aid); } /** * Tag gallery/album/tag content during rendering. * * @since 1.0.5 * @param object $render_parms Render parameters. * @return mixed Null if $render_parms is null, otherwise same input. */ public static function add_container( $render_parms ) { if (is_null($render_parms)) { return null; } $src = $render_parms[0]->source; $container_ids = $render_parms[0]->container_ids; switch ($src) { case 'albums': $tag = self::CACHETAG_ALBUMS; break; case 'galleries': $tag = self::CACHETAG_GALLERIES; break; case 'tags': $tag = self::CACHETAG_TAGS; break; default: return $render_parms; } foreach ($container_ids as $id) { do_action('litespeed_tag_add', $tag . $id); } return $render_parms; } } thirdparty/user-switching.cls.php000064400000001431152075713340013210 0ustar00ID); } } } } thirdparty/woocommerce.tab.tpl.php000064400000000346152075713340013343 0ustar00 WooCommerce thirdparty/wp-polls.cls.php000064400000001447152075713340012021 0ustar00 self::$_post_id, ]; $inline_tags = [ '', rtrim( Tag::TYPE_ESI, '.' ), Tag::TYPE_ESI . 'yith_wcwl_add' ]; $inline_tags = implode( ',', array_map( function ( $val ) { return 'public:' . LSWCP_TAG_PREFIX . '_' . $val; }, $inline_tags ) ); $inline_tags .= ',' . LSWCP_TAG_PREFIX . '_tag_priv'; do_action( 'litespeed_esi_combine', 'yith_wcwl_add' ); $inline_params = [ 'val' => $template, 'tag' => $inline_tags, 'control' => 'private,no-vary,max-age=' . Conf::cls()->conf( Base::O_CACHE_TTL_PRIV ), ]; return apply_filters( 'litespeed_esi_url', 'yith_wcwl_add', 'YITH ADD TO WISHLIST', $params, 'private,no-vary', false, false, false, $inline_params ); } /** * Load the add to wishlist button HTML for ESI output. * * @since 1.1.0 * * @param array $params ESI parameters, expects product id under ESI_PARAM_POSTID. * @return void */ public static function load_add_to_wishlist( $params ) { $pid = isset( $params[ self::ESI_PARAM_POSTID ] ) ? (int) $params[ self::ESI_PARAM_POSTID ] : 0; // Output the rendered shortcode safely. // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- wp_kses_post handles allowed HTML. echo wp_kses_post( \YITH_WCWL_Shortcode::add_to_wishlist( [ 'product_id' => $pid ] ) ); do_action( 'litespeed_control_set_private', 'yith wishlist' ); do_action( 'litespeed_vary_no' ); } /** * Generate ESI inline value. * * @since 3.4.2 * * @param mixed $res Current response (array or anything); will be normalized to array. * @param array $params ESI parameters that include product id. * @return array Inline ESI payload with value, control and tags. */ public static function inline_add_to_wishlist( $res, $params ) { if ( ! is_array( $res ) ) { $res = []; } $pid = isset( $params[ self::ESI_PARAM_POSTID ] ) ? (int) $params[ self::ESI_PARAM_POSTID ] : 0; $res['val'] = \YITH_WCWL_Shortcode::add_to_wishlist( [ 'product_id' => $pid ] ); $res['control'] = 'private,no-vary,max-age=' . Conf::cls()->conf( Base::O_CACHE_TTL_PRIV ); $inline_tags = [ '', rtrim( Tag::TYPE_ESI, '.' ), Tag::TYPE_ESI . 'yith_wcwl_add' ]; $inline_tags = implode( ',', array_map( function ( $val ) { return 'public:' . LSWCP_TAG_PREFIX . '_' . $val; }, $inline_tags ) ); $inline_tags .= ',' . LSWCP_TAG_PREFIX . '_tag_priv'; $res['tag'] = $inline_tags; return $res; } } thirdparty/aelia-currencyswitcher.cls.php000064400000005255152075713340014721 0ustar00get_widget_object( 'BBP_Replies_Widget' ); if ( bbp_is_reply( $post_id ) && $replies_widget ) { do_action( 'litespeed_purge_widget', $replies_widget->id ); } $topic_widget = $wp_widget_factory->get_widget_object( 'BBP_Topics_Widget' ); if ( bbp_is_topic( $post_id ) && $topic_widget ) { do_action( 'litespeed_purge_widget', $topic_widget->id ); } } } thirdparty/divi-theme-builder.cls.php000064400000007105152075713340013720 0ustar00 WordPress with no whitespace changes and relaxed rules 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 cli/ lib/ src/ tpl/ thirdparty/ autoload.php litespeed-cache.php data/cache_nocacheable.txt000064400000000677152075713340011606 0ustar00# Predefined list for Do Not Cache URIs # # Comment can use `# `(there is a space following), or `##`, can use both as a new line or end of one line # If you want to predefine new items, please send a Pull Request to https://github.com/litespeedtech/lscache_wp/blob/dev/data/cache_nocacheable.txt We will merge into next plugin release # WP v6.6 Official Site Editor (Appearance >> Editor) ^/wp-json/wp/v2 # Elementor API ^/wp-json/elementor/v1data/esi.nonce.txt000064400000001362152075713340010110 0ustar00# !!!!! Legacy file for v3.5.1- !!!!! ## Predefined elsewhere so not needed here: ## WordPress core #stats_nonce #subscribe_nonce # Divi Theme Builder #et-pb-contact-form-submit #et_frontend_nonce #et_ab_log_nonce # WooCommerce PayPal Checkout #_wc_ppec_update_shipping_costs_nonce private #_wc_ppec_start_checkout_nonce private #_wc_ppec_generate_cart_nonce private # User Switching #switch_to_olduser_'' # Caldera Forms #caldera_forms_front_* ## Predefined list of ESI nonces: # CM Registration Pro cmreg_registration_nonce private role_nonce private # WooCommerce Delivery Area Pro #16843635 wdap-call-nonce private # SEOpress Cookie Consent seopress_cookies_user_consent_nonce #SearchWP Metrics swpmtxnonce #wpDataTables #986128 wdt* data/preset/aggressive.data000064400000002666152075713340011772 0ustar00["_version","5.3"] ["guest",true] ["guest_optm",true] ["cache",true] ["cache-priv",true] ["cache-commenter",true] ["cache-rest",true] ["cache-page_login",true] ["cache-resources",true] ["cache-mobile",true] ["cache-browser",true] ["esi",false] ["esi-cache_admbar",true] ["esi-cache_commform",true] ["util-instant_click",false] ["util-no_https_vary",false] ["optm-css_min",true] ["optm-css_comb",true] ["optm-css_comb_ext_inl",false] ["optm-ucss",true] ["optm-ucss_inline",false] ["optm-js_min",true] ["optm-js_comb",true] ["optm-js_comb_ext_inl",false] ["optm-html_min",true] ["optm-qs_rm",true] ["optm-ggfonts_rm",false] ["optm-css_async",true] ["optm-ccss_per_url",true] ["optm-css_async_inline",true] ["optm-css_font_display",true] ["optm-js_defer",1] ["optm-emoji_rm",true] ["optm-noscript_rm",true] ["optm-ggfonts_async",false] ["optm-dns_prefetch_ctrl",true] ["optm-guest_only",true] ["discuss-avatar_cache",true] ["discuss-avatar_cron",true] ["optm-localize",false] ["media-lazy",false] ["media-lazy_placeholder",""] ["media-placeholder_resp",false] ["media-lqip",false] ["media-placeholder_resp_async",true] ["media-iframe_lazy",true] ["media-add_missing_sizes",false] ["media-vpi",false] ["media-vpi_cron",false] ["img_optm-auto",true] ["img_optm-ori",true] ["img_optm-rm_bkup",false] ["img_optm-webp",true] ["img_optm-lossless",false] ["img_optm-exif",false] ["img_optm-webp_replace_srcset",true] data/preset/advanced.data000064400000002675152075713340011400 0ustar00["_version","5.3"] ["guest",true] ["guest_optm",true] ["cache",true] ["cache-priv",true] ["cache-commenter",true] ["cache-rest",true] ["cache-page_login",true] ["cache-resources",true] ["cache-mobile",true] ["cache-browser",true] ["esi",false] ["esi-cache_admbar",true] ["esi-cache_commform",true] ["util-instant_click",false] ["util-no_https_vary",false] ["optm-css_min",true] ["optm-css_comb",false] ["optm-css_comb_ext_inl",false] ["optm-ucss",false] ["optm-ucss_inline",false] ["optm-js_min",true] ["optm-js_comb",false] ["optm-js_comb_ext_inl",false] ["optm-html_min",true] ["optm-qs_rm",true] ["optm-ggfonts_rm",false] ["optm-css_async",false] ["optm-ccss_per_url",false] ["optm-css_async_inline",false] ["optm-css_font_display",true] ["optm-js_defer",1] ["optm-emoji_rm",true] ["optm-noscript_rm",true] ["optm-ggfonts_async",false] ["optm-dns_prefetch_ctrl",true] ["optm-guest_only",true] ["discuss-avatar_cache",true] ["discuss-avatar_cron",true] ["optm-localize",false] ["media-lazy",false] ["media-lazy_placeholder",""] ["media-placeholder_resp",false] ["media-lqip",false] ["media-placeholder_resp_async",true] ["media-iframe_lazy",false] ["media-add_missing_sizes",false] ["media-vpi",false] ["media-vpi_cron",false] ["img_optm-auto",true] ["img_optm-ori",true] ["img_optm-rm_bkup",false] ["img_optm-webp",true] ["img_optm-lossless",false] ["img_optm-exif",false] ["img_optm-webp_replace_srcset",true] data/preset/essentials.data000064400000002712152075713340011775 0ustar00["_version","5.3"] ["guest",false] ["guest_optm",false] ["cache",true] ["cache-priv",true] ["cache-commenter",true] ["cache-rest",true] ["cache-page_login",true] ["cache-resources",true] ["cache-mobile",false] ["cache-browser",true] ["esi",false] ["esi-cache_admbar",true] ["esi-cache_commform",true] ["util-instant_click",false] ["util-no_https_vary",false] ["optm-css_min",false] ["optm-css_comb",false] ["optm-css_comb_ext_inl",true] ["optm-ucss",false] ["optm-ucss_inline",false] ["optm-js_min",false] ["optm-js_comb",false] ["optm-js_comb_ext_inl",true] ["optm-html_min",false] ["optm-qs_rm",false] ["optm-ggfonts_rm",false] ["optm-css_async",false] ["optm-ccss_per_url",false] ["optm-css_async_inline",true] ["optm-css_font_display",false] ["optm-js_defer",0] ["optm-emoji_rm",false] ["optm-noscript_rm",false] ["optm-ggfonts_async",false] ["optm-dns_prefetch_ctrl",false] ["optm-guest_only",true] ["discuss-avatar_cache",false] ["discuss-avatar_cron",false] ["optm-localize",false] ["media-lazy",false] ["media-lazy_placeholder",""] ["media-placeholder_resp",false] ["media-lqip",false] ["media-placeholder_resp_async",true] ["media-iframe_lazy",false] ["media-add_missing_sizes",false] ["media-vpi",false] ["media-vpi_cron",false] ["img_optm-auto",false] ["img_optm-ori",true] ["img_optm-rm_bkup",false] ["img_optm-webp",false] ["img_optm-lossless",false] ["img_optm-exif",false] ["img_optm-webp_replace_srcset",false] data/preset/basic.data000064400000002706152075713340010707 0ustar00["_version","5.3"] ["guest",false] ["guest_optm",false] ["cache",true] ["cache-priv",true] ["cache-commenter",true] ["cache-rest",true] ["cache-page_login",true] ["cache-resources",true] ["cache-mobile",true] ["cache-browser",true] ["esi",false] ["esi-cache_admbar",true] ["esi-cache_commform",true] ["util-instant_click",false] ["util-no_https_vary",false] ["optm-css_min",false] ["optm-css_comb",false] ["optm-css_comb_ext_inl",true] ["optm-ucss",false] ["optm-ucss_inline",false] ["optm-js_min",false] ["optm-js_comb",false] ["optm-js_comb_ext_inl",true] ["optm-html_min",false] ["optm-qs_rm",false] ["optm-ggfonts_rm",false] ["optm-css_async",false] ["optm-ccss_per_url",false] ["optm-css_async_inline",true] ["optm-css_font_display",false] ["optm-js_defer",0] ["optm-emoji_rm",false] ["optm-noscript_rm",false] ["optm-ggfonts_async",false] ["optm-dns_prefetch_ctrl",false] ["optm-guest_only",true] ["discuss-avatar_cache",false] ["discuss-avatar_cron",false] ["optm-localize",false] ["media-lazy",false] ["media-lazy_placeholder",""] ["media-placeholder_resp",false] ["media-lqip",false] ["media-placeholder_resp_async",true] ["media-iframe_lazy",false] ["media-add_missing_sizes",false] ["media-vpi",false] ["media-vpi_cron",false] ["img_optm-auto",true] ["img_optm-ori",true] ["img_optm-rm_bkup",false] ["img_optm-webp",true] ["img_optm-lossless",false] ["img_optm-exif",false] ["img_optm-webp_replace_srcset",true] data/preset/extreme.data000064400000003000152075713340011263 0ustar00["_version","5.3"] ["guest",true] ["guest_optm",true] ["cache",true] ["cache-priv",true] ["cache-commenter",true] ["cache-rest",true] ["cache-page_login",true] ["cache-resources",true] ["cache-mobile",true] ["cache-browser",true] ["esi",false] ["esi-cache_admbar",true] ["esi-cache_commform",true] ["util-instant_click",false] ["util-no_https_vary",false] ["optm-css_min",true] ["optm-css_comb",true] ["optm-css_comb_ext_inl",true] ["optm-ucss",true] ["optm-ucss_inline",false] ["optm-js_min",true] ["optm-js_comb",true] ["optm-js_comb_ext_inl",true] ["optm-html_min",true] ["optm-qs_rm",true] ["optm-ggfonts_rm",false] ["optm-css_async",true] ["optm-ccss_per_url",true] ["optm-css_async_inline",true] ["optm-css_font_display",true] ["optm-js_defer",2] ["optm-emoji_rm",true] ["optm-noscript_rm",true] ["optm-ggfonts_async",false] ["optm-dns_prefetch_ctrl",true] ["optm-guest_only",true] ["discuss-avatar_cache",true] ["discuss-avatar_cron",true] ["optm-localize",false] ["media-lazy",true] ["media-lazy_placeholder","data:image\/gif;base64,R0lGODlhAQABAIAAAAAAAP\/\/\/yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"] ["media-placeholder_resp",true] ["media-lqip",true] ["media-placeholder_resp_async",true] ["media-iframe_lazy",true] ["media-add_missing_sizes",true] ["media-vpi",true] ["media-vpi_cron",true] ["img_optm-auto",true] ["img_optm-ori",true] ["img_optm-rm_bkup",false] ["img_optm-webp",true] ["img_optm-lossless",false] ["img_optm-exif",false] ["img_optm-webp_replace_srcset",true] data/optm_uri_exc.txt000064400000000571152075713340010725 0ustar00# Predefined list for excluding URI from page optimization # # Comment can use `# `(there is a space following), or `##`, can use both as a new line or end of one line # If you want to predefine new items, please send a Pull Request to https://github.com/litespeedtech/lscache_wp/blob/dev/data/optm_uri_exc.txt We will merge into next plugin release # URI excludes .well-knowndata/css_excludes.txt000064400000001276152075713340010717 0ustar00# Predefined list for excluding CSS files or inline CSS codes # # Comment can use `# `(there is a space following), or `##`, can use both as a new line or end of one line # If you want to predefine new items, please send a Pull Request to https://github.com/litespeedtech/lscache_wp/blob/dev/data/css_excludes.txt We will merge into next plugin release # CSS file URL excludes # Inline CSS excludes ########## Flatsome theme random string excludes ############ #row- #col- #cats- #stack- #timer- #gap- #portfolio- #image_ #banner- #map- #text- #page-header- #section_ .tdi_ # Theme: Newspaper by tagDiv.com 2020 ######### WoodMart - Responsive WooCommerce WordPress Theme ######## .tabs-wd- #wd-data/js_defer_excludes.txt000064400000000774152075713340011712 0ustar00# Predefined list for excluding deferred JS files or inline JS codes # # Comment can use `# `(there is a space following), or `##`, can use both as a new line or end of one line # If you want to predefine new items, please send a Pull Request to https://github.com/litespeedtech/lscache_wp/blob/dev/data/js_defer_excludes.txt We will merge into next plugin release # JS file URL excludes adsbygoogle ## JetPack Stats stats.wp.com/e- _stq # Cloudflare turnstile - Tobolo turnstile challenges.cloudflare.comdata/ucss_whitelist.txt000064400000000714152075713340011300 0ustar00# Predefined list for UCSS whitelist # # Comment can use `# `(there is a space following), or `##`, can use both as a new line or end of one line # If you want to predefine new items, please send a Pull Request to https://github.com/litespeedtech/lscache_wp/blob/dev/data/ucss_whitelist.txt We will merge into next plugin release ############# DoBar compatibility ############# .pace-inactive ############# DIVI ################ .et_pb_number_counter.activedata/js_excludes.txt000064400000002057152075713340010541 0ustar00# Predefined list for excluding JS files or inline JS codes # # Comment can use `# `(there is a space following), or `##`, can use both as a new line or end of one line # If you want to predefine new items, please send a Pull Request to https://github.com/litespeedtech/lscache_wp/blob/dev/data/js_excludes.txt We will merge into next plugin release # JS file URL excludes maps-api-ssl.google.com maps.google.com/maps maps.googleapis.com google.com/recaptcha google-analytics.com/analytics.js stats.wp.com js.stripe.com paypal.com/sdk/js cse.google.com/cse.js /syntaxhighlighter/ spotlight-social-photo-feeds ## https://docs.spotlightwp.com/article/757-autoptimize-compatibility @Tobolo userway.org # Inline JS excludes document.write gtag gtm dataLayer adsbygoogle block_tdi_ ## Theme: Newspaper by tagDiv.com data-view-breakpoint-pointer ## Plugin: The Events Calendar by Modern Tribe (https://theeventscalendar.com/) wp-json/wp-statistics ## WP Statistics ## JetPack Stats stats.wp.com/e- _stq # Cloudflare turnstile - Tobolo turnstile challenges.cloudflare.comdata/gm_ips.txt000064400000001206152075713340007502 0ustar0066.102.0.0/20 66.249.64.0/19 74.125.0.0/16 142.250.0.0/15 172.255.48.128/27 172.255.61.32/28 208.70.247.157 52.229.122.240 104.214.72.101 13.66.7.11 13.85.24.83 13.85.24.90 13.85.82.26 40.74.242.253 40.74.243.13 40.74.243.176 104.214.48.247 157.55.189.189 104.214.110.135 70.37.83.240 65.52.36.250 13.78.216.56 52.162.212.163 23.96.34.105 65.52.113.236 104.41.2.19 191.235.98.164 191.235.99.221 191.232.194.51 52.237.235.185 52.237.250.73 52.237.236.145 104.211.143.8 104.211.165.53 52.172.14.87 40.83.89.214 52.175.57.81 20.188.63.151 20.52.36.49 52.246.165.153 51.144.102.233 13.76.97.224 102.133.169.66 52.231.199.170 13.53.162.7 40.123.218.94data/ccss_whitelist.txt000064400000000715152075713340011257 0ustar00# Predefined list for CCSS whitelist # # Comment can use `# `(there is a space following), or `##`, can use both as a new line or end of one line # If you want to predefine new items, please send a Pull Request to https://github.com/litespeedtech/lscache_wp/blob/dev/data/ccss_whitelist.txt We will merge into next plugin release ############# DoBar compatibility ############# .pace-inactive ############# DIVI ################ .et_pb_number_counter.active data/const.network_default.json000064400000002250152075713340012700 0ustar00{ "cache": false, "use_primary_settings": false, "auto_upgrade": false, "cache-resources": true, "cache-browser": false, "cache-mobile": false, "cache-mobile_rules": "Mobile\nAndroid\nSilk/\nKindle\nBlackBerry\nOpera Mini\nOpera Mobi", "cache-drop_qs": "fbclid\ngclid\nutm*\n_ga", "cache-login_cookie": "", "cache-exc_cookies": "", "cache-exc_useragents": "", "cache-ttl_browser": 31557600, "purge-upgrade": false, "object": false, "object-kind": false, "object-host": "localhost", "object-port": 11211, "object-life": 360, "object-persistent": true, "object-admin": true, "object-db_id": 0, "object-user": "", "object-pswd": "", "object-global_groups": "users\nuserlogins\nusermeta\nuser_meta\nuseremail\nuserslugs\nsites\nsite-details\nsite-transient\nsite-options\nsite-lookup\nblog-lookup\nblog-id-cache\nblog-details\nnetworks\nrss\nglobal-posts\nglobal-cache-test", "object-non_persistent_groups": "comment\ncounts\nplugins", "debug-disable_all": false, "debug": false, "debug-ips": "127.0.0.1", "debug-level": false, "debug-filesize": 3, "debug-collapse_qs": false, "debug-inc": "", "debug-exc": "", "debug-exc_strings": "", "img_optm-webp": false } data/gm_uas.txt000064400000000101152075713340007470 0ustar00Lighthouse GTmetrix Google Pingdom bot spider PTST HeadlessChromedata/esi.nonces.txt000064400000003173152075713340010275 0ustar00## To predefine more list, please submit a PR to https://github.com/litespeedtech/lscache_wp/blob/dev/data/esi.nonces.txt ## Comment Format: ## 1. `# this is comment` ## 2. `##this is comment` ## Predefined elsewhere so not needed here: ## WordPress core # stats_nonce # subscribe_nonce # Divi Theme Builder # et-pb-contact-form-submit # et_frontend_nonce # et_ab_log_nonce # WooCommerce PayPal Checkout # _wc_ppec_update_shipping_costs_nonce private # _wc_ppec_start_checkout_nonce private # _wc_ppec_generate_cart_nonce private # User Switching # switch_to_olduser_'' # Caldera Forms # caldera_forms_front_* ## Predefined list of ESI nonces: # WordPress REST nonce wp_rest # CM Registration Pro cmreg_registration_nonce private role_nonce private # WooCommerce Delivery Area Pro #16843635 wdap-call-nonce private # SEOpress Cookie Consent seopress_cookies_user_consent_nonce # SearchWP Metrics swpmtxnonce # The Events Calendar _tec_view_rest_nonce_primary _tec_view_rest_nonce_secondary # wpDataTables #986128 wdt* # WPBakery gallery _vcnonce data-vc-public-nonce # Extra Theme rating_nonce timeline_nonce blog_feed_nonce # WS Form wsf_post # Easy Digital Download (EDD) edd-* private edd_* private # WP Menu Cart wpmenucart private # Advanced Custom Fields + Advanced Forms acf_nonce af_form_nonce af_submission_* # Woo nonce woocommerce-login # Premium Addons for Elementor pa-blog-widget-nonce # WPUF User Frontend wpuf* private # MetForm form_nonce # Mobile hamburger menu - jetMenu #306983 #163710 PR#419 tgmpa-* bulk-* # WP Data Access wpda-* # Elementor elementor-pro-frontend elementor-conversion-center-clickdata/const.default.json000064400000013224152075713340011132 0ustar00{ "auto_upgrade": "", "server_ip": "", "guest": "", "guest_optm": "", "news": "1", "cache-priv": "1", "cache-commenter": "1", "cache-rest": "1", "cache-page_login": "1", "cache-resources": "1", "cache-browser": "", "cache-mobile": "", "cache-mobile_rules": "Mobile\nAndroid\nSilk/\nKindle\nBlackBerry\nOpera Mini\nOpera Mobi", "cache-exc_useragents": "", "cache-exc_cookies": "", "cache-exc_qs": "", "cache-exc_cat": "", "cache-exc_tag": "", "cache-force_uri": "", "cache-force_pub_uri": "", "cache-priv_uri": "", "cache-exc": "", "cache-exc_roles": "", "cache-drop_qs": "fbclid\ngclid\nutm*\n_ga", "cache-ttl_pub": "604800", "cache-ttl_priv": "1800", "cache-ttl_frontpage": "604800", "cache-ttl_feed": "604800", "cache-ttl_rest": "604800", "cache-ttl_browser": "31557600", "cache-login_cookie": "", "cache-vary_group": "", "cache-ttl_status": "404 3600\n500 600", "purge-upgrade": "0", "purge-stale": "", "purge-post_all": "", "purge-post_f": "1", "purge-post_h": "1", "purge-post_p": "1", "purge-post_pwrp": "1", "purge-post_a": "1", "purge-post_y": "", "purge-post_m": "1", "purge-post_d": "", "purge-post_t": "1", "purge-post_pt": "1", "purge-timed_urls": "", "purge-timed_urls_time": "", "purge-hook_all": "switch_theme\nwp_create_nav_menu\nwp_update_nav_menu\nwp_delete_nav_menu\ncreate_term\nedit_terms\ndelete_term\nadd_link\nedit_link\ndelete_link", "esi": "", "esi-cache_admbar": "1", "esi-cache_commform": "1", "esi-nonce": "stats_nonce\nsubscribe_nonce", "util-heartbeat": "1", "util-instant_click": "", "util-no_https_vary": "", "debug-disable_all": "", "debug": "", "debug-ips": "127.0.0.1", "debug-level": "", "debug-filesize": "3", "debug-collapse_qs": "", "debug-inc": "", "debug-exc": "", "debug-exc_strings": "", "db_optm-revisions_max": "0", "db_optm-revisions_age": "0", "optm-css_min": "", "optm-css_comb": "", "optm-css_comb_ext_inl": "1", "optm-ucss": "", "optm-ucss_inline": "", "optm-ucss_file_exc_inline": "", "optm-ucss_whitelist": "", "optm-ucss_exc": "", "optm-css_exc": "", "optm-js_min": "", "optm-js_comb": "", "optm-js_comb_ext_inl": "1", "optm-js_exc": "jquery.js\njquery.min.js", "optm-html_min": "", "optm-html_lazy": "", "optm-qs_rm": "", "optm-ggfonts_rm": "", "optm-css_async": "", "optm-ccss_per_url": "", "optm-ccss_whitelist": "", "optm-css_async_inline": "1", "optm-css_font_display": "", "optm-js_defer": "", "optm-emoji_rm": "", "optm-noscript_rm": "", "optm-ggfonts_async": "", "optm-exc_roles": "", "optm-ccss_con": "", "optm-ccss_sep_posttype": "page", "optm-ccss_sep_uri": "", "optm-js_defer_exc": "jquery.js\njquery.min.js\ngtm.js\nanalytics.js", "optm-gm_js_exc": "", "optm-dns_prefetch": "", "optm-dns_prefetch_ctrl": "", "optm-dns_preconnect": "", "optm-exc": "", "optm-guest_only": "1", "object": "", "object-kind": "", "object-host": "localhost", "object-port": "11211", "object-life": "360", "object-persistent": "1", "object-admin": "1", "object-db_id": "0", "object-user": "", "object-pswd": "", "object-global_groups": "users\nuserlogins\nuseremail\nuserslugs\nusermeta\nuser_meta\nsite-transient\nsite-options\nsite-lookup\nsite-details\nblog-lookup\nblog-details\nblog-id-cache\nrss\nglobal-posts\nglobal-cache-test", "object-non_persistent_groups": "comment\ncounts\nplugins\nwc_session_id", "discuss-avatar_cache": "", "discuss-avatar_cron": "", "discuss-avatar_cache_ttl": "604800", "optm-localize": "", "optm-localize_domains": "### Popular scripts ###\nhttps://platform.twitter.com/widgets.js\nhttps://connect.facebook.net/en_US/fbevents.js", "media-lazy": "", "media-lazy_placeholder": "", "media-placeholder_resp": "", "media-placeholder_resp_color": "#cfd4db", "media-placeholder_resp_svg": "", "media-lqip": "", "media-lqip_qual": "4", "media-lqip_min_w": "150", "media-lqip_min_h": "150", "media-placeholder_resp_async": "1", "media-iframe_lazy": "", "media-add_missing_sizes": "", "media-lazy_exc": "", "media-lazy_cls_exc": "wmu-preview-img", "media-lazy_parent_cls_exc": "", "media-iframe_lazy_cls_exc": "", "media-iframe_lazy_parent_cls_exc": "", "media-lazy_uri_exc": "", "media-lqip_exc": "", "media-vpi": "", "media-vpi_cron": "", "img_optm-auto": "", "img_optm-ori": "1", "img_optm-rm_bkup": "", "img_optm-webp": "", "img_optm-lossless": "", "img_optm-exif": "1", "img_optm-webp_attr": "img.src\ndiv.data-thumb\nimg.data-src\nimg.data-lazyload\ndiv.data-large_image\nimg.retina_logo_url\ndiv.data-parallax-image\ndiv.data-vc-parallax-image\nvideo.poster", "img_optm-webp_replace_srcset": "", "img_optm-jpg_quality": "82", "crawler": "", "crawler-crawl_interval": "302400", "crawler-load_limit": "1", "crawler-sitemap": "", "crawler-roles": "", "crawler-cookies": "", "misc-heartbeat_front": "", "misc-heartbeat_front_ttl": "60", "misc-heartbeat_back": "", "misc-heartbeat_back_ttl": "60", "misc-heartbeat_editor": "", "misc-heartbeat_editor_ttl": "15", "cdn": "", "cdn-attr": ".src\n.data-src\n.href\n.poster\nsource.srcset", "cdn-ori": "", "cdn-ori_dir": "", "cdn-exc": "", "cdn-quic": "", "cdn-quic_email": "", "cdn-quic_key": "", "cdn-cloudflare": "", "cdn-cloudflare_email": "", "cdn-cloudflare_key": "", "cdn-cloudflare_name": "", "cdn-cloudflare_zone": "", "cdn-cloudflare_clear": "", "cdn-mapping": { "url": [""], "inc_js": ["1"], "inc_css": ["1"], "inc_img": ["1"], "filetype": [".aac\n.css\n.eot\n.gif\n.jpeg\n.jpg\n.js\n.less\n.mp3\n.mp4\n.ogg\n.otf\n.pdf\n.png\n.svg\n.ttf\n.webp\n.woff\n.woff2"] } } tpl/dash/network_dash.tpl.php000064400000011242152075713340012270 0ustar00
$cloud_summary ) : ?>


esc_html__( 'Image Optimization', 'litespeed-cache' ), 'page_optm' => esc_html__( 'Page Optimization', 'litespeed-cache' ), 'cdn' => esc_html__( 'CDN Bandwidth', 'litespeed-cache' ), 'lqip' => esc_html__( 'Low Quality Image Placeholder', 'litespeed-cache' ), ); foreach ( $cat_list as $svc => $svc_title ) : $finished_percentage = 0; $total_used = '-'; $used = '-'; $quota = '-'; $pag_used = '-'; $pag_total = '-'; $pag_width = 0; $pag_bal = 0; if ( ! empty( $cloud_summary[ 'usage.' . $svc ] ) ) { $usage = $cloud_summary[ 'usage.' . $svc ]; $finished_percentage = floor( $usage['used'] * 100 / $usage['quota'] ); $used = $usage['used']; $quota = $usage['quota']; $pag_used = ! empty( $usage['pag_used'] ) ? $usage['pag_used'] : 0; $pag_bal = ! empty( $usage['pag_bal'] ) ? $usage['pag_bal'] : 0; $pag_total = $pag_used + $pag_bal; if ( $pag_total ) { $pag_width = round( $pag_used / $pag_total * 100 ) . '%'; } if ( 'cdn' === $svc ) { $used = Utility::real_size( $used * 1024 * 1024 ); $quota = Utility::real_size( $quota * 1024 * 1024 ); $pag_used = Utility::real_size( $pag_used * 1024 * 1024 ); $pag_total = Utility::real_size( $pag_total * 1024 * 1024 ); } if ( ! empty( $usage['total_used'] ) ) { $total_used = $usage['total_used']; } } $percentage_bg = 'success'; if ( 95 < $finished_percentage ) { $percentage_bg = 'danger'; } elseif ( 85 < $finished_percentage ) { $percentage_bg = 'warning'; } ?>

/

:

: / ∞

tpl/dash/entry.tpl.php000064400000001704152075713340010743 0ustar00 esc_html__( 'Dashboard', 'litespeed-cache' ), ); if ( $this->_is_network_admin ) { $menu_list = array( 'network_dash' => esc_html__( 'Network Dashboard', 'litespeed-cache' ), ); } ?>


$tab_val ) { echo '
'; require LSCWP_DIR . 'tpl/dash/' . $tab_key . '.tpl.php'; echo '
'; } ?>
tpl/dash/dashboard.tpl.php000064400000127474152075713340011546 0ustar00scores(); $crawler_summary = Crawler::get_summary(); // Image related info $img_optm_summary = Img_Optm::get_summary(); $img_count = Img_Optm::cls()->img_count(); $img_finished_percentage = 0; if ( ! empty( $img_count['groups_all'] ) ) { $img_finished_percentage = 100 - floor( $img_count['groups_new'] * 100 / $img_count['groups_all'] ); } if ( 100 === $img_finished_percentage && ! empty( $img_count['groups_new'] ) ) { $img_finished_percentage = 99; } $cloud_instance = Cloud::cls(); $cloud_instance->finish_qc_activation(); $cloud_summary = Cloud::get_summary(); $css_summary = CSS::get_summary(); $ucss_summary = UCSS::get_summary(); $placeholder_summary = Placeholder::get_summary(); $vpi_summary = VPI::get_summary(); $ccss_count = count( $this->load_queue( 'ccss' ) ); $ucss_count = count( $this->load_queue( 'ucss' ) ); $placeholder_queue_count = count( $this->load_queue( 'lqip' ) ); $vpi_queue_count = count( $this->load_queue( 'vpi' ) ); $can_page_load_time = defined( 'LITESPEED_SERVER_TYPE' ) && 'NONE' !== LITESPEED_SERVER_TYPE; ?>
activated() && ! Admin_Display::has_qc_hide_banner() ) : ?>

esc_html__( 'Public Cache', 'litespeed-cache' ), Base::O_CACHE_PRIV => esc_html__( 'Private Cache', 'litespeed-cache' ), Base::O_OBJECT => esc_html__( 'Object Cache', 'litespeed-cache' ), Base::O_CACHE_BROWSER => esc_html__( 'Browser Cache', 'litespeed-cache' ), ); foreach ( $cache_list as $cache_option => $cache_title ) : ?>

conf( $cache_option ) ) : ?>

list_crawlers() ) ); ?>

:

:

:

:

' . esc_html__( 'Last crawled:', 'litespeed-cache' ) . '', esc_html( $crawler_summary['last_crawled'] ) ); ?>

load_qc_status_for_dash( 'news_dash_guest' ); if ( ! empty( $news ) ) : ?>

activated() && ! Admin_Display::has_qc_hide_banner() ) : ?>

QUIC.cloud Online Services and CDN.', 'litespeed-cache' ) ); ?>

activated() && Admin_Display::has_qc_hide_banner() ) : ?>

QUIC.cloud' ); ?>

esc_html__( 'Image Optimization', 'litespeed-cache' ), 'page_optm' => esc_html__( 'Page Optimization', 'litespeed-cache' ), 'cdn' => esc_html__( 'CDN Bandwidth', 'litespeed-cache' ), 'lqip' => esc_html__( 'Low Quality Image Placeholder', 'litespeed-cache' ), ); foreach ( $cat_list as $svc => $svc_title ) : $finished_percentage = 0; $total_used = '-'; $used = '-'; $quota = '-'; $pag_used = '-'; $pag_total = '-'; $pag_width = 0; $percentage_bg = 'success'; $pag_txt_color = ''; $usage = false; if ( ! empty( $cloud_summary[ 'usage.' . $svc ] ) ) { $usage = $cloud_summary[ 'usage.' . $svc ]; $finished_percentage = floor( $usage['used'] * 100 / $usage['quota'] ); $used = (int) $usage['used']; $quota = (int) $usage['quota']; $pag_used = ! empty( $usage['pag_used'] ) ? (int) $usage['pag_used'] : 0; $pag_bal = ! empty( $usage['pag_bal'] ) ? (int) $usage['pag_bal'] : 0; $pag_total = $pag_used + $pag_bal; if ( ! empty( $usage['total_used'] ) ) { $total_used = (int) $usage['total_used']; } if ( $pag_total ) { $pag_width = round( $pag_used / $pag_total * 100 ) . '%'; } if ( $finished_percentage > 85 ) { $percentage_bg = 'warning'; if ( $finished_percentage > 95 ) { $percentage_bg = 'danger'; if ( $pag_bal ) { $percentage_bg = 'warning'; $pag_txt_color = 'litespeed-success'; } } } } ?>

/

0 ) : ?>

:

$sub_usage ) : ?> :

: / ∞

= 0 && isset( $usage['daily_quota'] ) && $usage['daily_quota'] >= 0 ) : ?>

: /


s

s

%




conf( $guest_option ) ) : ?>

get_cls_of_pagescore( $health_scores['score_before'] ) ) ), GUI::allowed_svg_tags() ); ?>

get_cls_of_pagescore( $health_scores['score_after'] ) ) ), GUI::allowed_svg_tags() ); ?>

%

: ()

: ()

:

:

Lang::title( Base::O_IMG_OPTM_AUTO ), ); foreach ( $opt_list as $opt_id => $opt_title ) : ?>

conf( $opt_id ) ) : ?>

esc_html__( 'Public Cache', 'litespeed-cache' ), Base::O_CACHE_PRIV => esc_html__( 'Private Cache', 'litespeed-cache' ), Base::O_OBJECT => esc_html__( 'Object Cache', 'litespeed-cache' ), Base::O_CACHE_BROWSER => esc_html__( 'Browser Cache', 'litespeed-cache' ), ); foreach ( $cache_list as $cache_option => $cache_title ) : ?>

conf( $cache_option ) ) : ?>

' . esc_html( Utility::readable_time( $css_summary['last_request_ccss'] ) ) . '' ); ?>

' . esc_html( $css_summary['last_spent_ccss'] ) . 's' ); ?>

:

' . esc_html( Utility::readable_time( $ucss_summary['last_request'] ) ) . '' ); ?>

' . esc_html( $ucss_summary['last_spent'] ) . 's' ); ?>

:

' . esc_html( Utility::readable_time( $placeholder_summary['last_request'] ) ) . '' ); ?>

' . esc_html( $placeholder_summary['last_spent'] ) . 's' ); ?>

:

(VPI)

' . esc_html( Utility::readable_time( $vpi_summary['last_request'] ) ) . '' ); ?>

' . esc_html( $vpi_summary['last_spent'] ) . 's' ); ?>

:

list_crawlers() ) ); ?>

:

:

:

:

' . esc_html__( 'Last crawled:', 'litespeed-cache' ) . '', esc_html( $crawler_summary['last_crawled'] ) ); ?>

activated() ? Cloud::TYPE_ENABLE_CDN : Cloud::TYPE_ACTIVATE ) ), '' . esc_html__( 'Enable QUIC.cloud CDN', 'litespeed-cache' ), true, 'button button-primary litespeed-button-cta' ); ?>

' . esc_html__( 'more', 'litespeed-cache' ) . '' ); ?>

load_qc_status_for_dash( 'cdn_dash_mini' ) ); ?>
activated() ) : ?>
load_qc_status_for_dash( 'promo_mini' ); if ( $promo_mini ) : echo wp_kses_post( $promo_mini ); endif; ?> activated() ) : ?> load_qc_status_for_dash( 'news_dash' ); if ( $news ) : ?>

tpl/esi.tpl.php000064400000000321152075713340007435 0ustar00load_esi_block(); tpl/img_optm/summary.tpl.php000064400000045771152075713340012207 0ustar00allowance( Cloud::SVC_IMG_OPTM ); $img_optm = Img_Optm::cls(); $wet_limit = $img_optm->wet_limit(); $img_count = $img_optm->img_count(); $optm_summary = Img_Optm::get_summary(); list($last_run, $is_running) = $img_optm->cron_running( false ); $finished_percentage = 0; if ( $img_count['groups_all'] ) { $finished_percentage = 100 - floor( $img_count['groups_new'] * 100 / $img_count['groups_all'] ); } if ( 100 === $finished_percentage && $img_count['groups_new'] ) { $finished_percentage = 99; } $unfinished_num = 0; if ( ! empty( $img_count[ 'img.' . Img_Optm::STATUS_REQUESTED ] ) ) { $unfinished_num += $img_count[ 'img.' . Img_Optm::STATUS_REQUESTED ]; } if ( ! empty( $img_count[ 'img.' . Img_Optm::STATUS_NOTIFIED ] ) ) { $unfinished_num += $img_count[ 'img.' . Img_Optm::STATUS_NOTIFIED ]; } if ( ! empty( $img_count[ 'img.' . Img_Optm::STATUS_ERR_FETCH ] ) ) { $unfinished_num += $img_count[ 'img.' . Img_Optm::STATUS_ERR_FETCH ]; } $imgoptm_service_hot = $this->cls( 'Cloud' )->service_hot( Cloud::SVC_IMG_OPTM . '-' . Img_Optm::CLOUD_ACTION_NEW_REQ ); ?>

' . intval( $allowance ) . '' ); ?>

' . esc_html( $wet_limit ) . ''; ?>

:

: ()

: ()

: ()

' . esc_html( Utility::readable_time( $last_run ) ) . '' ); ?>

: ()

%4$s', ( $unfinished_num ? esc_url( Utility::build_url( Router::ACTION_IMG_OPTM, Img_Optm::TYPE_CLEAN ) ) : 'javascript:;' ), esc_html__( 'Remove all previous unfinished image optimization requests.', 'litespeed-cache' ), ( $unfinished_num ? '' : ' disabled' ), esc_html__( 'Clean Up Unfinished Data', 'litespeed-cache' ) . ( $unfinished_num ? ': ' . esc_html( Admin_Display::print_plural( $unfinished_num, 'image' ) ) : '' ) ); ?>

' . esc_html( Utility::readable_time( $optm_summary['bk_summary']['date'] ) ) . ''; ?>

' . intval( $optm_summary['bk_summary']['count'] ) . ''; ?>

' . esc_html( Utility::real_size( $optm_summary['bk_summary']['sum'] ) ) . ''; ?>

get_image_sizes() as $size_title => $size ) { printf( '
%1$s ( %2$s x %3$s )
', esc_html( $size_title ), $size['width'] ? esc_html( $size['width'] ) . 'px' : '*', $size['height'] ? esc_html( $size['height'] ) . 'px' : '*' ); } ?>

🚨

' . esc_html( Utility::readable_time( $optm_summary['rmbk_summary']['date'] ) ) . ''; ?>

' . esc_html( $optm_summary['rmbk_summary']['count'] ) . ''; ?>

' . esc_html( Utility::real_size( $optm_summary['rmbk_summary']['sum'] ) ) . ''; ?>

:

:
:

:

:

:

:

tpl/img_optm/entry.tpl.php000064400000002427152075713340011642 0ustar00 esc_html__( 'Image Optimization Summary', 'litespeed-cache' ), 'settings' => esc_html__( 'Image Optimization Settings', 'litespeed-cache' ), ); if ( is_network_admin() ) { $menu_list = array( 'network_settings' => esc_html__( 'Image Optimization Settings', 'litespeed-cache' ), ); } ?>

v
$val ) { echo '
'; require LSCWP_DIR . 'tpl/img_optm/' . $menu_key . '.tpl.php'; echo '
'; } ?>
tpl/img_optm/settings.tpl.php000064400000012100152075713340012326 0ustar00form_action(); ?>

title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>

🚨
title( $option_id ); ?> build_switch( $option_id ); ?>
conf( $option_id ); ?> title( $option_id ); ?> 0 ) : ?> build_checkbox( $option_id . '[]', esc_html( $current_size['label'] ), $checked, $current_size['file_size'] ); } ?>

title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?>

element.attribute', '.attribute' ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>
srcset' ); ?>
form_end(); tpl/img_optm/network_settings.tpl.php000064400000001313152075713340014103 0ustar00form_action( Router::ACTION_SAVE_SETTINGS_NETWORK ); ?>

form_end();tpl/img_optm/settings.media_webp.tpl.php000064400000003040152075713340014424 0ustar00 title( $option_id ); ?> build_switch( $option_id, array( esc_html__( 'OFF', 'litespeed-cache' ), 'WebP', 'AVIF' ) ); ?>



⚠️
⚠️
tpl/presets/entry.tpl.php000064400000002336152075713340011513 0ustar00 esc_html__( 'Standard Presets', 'litespeed-cache' ), 'import_export' => esc_html__( 'Import / Export', 'litespeed-cache' ), ); ?>

v
$val ) : ?>
tpl/presets/standard.tpl.php000064400000021160152075713340012146 0ustar00 array( 'title' => esc_html__( 'Essentials', 'litespeed-cache' ), 'body' => array( esc_html__( 'Default Cache', 'litespeed-cache' ), esc_html__( 'Higher TTL', 'litespeed-cache' ), esc_html__( 'Browser Cache', 'litespeed-cache' ), ), 'footer' => array( esc_html__( 'This no-risk preset is appropriate for all websites. Good for new users, simple websites, or cache-oriented development.', 'litespeed-cache' ), esc_html__( 'A QUIC.cloud connection is not required to use this preset. Only basic caching features are enabled.', 'litespeed-cache' ), ), ), 'basic' => array( 'title' => esc_html__( 'Basic', 'litespeed-cache' ), 'body' => array( esc_html__( 'Everything in Essentials, Plus', 'litespeed-cache' ), esc_html__( 'Image Optimization', 'litespeed-cache' ), esc_html__( 'Mobile Cache', 'litespeed-cache' ), ), 'footer' => array( esc_html__( 'This low-risk preset introduces basic optimizations for speed and user experience. Appropriate for enthusiastic beginners.', 'litespeed-cache' ), esc_html__( 'A QUIC.cloud connection is required to use this preset. Includes optimizations known to improve site score in page speed measurement tools.', 'litespeed-cache' ), ), ), 'advanced' => array( 'title' => esc_html__( 'Advanced (Recommended)', 'litespeed-cache' ), 'body' => array( esc_html__( 'Everything in Basic, Plus', 'litespeed-cache' ), esc_html__( 'Guest Mode and Guest Optimization', 'litespeed-cache' ), esc_html__( 'CSS, JS and HTML Minification', 'litespeed-cache' ), esc_html__( 'Font Display Optimization', 'litespeed-cache' ), esc_html__( 'JS Defer for both external and inline JS', 'litespeed-cache' ), esc_html__( 'DNS Prefetch for static files', 'litespeed-cache' ), esc_html__( 'Gravatar Cache', 'litespeed-cache' ), esc_html__( 'Remove Query Strings from Static Files', 'litespeed-cache' ), esc_html__( 'Remove WordPress Emoji', 'litespeed-cache' ), esc_html__( 'Remove Noscript Tags', 'litespeed-cache' ), ), 'footer' => array( esc_html__( 'This preset is good for most websites, and is unlikely to cause conflicts. Any CSS or JS conflicts may be resolved with Page Optimization > Tuning tools.', 'litespeed-cache' ), esc_html__( 'A QUIC.cloud connection is required to use this preset. Includes many optimizations known to improve page speed scores.', 'litespeed-cache' ), ), ), 'aggressive' => array( 'title' => esc_html__( 'Aggressive', 'litespeed-cache' ), 'body' => array( esc_html__( 'Everything in Advanced, Plus', 'litespeed-cache' ), esc_html__( 'CSS & JS Combine', 'litespeed-cache' ), esc_html__( 'Asynchronous CSS Loading with Critical CSS', 'litespeed-cache' ), esc_html__( 'Removed Unused CSS for Users', 'litespeed-cache' ), esc_html__( 'Lazy Load for Iframes', 'litespeed-cache' ), ), 'footer' => array( esc_html__( 'This preset might work out of the box for some websites, but be sure to test! Some CSS or JS exclusions may be necessary in Page Optimization > Tuning.', 'litespeed-cache' ), esc_html__( 'A QUIC.cloud connection is required to use this preset. Includes many optimizations known to improve page speed scores.', 'litespeed-cache' ), ), ), 'extreme' => array( 'title' => esc_html__( 'Extreme', 'litespeed-cache' ), 'body' => array( esc_html__( 'Everything in Aggressive, Plus', 'litespeed-cache' ), esc_html__( 'Lazy Load for Images', 'litespeed-cache' ), esc_html__( 'Viewport Image Generation', 'litespeed-cache' ), esc_html__( 'JS Delayed', 'litespeed-cache' ), esc_html__( 'Inline JS added to Combine', 'litespeed-cache' ), esc_html__( 'Inline CSS added to Combine', 'litespeed-cache' ), ), 'footer' => array( esc_html__( 'This preset almost certainly will require testing and exclusions for some CSS, JS and Lazy Loaded images. Pay special attention to logos, or HTML-based slider images.', 'litespeed-cache' ), esc_html__( 'A QUIC.cloud connection is required to use this preset. Enables the maximum level of optimizations for improved page speed scores.', 'litespeed-cache' ), ), ), ); ?>

$timestamp, 'time' => $time, 'title' => $curr_title, ); } if ( ! empty( $summary['preset'] ) || ! empty( $backups ) ) : ?>

' . esc_html( $presets[ $name ]['title'] ) . '', esc_html( $time ) ); } ?>

tpl/cache/settings-purge.tpl.php000064400000014566152075713340012720 0ustar00

esc_html__( 'All pages', 'litespeed-cache' ), Base::O_PURGE_POST_FRONTPAGE => esc_html__( 'Front page', 'litespeed-cache' ), Base::O_PURGE_POST_HOMEPAGE => esc_html__( 'Home page', 'litespeed-cache' ), Base::O_PURGE_POST_PAGES => esc_html__( 'Pages', 'litespeed-cache' ), Base::O_PURGE_POST_PAGES_WITH_RECENT_POSTS => esc_html__( 'All pages with Recent Posts Widget', 'litespeed-cache' ), Base::O_PURGE_POST_AUTHOR => esc_html__( 'Author archive', 'litespeed-cache' ), Base::O_PURGE_POST_POSTTYPE => esc_html__( 'Post type archive', 'litespeed-cache' ), Base::O_PURGE_POST_YEAR => esc_html__( 'Yearly archive', 'litespeed-cache' ), Base::O_PURGE_POST_MONTH => esc_html__( 'Monthly archive', 'litespeed-cache' ), Base::O_PURGE_POST_DATE => esc_html__( 'Daily archive', 'litespeed-cache' ), Base::O_PURGE_POST_TERM => esc_html__( 'Term archive (include category, tag, and tax)', 'litespeed-cache' ), ); // break line at these ids $break_arr = array( Base::O_PURGE_POST_PAGES, Base::O_PURGE_POST_PAGES_WITH_RECENT_POSTS, Base::O_PURGE_POST_POSTTYPE, Base::O_PURGE_POST_DATE, ); ?> _is_multisite ) : ?>



$cur_title ) { $this->build_checkbox( $option_id, $cur_title ); if ( in_array( $option_id, $break_arr, true ) ) { echo '
'; } } ?>
title( $option_id ); ?> build_switch( $option_id ); ?>


title( $option_id ); ?> build_textarea( $option_id, 80 ); ?>

http://www.example.com/path/url.php', '/path/url.php' ); ?>
*', '/path/u-1.html', '/path/u-2.html', '/path/u-*.html' ); ?>


title( $option_id ); ?> build_input( $option_id, null, null, 'time' ); ?>
' . esc_html( gmdate( 'H:i:s', time() + LITESPEED_TIME_OFFSET ) ) . '' ); ?>
title( $option_id ); ?>
tpl/cache/settings_inc.cache_dropquery.tpl.php000064400000002134152075713340015571 0ustar00 title( $option_id ); ?> build_textarea( $option_id, 40 ); ?>
utm', 'utm*' ); ?>

tpl/cache/network_settings-cache.tpl.php000064400000002413152075713340014376 0ustar00

build_switch( Base::O_CACHE ); ?>


tpl/cache/settings_inc.cache_mobile.tpl.php000064400000005351152075713340015012 0ustar00 title( $cid ); ?> build_switch( $cid ); ?>


title( $cid ); ?> conf( Base::O_CACHE_MOBILE ) ) { if ( defined( 'LITESPEED_ON' ) ) { try { $mobile_agents = Htaccess::cls()->current_mobile_agents(); if ( Utility::arr2regex( $this->conf( $cid ), true ) !== $mobile_agents ) { ?>

' . esc_html( $mobile_agents ) . '' ); ?>

getMessage() ); ?>

_validate_syntax( $cid ); ?> conf( Base::O_CACHE_MOBILE ) && ! $this->conf( $cid ) ) : ?> ' . esc_html__( 'Cache Mobile', 'litespeed-cache' ) . '', '' . esc_html__( 'ON', 'litespeed-cache' ) . '', '' . esc_html__( 'List of Mobile User Agents', 'litespeed-cache' ) . '' ); ?>
tpl/cache/entry.tpl.php000064400000007120152075713340011065 0ustar00_is_network_admin ) { $menu_list = array( 'cache' => __( 'Cache', 'litespeed-cache' ), 'purge' => __( 'Purge', 'litespeed-cache' ), 'excludes' => __( 'Excludes', 'litespeed-cache' ), 'object' => __( 'Object', 'litespeed-cache' ), 'browser' => __( 'Browser', 'litespeed-cache' ), 'advanced' => __( 'Advanced', 'litespeed-cache' ), ); ?>


cache_disabled_warning(); ?> form_action( Router::ACTION_SAVE_SETTINGS_NETWORK ); foreach ( $menu_list as $k => $val ) { $k_escaped = esc_attr( $k ); ?>
form_end(); ?>
__( 'Cache', 'litespeed-cache' ), 'ttl' => __( 'TTL', 'litespeed-cache' ), 'purge' => __( 'Purge', 'litespeed-cache' ), 'excludes' => __( 'Excludes', 'litespeed-cache' ), 'esi' => __( 'ESI', 'litespeed-cache' ), ); if ( ! $this->_is_multisite ) { $menu_list['object'] = __( 'Object', 'litespeed-cache' ); $menu_list['browser'] = __( 'Browser', 'litespeed-cache' ); } $menu_list['advanced'] = __( 'Advanced', 'litespeed-cache' ); /** * Generate roles for setting usage * * @since 1.6.2 */ global $wp_roles; $wp_orig_roles = $wp_roles; if ( ! isset( $wp_roles ) ) { $wp_orig_roles = new \WP_Roles(); } $roles = array(); foreach ( $wp_orig_roles->roles as $k => $v ) { $roles[ $k ] = $v['name']; } ksort( $roles ); ?>


cache_disabled_warning(); ?> form_action(); require LSCWP_DIR . 'tpl/inc/check_if_network_disable_all.php'; require LSCWP_DIR . 'tpl/cache/more_settings_tip.tpl.php'; foreach ( $menu_list as $k => $val ) { echo '
'; require LSCWP_DIR . "tpl/cache/settings-$k.tpl.php"; echo '
'; } do_action( 'litespeed_settings_content', 'cache' ); $this->form_end(); ?>
tpl/cache/settings-ttl.tpl.php000064400000006563152075713340012377 0ustar00

title( $option_id ); ?> build_input( $option_id ); ?> readable_seconds(); ?>
recommended( $option_id ); ?> _validate_ttl( $option_id, 30 ); ?>
title( $option_id ); ?> build_input( $option_id ); ?> readable_seconds(); ?>
recommended( $option_id ); ?> _validate_ttl( $option_id, 60, 3600 ); ?>
title( $option_id ); ?> build_input( $option_id ); ?> readable_seconds(); ?>
recommended( $option_id ); ?> _validate_ttl( $option_id, 30 ); ?>
title( $option_id ); ?> build_input( $option_id ); ?> readable_seconds(); ?>
recommended( $option_id ); ?>
title( $option_id ); ?> build_input( $option_id ); ?> readable_seconds(); ?>
recommended( $option_id ); ?>
title( $option_id ); ?>
tpl/cache/more_settings_tip.tpl.php000064400000001236152075713340013464 0ustar00

' . esc_html__( 'LiteSpeed Cache', 'litespeed-cache' ) . '' ); ?>

tpl/cache/settings_inc.browser.tpl.php000064400000004453152075713340014105 0ustar00

:

title( $option_id ); ?> build_switch( $option_id ); ?>


', '' ); ?>
title( $option_id ); ?> build_input( $option_id ); ?> readable_seconds(); ?>
recommended( $option_id ); ?> _validate_ttl( $option_id, 30 ); ?>
tpl/cache/settings_inc.login_cookie.tpl.php000064400000007042152075713340015060 0ustar00 title( $option_id ); ?> build_input( $option_id ); ?> _validate_syntax( $option_id ); ?>

_lscache_vary' ); ?>

www.example.com' ); ?>
www.example.com/blog/' ); ?>
conf( $option_id ) ) ) : ?>

conf( $option_id ) ) { $cookie_rule = ''; try { $cookie_rule = Htaccess::cls()->current_login_cookie(); } catch ( \Exception $e ) { ?>

getMessage() ); ?>

conf( $option_id ), $cookie_arr, true ) ) { ?>

title( $option_id ); ?> build_textarea( $option_id, 50 ); ?> _validate_syntax( $option_id ); ?>


tpl/cache/settings-object.tpl.php000064400000000321152075713340013024 0ustar00

tpl/cache/settings-excludes.tpl.php000064400000011463152075713340013403 0ustar00

_is_multisite ) : require LSCWP_DIR . 'tpl/cache/settings_inc.exclude_cookies.tpl.php'; require LSCWP_DIR . 'tpl/cache/settings_inc.exclude_useragent.tpl.php'; endif; ?>
title( $option_id ); ?> build_textarea( $option_id ); ?>
title( $option_id ); ?> build_textarea( $option_id ); ?>
?aa=bb&cc=dd', 'aa', 'cc' ); ?>
title( $option_id ); ?> conf( $option_id ) ) { $excludes_buf = implode( "\n", array_map( 'get_cat_name', $this->conf( $option_id ) ) ); } $this->build_textarea( $option_id, false, $excludes_buf ); ?>

:

title( $option_id ); ?> conf( $option_id ) ) { $tag_names = array(); foreach ( array_map( 'get_tag', $this->conf( $option_id ) ) as $curr_tag ) { $tag_names[] = $curr_tag->name; } if ( ! empty( $tag_names ) ) { $excludes_buf = implode( "\n", $tag_names ); } } $this->build_textarea( $option_id, false, $excludes_buf ); ?>

:

  1. http://www.example.com/tag/category/tag-slug/', 'tag-slug' ); ?>
title( $option_id ); ?>
$curr_title ) : ?> build_checkbox( $option_id . '[]', esc_html( $curr_title ), Control::cls()->in_cache_exc_roles( $curr_role ), $curr_role ); ?>
tpl/cache/settings_inc.exclude_useragent.tpl.php000064400000001432152075713340016122 0ustar00 title( $option_id ); ?> build_textarea( $option_id ); ?>
_validate_syntax( $option_id ); ?>
tpl/cache/settings_inc.object.tpl.php000064400000020046152075713340013664 0ustar00' . esc_html__( 'Enabled', 'litespeed-cache' ) . ''; $lang_disabled = '' . esc_html__( 'Disabled', 'litespeed-cache' ) . ''; $mem_enabled = class_exists( 'Memcached' ) ? $lang_enabled : $lang_disabled; $redis_enabled = class_exists( 'Redis' ) ? $lang_enabled : $lang_disabled; $mem_conn = $this->cls( 'Object_Cache' )->test_connection(); if ( null === $mem_conn ) { $mem_conn_desc = '' . esc_html__( 'Not Available', 'litespeed-cache' ) . ''; } elseif ( $mem_conn ) { $mem_conn_desc = '' . esc_html__( 'Passed', 'litespeed-cache' ) . ''; } else { $severity = $this->conf( Base::O_OBJECT, true ) ? 'danger' : 'warning'; $mem_conn_desc = '' . esc_html__( 'Failed', 'litespeed-cache' ) . ''; } ?>

title( $option_id ); ?> build_switch( $option_id ); ?>

:
:
:
title( $option_id ); ?> build_switch( $option_id, array( 'Memcached', 'Redis' ) ); ?>
title( $option_id ); ?> build_input( $option_id ); ?>
LSMCD/Redis' ); ?>
/path/to/memcached.sock' ); ?>
title( $option_id ); ?> build_input( $option_id, 'litespeed-input-short2' ); ?>
11211' ); ?>
6379' ); ?>
0' ); ?>
title( $option_id ); ?> build_input( $option_id, 'litespeed-input-short2' ); ?>
title( $option_id ); ?> build_input( $option_id ); ?>
title( $option_id ); ?> build_input( $option_id ); ?>
title( $option_id ); ?> build_input( $option_id, 'litespeed-input-short' ); ?>
title( $option_id ); ?> build_textarea( $option_id, 30 ); ?>
title( $option_id ); ?> build_textarea( $option_id, 30 ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>
tpl/cache/settings_inc.purge_on_upgrade.tpl.php000064400000001175152075713340015745 0ustar00 title( $option_id ); ?> build_switch( $option_id ); ?>
tpl/cache/settings_inc.exclude_cookies.tpl.php000064400000001407152075713340015563 0ustar00 title( $option_id ); ?> build_textarea( $option_id ); ?>
_validate_syntax( $option_id ); ?>
tpl/cache/network_settings-advanced.tpl.php000064400000001077152075713350015106 0ustar00

tpl/cache/settings-advanced.tpl.php000064400000005065152075713350013336 0ustar00

_is_multisite ) : ?>
title( $option_id ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>

⚠️
tpl/cache/settings-esi.tpl.php000064400000013463152075713350012352 0ustar00

💡: [shortcodeA att1="val1" att2="val2"]', '[esi shortcodeA att1="val1" att2="val2"]' ); ?>

conf( Base::O_CDN_QUIC ) ) : ?>

title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?>
build_textarea( $option_id ); ?>

: https://github.com/litespeedtech/lscache_wp/blob/master/data/esi.nonces.txt
: litespeed_esi_nonces' ); ?>


: my_nonce_action private
*', 'nonce_formid_1', 'nonce_formid_3', 'nonce_formid_*' ); ?>
title( $option_id ); ?> $curr_title ) : ?>
build_input( $option_id . '[' . $curr_role . ']', 'litespeed-input-short', $this->cls( 'Vary' )->in_vary_group( $curr_role ) ); ?>
tpl/cache/settings-cache.tpl.php000064400000013220152075713350012624 0ustar00

_is_multisite ) : ?> _is_multisite ) : ?>
title( $option_id ); ?> _is_multisite ) : ?> build_switch( $option_id, array( esc_html__( 'OFF', 'litespeed-cache' ), esc_html__( 'ON', 'litespeed-cache' ), esc_html__( 'Use Network Admin Setting', 'litespeed-cache' ) ) ); ?> build_switch( $option_id ); ?>
', '' ); ?>
:
_is_multisite ) : ?>
conf( Base::O_CACHE ) && $this->conf( Base::O_CDN_QUIC ) ) : ?>
title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?> build_textarea( $option_id ); ?>
_uri_usage_example(); ?>
title( $option_id ); ?> build_textarea( $option_id ); ?>
_uri_usage_example(); ?>
/mypath/mypage 300', 300, '/mypath/mypage' ); ?>
title( $option_id ); ?> build_textarea( $option_id ); ?>
_uri_usage_example(); ?>
/mypath/mypage 300', 300, '/mypath/mypage' ); ?>
tpl/cache/network_settings-purge.tpl.php000064400000001107152075713350014455 0ustar00

tpl/cache/settings-browser.tpl.php000064400000000322152075713350013243 0ustar00generate_environment_report(); $env_ref = Report::get_summary(); // Detect passwordless plugin $dologin_link = ''; $has_pswdless_plugin = false; if ( function_exists( 'dologin_gen_link' ) ) { $has_pswdless_plugin = true; if ( ! empty( $_GET['dologin_gen_link'] ) && ! empty( $_GET['litespeed_purge_nonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['litespeed_purge_nonce'] ) ), 'litespeed_purge_action' ) ) { unset( $_GET['dologin_gen_link'] ); $dologin_link = dologin_gen_link( 'Litespeed Report' ); ?> 'dologin' ) ); $btn_title = esc_html__( 'Send to LiteSpeed', 'litespeed-cache' ); if ( ! empty( $env_ref['num'] ) ) { $btn_title = esc_html__( 'Regenerate and Send a New Report', 'litespeed-cache' ); } ?>

DoLogin Security' ); ?>

: ' . esc_html( $env_ref['num'] ) . '' : '-'; ?>

:


form_action( Router::ACTION_REPORT, Report::TYPE_SEND_REPORT ); ?>
build_checkbox( 'attach_php', sprintf( esc_html__( 'Attach PHP info to report. Check this box to insert relevant data from %s.', 'litespeed-cache' ), 'phpinfo()' ), false ); ?>

🚨 ', '' ); ?>
:

tpl/toolbox/entry.tpl.php000064400000003277152075713350011522 0ustar00 esc_html__( 'Purge', 'litespeed-cache' ), ); if ( ! $this->_is_network_admin ) { $menu_list['import_export'] = esc_html__( 'Import / Export', 'litespeed-cache' ); } if ( ! $this->_is_multisite || $this->_is_network_admin ) { $menu_list['edit_htaccess'] = esc_html__( 'View .htaccess', 'litespeed-cache' ); } if ( ! $this->_is_network_admin ) { $menu_list['heartbeat'] = esc_html__( 'Heartbeat', 'litespeed-cache' ); $menu_list['report'] = esc_html__( 'Report', 'litespeed-cache' ); } if ( ! $this->_is_multisite || $this->_is_network_admin ) { $menu_list['settings-debug'] = esc_html__( 'Debug Settings', 'litespeed-cache' ); $menu_list['log_viewer'] = esc_html__( 'Log View', 'litespeed-cache' ); $menu_list['beta_test'] = esc_html__( 'Beta Test', 'litespeed-cache' ); } ?>

v
$val ) : ?>
tpl/toolbox/import_export.tpl.php000064400000005265152075713350013273 0ustar00

:

form_action( Router::ACTION_IMPORT, Import::TYPE_IMPORT, true ); ?>
:

🚨

tpl/toolbox/settings-debug.tpl.php000064400000014730152075713350013301 0ustar00form_action( $this->_is_network_admin ? Router::ACTION_SAVE_SETTINGS_NETWORK : false ); ?>

conf( Base::DEBUG_TMP_DISABLE ); $temp_disabled = Debug2::is_tmp_disable(); if ( !$temp_disabled ) { ?>

' . $date . '' ) ); ?>

title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?> build_switch( $option_id, array( esc_html__( 'OFF', 'litespeed-cache' ), esc_html__( 'ON', 'litespeed-cache' ), esc_html__( 'Admin IP Only', 'litespeed-cache' ) ) ); ?>
wp-content/litespeed/debug' ); ?>
title( $option_id ); ?> build_textarea( $option_id, 50 ); ?>
: _validate_ip( $option_id ); ?>
title( $option_id ); ?> build_switch( $option_id, array( esc_html__( 'Basic', 'litespeed-cache' ), esc_html__( 'Advanced', 'litespeed-cache' ) ) ); ?>
title( $option_id ); ?> build_input( $option_id, 'litespeed-input-short' ); ?>
recommended( $option_id ); ?> _validate_ttl( $option_id, 3, 3000 ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?> build_textarea( $option_id ); ?>
_uri_usage_example(); ?>
title( $option_id ); ?> build_textarea( $option_id ); ?>
_uri_usage_example(); ?>
title( $option_id ); ?> build_textarea( $option_id ); ?>
form_end(); ?>tpl/toolbox/purge.tpl.php000064400000027313152075713350011500 0ustar00 esc_html__( 'Purge Front Page', 'litespeed-cache' ), 'desc' => esc_html__( 'This will Purge Front Page only', 'litespeed-cache' ), 'icon' => 'purge-front', 'append_url' => Purge::TYPE_PURGE_FRONTPAGE, ), array( 'title' => esc_html__( 'Purge Pages', 'litespeed-cache' ), 'desc' => esc_html__( 'This will Purge Pages only', 'litespeed-cache' ), 'icon' => 'purge-pages', 'append_url' => Purge::TYPE_PURGE_PAGES, ), ); foreach ( Tag::$error_code_tags as $code ) { $_panels[] = array( 'title' => sprintf( esc_html__( 'Purge %s Error', 'litespeed-cache' ), esc_html( $code ) ), 'desc' => sprintf( esc_html__( 'Purge %s error pages', 'litespeed-cache' ), esc_html( $code ) ), 'icon' => 'purge-' . esc_attr( $code ), 'append_url' => Purge::TYPE_PURGE_ERROR . esc_attr( $code ), ); } $_panels[] = array( 'title' => esc_html__( 'Purge All', 'litespeed-cache' ) . ' - LSCache', 'desc' => esc_html__( 'Purge the LiteSpeed cache entries created by this plugin', 'litespeed-cache' ), 'icon' => 'purge-all', 'append_url' => Purge::TYPE_PURGE_ALL_LSCACHE, ); $_panels[] = array( 'title' => esc_html__( 'Purge All', 'litespeed-cache' ) . ' - ' . esc_html__( 'CSS/JS Cache', 'litespeed-cache' ), 'desc' => esc_html__( 'This will purge all minified/combined CSS/JS entries only', 'litespeed-cache' ), 'icon' => 'purge-cssjs', 'append_url' => Purge::TYPE_PURGE_ALL_CSSJS, ); if ( defined( 'LSCWP_OBJECT_CACHE' ) ) { $_panels[] = array( 'title' => esc_html__( 'Purge All', 'litespeed-cache' ) . ' - ' . esc_html__( 'Object Cache', 'litespeed-cache' ), 'desc' => esc_html__( 'Purge all the object caches', 'litespeed-cache' ), 'icon' => 'purge-object', 'append_url' => Purge::TYPE_PURGE_ALL_OBJECT, ); } if ( Router::opcache_enabled() ) { $_panels[] = array( 'title' => esc_html__( 'Purge All', 'litespeed-cache' ) . ' - ' . esc_html__( 'Opcode Cache', 'litespeed-cache' ), 'desc' => esc_html__( 'Reset the entire opcode cache', 'litespeed-cache' ), 'icon' => 'purge-opcache', 'append_url' => Purge::TYPE_PURGE_ALL_OPCACHE, ); } if ( $this->has_cache_folder( 'ccss' ) ) { $_panels[] = array( 'title' => esc_html__( 'Purge All', 'litespeed-cache' ) . ' - ' . esc_html__( 'Critical CSS', 'litespeed-cache' ), 'desc' => esc_html__( 'This will delete all generated critical CSS files', 'litespeed-cache' ), 'icon' => 'purge-cssjs', 'append_url' => Purge::TYPE_PURGE_ALL_CCSS, ); } if ( $this->has_cache_folder( 'ucss' ) ) { $_panels[] = array( 'title' => esc_html__( 'Purge All', 'litespeed-cache' ) . ' - ' . esc_html__( 'Unique CSS', 'litespeed-cache' ), 'desc' => esc_html__( 'This will delete all generated unique CSS files', 'litespeed-cache' ), 'icon' => 'purge-cssjs', 'append_url' => Purge::TYPE_PURGE_ALL_UCSS, ); } if ( $this->has_cache_folder( 'localres' ) ) { $_panels[] = array( 'title' => esc_html__( 'Purge All', 'litespeed-cache' ) . ' - ' . esc_html__( 'Localized Resources', 'litespeed-cache' ), 'desc' => esc_html__( 'This will delete all localized resources', 'litespeed-cache' ), 'icon' => 'purge-cssjs', 'append_url' => Purge::TYPE_PURGE_ALL_LOCALRES, ); } if ( $this->has_cache_folder( 'lqip' ) ) { $_panels[] = array( 'title' => esc_html__( 'Purge All', 'litespeed-cache' ) . ' - ' . esc_html__( 'LQIP Cache', 'litespeed-cache' ), 'desc' => esc_html__( 'This will delete all generated image LQIP placeholder files', 'litespeed-cache' ), 'icon' => 'purge-front', 'append_url' => Purge::TYPE_PURGE_ALL_LQIP, ); } if ( $this->has_cache_folder( 'vpi' ) ) { $_panels[] = array( 'title' => __( 'Purge All', 'litespeed-cache' ) . ' - VPI', 'desc' => __( 'This will delete all generated Viewport Images', 'litespeed-cache' ), 'icon' => 'purge-front', 'append_url' => Purge::TYPE_PURGE_ALL_VPI, ); } if ( $this->has_cache_folder( 'avatar' ) ) { $_panels[] = array( 'title' => esc_html__( 'Purge All', 'litespeed-cache' ) . ' - ' . esc_html__( 'Gravatar Cache', 'litespeed-cache' ), 'desc' => esc_html__( 'This will delete all cached Gravatar files', 'litespeed-cache' ), 'icon' => 'purge-cssjs', 'append_url' => Purge::TYPE_PURGE_ALL_AVATAR, ); } $_panels[] = array( 'title' => esc_html__( 'Purge All', 'litespeed-cache' ), 'desc' => esc_html__( 'Purge the cache entries created by this plugin except for Critical CSS & Unique CSS & LQIP caches', 'litespeed-cache' ), 'icon' => 'purge-all', 'title_cls' => 'litespeed-warning', 'newline' => true, 'append_url' => Purge::TYPE_PURGE_ALL, ); if ( ! is_multisite() || is_network_admin() ) { $_panels[] = array( 'title' => esc_html__( 'Empty Entire Cache', 'litespeed-cache' ), 'desc' => esc_html__( 'Clears all cache entries related to this site, including other web applications.', 'litespeed-cache' ) . ' ' . esc_html__( 'This action should only be used if things are cached incorrectly.', 'litespeed-cache' ) . '', 'tag' => Core::ACTION_PURGE_EMPTYCACHE, 'icon' => 'empty-cache', 'title_cls' => 'litespeed-danger', 'cfm' => esc_html__( 'This will clear EVERYTHING inside the cache.', 'litespeed-cache' ) . ' ' . esc_html__( 'This may cause heavy load on the server.', 'litespeed-cache' ) . ' ' . esc_html__( 'If only the WordPress site should be purged, use Purge All.', 'litespeed-cache' ), ); } ?>

form_action( Core::ACTION_PURGE_BY ); ?>
http://example.com/category/category-name/', 'category-name' ); ?>
http://example.com/tag/tag-name/', 'tag-name' ); ?>
/2016/02/24/hello-world/', 'http://example.com/2016/02/24/hello-world/' ); ?>

tpl/toolbox/edit_htaccess.tpl.php000064400000007031152075713350013153 0ustar00htaccess_read(); } catch ( \Exception $e ) { ?>

getMessage() ); ?>

:
: LITESPEED_CFG_HTACCESS' ); ?> defined("LITESPEED_CFG_HTACCESS") || define("LITESPEED_CFG_HTACCESS", "your path on server");', 'wp-config.php' ); ?>
:
: LITESPEED_CFG_HTACCESS_BACKEND' ); ?> defined("LITESPEED_CFG_HTACCESS_BACKEND") || define("LITESPEED_CFG_HTACCESS_BACKEND", "your path on server");', 'wp-config.php' ); ?>

tpl/toolbox/log_viewer.tpl.php000064400000006174152075713350012522 0ustar00 'debug', 'label' => esc_html__( 'Debug Log', 'litespeed-cache' ), 'accesskey' => 'A', ), array( 'name' => 'purge', 'label' => esc_html__( 'Purge Log', 'litespeed-cache' ), 'accesskey' => 'B', ), array( 'name' => 'crawler', 'label' => esc_html__( 'Crawler Log', 'litespeed-cache' ), 'accesskey' => 'C', ), ); ?>

cls( 'Debug2' )->path( $log['name'] ); $lines = File::count_lines( $file ); $max_lines = apply_filters( 'litespeed_debug_show_max_lines', 1000 ); $start = $lines > $max_lines ? $lines - $max_lines : 0; $lines = File::read( $file, $start ); $lines = $lines ? trim( implode( "\n", $lines ) ) : ''; $log_body_id = 'litespeed-log-' . esc_attr( $log['name'] ); ?> tpl/toolbox/heartbeat.tpl.php000064400000010532152075713350012310 0ustar00form_action(); ?>

🚨

title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?> build_input( $option_id, 'litespeed-input-short' ); ?> readable_seconds(); ?>
15 - 120' ); ?>
0', 'frontend' ); ?>
recommended( $option_id ); ?> _validate_ttl( $option_id, 15, 120, true ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?> build_input( $option_id, 'litespeed-input-short' ); ?> readable_seconds(); ?>
15 - 120' ); ?>
0', 'backend' ); ?>
recommended( $option_id ); ?> _validate_ttl( $option_id, 15, 120, true ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?> build_input( $option_id, 'litespeed-input-short' ); ?> readable_seconds(); ?>
15 - 120' ); ?>
0', 'backend editor' ); ?>
recommended( $option_id ); ?> _validate_ttl( $option_id, 15, 120, true ); ?>
form_end(); ?>tpl/toolbox/beta_test.tpl.php000064400000007647152075713350012340 0ustar00 form_action( Router::ACTION_DEBUG2, Debug2::TYPE_BETA_TEST ); ?>

: https://github.com/litespeedtech/lscache_wp/commit/example_comment_hash_d3ebec0535aaed5c932c0

dev

master

latest

🚨

' . esc_html__( 'Use latest GitHub Dev/Master commit', 'litespeed-cache' ) . '' ); ?>
' . esc_html__( 'Use latest WordPress release version', 'litespeed-cache' ) . '' ); ?>

🚨 v3.6.4', 'dev/master/v4+' ); ?>

tpl/crawler/summary.tpl.php000064400000044111152075713350012017 0ustar00list_crawlers(); $summary = Crawler::get_summary(); if ( $summary['curr_crawler'] >= count( $crawler_list ) ) { $summary['curr_crawler'] = 0; } $is_running = time() - $summary['is_running'] <= 900; $disabled = Router::can_crawl() ? '' : 'disabled'; $disabled_tip = ''; if ( ! $this->conf( Base::O_CRAWLER_SITEMAP ) ) { $disabled = 'disabled'; $disabled_tip = '' . sprintf( esc_html__( 'You need to set the %s in Settings first before using the crawler', 'litespeed-cache' ), '' . esc_html( Lang::title( Base::O_CRAWLER_SITEMAP ) ) . '' ) . ''; } $crawler_run_interval = defined( 'LITESPEED_CRAWLER_RUN_INTERVAL' ) ? LITESPEED_CRAWLER_RUN_INTERVAL : 600; if ( $crawler_run_interval > 0 ) : $recurrence = ''; $hours = (int) floor( $crawler_run_interval / 3600 ); if ( $hours ) { $recurrence .= sprintf( $hours > 1 ? esc_html__( '%d hours', 'litespeed-cache' ) : esc_html__( '%d hour', 'litespeed-cache' ), $hours ); } $minutes = (int) floor( ( $crawler_run_interval % 3600 ) / 60 ); if ( $minutes ) { $recurrence .= ' '; $recurrence .= sprintf( $minutes > 1 ? esc_html__( '%d minutes', 'litespeed-cache' ) : esc_html__( '%d minute', 'litespeed-cache' ), $minutes ); } ?>

', '' ); ?>

:

: conf( Base::O_CRAWLER_CRAWL_INTERVAL ) ) ); ?>

:

:

:

: get_server_load() ); ?>

:

:

:

>

$v ) : $hit = ! empty( $summary['crawler_stats'][ $i ][ Crawler::STATUS_HIT ] ) ? (int) $summary['crawler_stats'][ $i ][ Crawler::STATUS_HIT ] : 0; $miss = ! empty( $summary['crawler_stats'][ $i ][ Crawler::STATUS_MISS ] ) ? (int) $summary['crawler_stats'][ $i ][ Crawler::STATUS_MISS ] : 0; $blacklisted = ! empty( $summary['crawler_stats'][ $i ][ Crawler::STATUS_BLACKLIST ] ) ? (int) $summary['crawler_stats'][ $i ][ Crawler::STATUS_BLACKLIST ] : 0; $blacklisted += ! empty( $summary['crawler_stats'][ $i ][ Crawler::STATUS_NOCACHE ] ) ? (int) $summary['crawler_stats'][ $i ][ Crawler::STATUS_NOCACHE ] : 0; $waiting = isset( $summary['crawler_stats'][ $i ][ Crawler::STATUS_WAIT ] ) ? (int) $summary['crawler_stats'][ $i ][ Crawler::STATUS_WAIT ] : (int) ( $summary['list_size'] - $hit - $miss - $blacklisted ); ?>
#
'; } ?> %s ', esc_attr__( 'Waiting', 'litespeed-cache' ), esc_html( $waiting > 0 ? $waiting : '-' ) ); printf( '%s ', esc_attr__( 'Hit', 'litespeed-cache' ), esc_html( $hit > 0 ? $hit : '-' ) ); printf( '%s ', esc_attr__( 'Miss', 'litespeed-cache' ), esc_html( $miss > 0 ? $miss : '-' ) ); printf( '%s ', esc_attr__( 'Blocklisted', 'litespeed-cache' ), esc_html( $blacklisted > 0 ? $blacklisted : '-' ) ); ?> build_toggle( 'litespeed-crawler-' . $i, $__crawler->is_active( $i ) ); ?> conf( Base::O_SERVER_IP ) ) ) : ?>
🚨 :
' . esc_html__( 'running', 'litespeed-cache' ) . ''; } } ?>

=
=
=
=

', '' ); ?>

json_path(); if ( $ajax_url ) : ?>
LiteSpeed Icon

tpl/crawler/entry.tpl.php000064400000002173152075713350011465 0ustar00 esc_html__( 'Summary', 'litespeed-cache' ), 'map' => esc_html__( 'Map', 'litespeed-cache' ), 'blacklist' => esc_html__( 'Blocklist', 'litespeed-cache' ), 'settings' => esc_html__( 'Settings', 'litespeed-cache' ), ]; ?>


$menu_value ) { printf( '
', esc_attr( $menu_key ) ); require LSCWP_DIR . "tpl/crawler/$menu_key.tpl.php"; echo '
'; } ?>
tpl/crawler/settings.tpl.php000064400000014144152075713350012165 0ustar00form_action(); ?>

title( $option_id ); ?> build_switch( $option_id ); ?>

title( $option_id ); ?> build_input( $option_id ); ?>
recommended( $option_id ); ?>
title( $option_id ); ?> build_textarea( $option_id ); ?>
title( $option_id ); ?> build_input( $option_id ); ?>
: ' . esc_html( sanitize_text_field( wp_unslash( $_SERVER[ Base::ENV_CRAWLER_LOAD_LIMIT_ENFORCE ] ) ) ) . '' ); ?> : ' . esc_html( sanitize_text_field( wp_unslash( $_SERVER[ Base::ENV_CRAWLER_LOAD_LIMIT ] ) ) ) . '' ); ?>
_api_env_var( Base::ENV_CRAWLER_LOAD_LIMIT, Base::ENV_CRAWLER_LOAD_LIMIT_ENFORCE ); ?>
title( $option_id ); ?> build_textarea( $option_id, 20 ); ?>
conf( Base::O_SERVER_IP ) ) ) : ?>
🚨 :
conf( Base::O_ESI ) ) ) : ?>
🚨 :
title( $option_id ); ?> enroll( $option_id . '[name][]' ); ?> enroll( $option_id . '[vals][]' ); ?>

_null', esc_html__( 'Cookie Values', 'litespeed-cache' ) ); ?>

form_end(); ?>tpl/crawler/blacklist.tpl.php000064400000006017152075713350012275 0ustar00list_blacklist( 30 ); $count = $__map->count_blacklist(); $pagination = Utility::pagination( $count, 30 ); ?>

$v ) : ?>
#
display_status( $v['res'], $v['reason'] ) ); ?>

LITESPEED_CRAWLER_DISABLE_BLOCKLIST' ); ?>

add_filter( \'litespeed_crawler_disable_blocklist\', \'__return_true\' );' ); ?>

_check_overwritten( 'crawler-blocklist' ); ?>

=
=
=

tpl/crawler/map.tpl.php000064400000010172152075713350011077 0ustar00list_map( 30 ); $count = $__map->count_map(); $pagination = Utility::pagination( $count, 30 ); $kw = ''; if (! empty( $_POST['kw'] ) && ! empty( $_POST['_wpnonce'] )) { $nonce = sanitize_text_field(wp_unslash($_POST['_wpnonce'])); if (wp_verify_nonce($nonce)) { $kw = sanitize_text_field(wp_unslash($_POST['kw'])); } } ?>

$v ) : ?>
#
display_status( $v['res'], $v['reason'] ) ); ?>

=
=
=
=

tpl/banner/slack.php000064400000004142152075713350010427 0ustar00

LiteSpeed Slack' ); ?>

golitespeed.slack.com

'slack' ) ); ?>
tpl/banner/ajax.php000064400000001151152075713350010252 0ustar00 tpl/banner/score.php000064400000016313152075713350010450 0ustar00scores(); // Exit if speed is not significantly improved or score is reduced. if ( $health_scores['speed_before'] <= $health_scores['speed_after'] * 2 || $health_scores['score_before'] >= $health_scores['score_after'] ) { return; } // Banner can be shown now. $this->_promo_true = true; if ( $check_only ) { return; } $ajax_url_promo = Utility::build_url(Core::ACTION_DISMISS, GUI::TYPE_DISMISS_PROMO, true, null, array( 'promo_tag' => $promo_tag ), true); ?>


s

s

%


get_cls_of_pagescore( $health_scores['score_before'] ) ) ), GUI::allowed_svg_tags() ); ?>

get_cls_of_pagescore( $health_scores['score_after'] ) ) ), GUI::allowed_svg_tags() ); ?>

%
'score', 'later' => 1, ) ); ?>
tpl/banner/cloud_news.tpl.php000064400000004202152075713350012267 0ustar00

_summary['news.title'] ); ?>

_summary['news.content'] ); ?>

_summary['news.plugin'] ) ) : ?> $this->_summary['news.plugin'] ) ); ?> _summary['news.plugin_name'] ) ) { echo esc_html( $this->_summary['news.plugin_name'] ); } ?> _summary['news.zip'] ) ) : ?>
X
tpl/banner/new_version_dev.tpl.php000064400000003060152075713350013322 0ustar00

:

_summary['version.dev'] ) ); ?>

'dev' ) ); ?>
tpl/banner/cloud_promo.tpl.php000064400000005724152075713350012461 0ustar00

' . absint($this->_summary['promo'][0]['quota']) . '' ); ?>

_summary['promo'][0]['quota']) ); ?>

_summary['promo'][0]['content']); ?>

. X
tpl/banner/new_version.php000064400000007473152075713350011702 0ustar00conf( Base::O_AUTO_UPGRADE ) ) { return; } $current = get_site_transient( 'update_plugins' ); if ( ! isset( $current->response[ Core::PLUGIN_FILE ] ) ) { return; } // Check for new version every 12 hours. $last_check = empty( $this->_summary['new_version.last_check'] ) ? 0 : $this->_summary['new_version.last_check']; if ( time() - $last_check > 43200 ) { GUI::save_summary( array( 'new_version.last_check' => time() ) ); // Detect version $auto_v = Cloud::version_check( 'new_version_banner' ); if ( ! empty( $auto_v['latest'] ) ) { GUI::save_summary( array( 'new_version.v' => $auto_v['latest'] ) ); } // After detect, don't show, just return and show next time return; } if ( ! isset( $this->_summary['new_version.v'] ) || version_compare( Core::VER, $this->_summary['new_version.v'], '>=' ) ) { return; } // Banner can be shown now. $this->_promo_true = true; if ( $check_only ) { return; } ?>

:

_summary['new_version.v'] ) ); ?>

1 ); $url = Utility::build_url( Router::ACTION_CONF, Conf::TYPE_SET, false, null, $cfg ); ?>
'new_version' ) ); ?>
'new_version', 'later' => 1, ) ); ?>
tpl/db_optm/entry.tpl.php000064400000002044152075713350011447 0ustar00 esc_html__( 'Manage', 'litespeed-cache' ), ); if ( ! is_network_admin() ) { $menu_list['settings'] = esc_html__( 'DB Optimization Settings', 'litespeed-cache' ); } ?>


$tab_val ) { echo '
'; require LSCWP_DIR . 'tpl/db_optm/' . $tab_key . '.tpl.php'; echo '
'; } ?>
tpl/db_optm/settings.tpl.php000064400000002760152075713350012153 0ustar00form_action(); ?>

title( $option_id ); ?> build_input( $option_id, 'litespeed-input-short' ); ?>
_validate_ttl( $option_id, 1, 100, true ); ?>
title( $option_id ); ?> build_input( $option_id, 'litespeed-input-short' ); ?>
_validate_ttl( $option_id, 1, 600, true ); ?>
form_end(); ?>tpl/db_optm/manage.tpl.php000064400000020051152075713350011534 0ustar00 array( 'title' => esc_html__( 'Clean All', 'litespeed-cache' ), 'desc' => '', ), 'revision' => array( 'title' => esc_html__( 'Post Revisions', 'litespeed-cache' ), 'desc' => esc_html__( 'Clean all post revisions', 'litespeed-cache' ), ), 'orphaned_post_meta' => array( 'title' => esc_html__( 'Orphaned Post Meta', 'litespeed-cache' ), 'desc' => esc_html__( 'Clean all orphaned post meta records', 'litespeed-cache' ), ), 'auto_draft' => array( 'title' => esc_html__( 'Auto Drafts', 'litespeed-cache' ), 'desc' => esc_html__( 'Clean all auto saved drafts', 'litespeed-cache' ), ), 'trash_post' => array( 'title' => esc_html__( 'Trashed Posts', 'litespeed-cache' ), 'desc' => esc_html__( 'Clean all trashed posts and pages', 'litespeed-cache' ), ), 'spam_comment' => array( 'title' => esc_html__( 'Spam Comments', 'litespeed-cache' ), 'desc' => esc_html__( 'Clean all spam comments', 'litespeed-cache' ), ), 'trash_comment' => array( 'title' => esc_html__( 'Trashed Comments', 'litespeed-cache' ), 'desc' => esc_html__( 'Clean all trashed comments', 'litespeed-cache' ), ), 'trackback-pingback' => array( 'title' => esc_html__( 'Trackbacks/Pingbacks', 'litespeed-cache' ), 'desc' => esc_html__( 'Clean all trackbacks and pingbacks', 'litespeed-cache' ), ), 'expired_transient' => array( 'title' => esc_html__( 'Expired Transients', 'litespeed-cache' ), 'desc' => esc_html__( 'Clean expired transient options', 'litespeed-cache' ), ), 'all_transients' => array( 'title' => esc_html__( 'All Transients', 'litespeed-cache' ), 'desc' => esc_html__( 'Clean all transient options', 'litespeed-cache' ), ), 'optimize_tables' => array( 'title' => esc_html__( 'Optimize Tables', 'litespeed-cache' ), 'desc' => esc_html__( 'Optimize all tables in your database', 'litespeed-cache' ), ), ); $rev_max = $this->conf( Base::O_DB_OPTM_REVISIONS_MAX ); $rev_age = $this->conf( Base::O_DB_OPTM_REVISIONS_AGE ); if ( $rev_max || $rev_age ) { $_panels['revision']['desc'] = sprintf( esc_html__( 'Clean revisions older than %1$s day(s), excluding %2$s latest revisions', 'litespeed-cache' ), '' . esc_html( $rev_age ) . '', '' . esc_html( $rev_max ) . '' ); } $total = 0; foreach ( $_panels as $key => $v ) { if ( 'all' !== $key ) { $_panels[ $key ]['count'] = $this->cls( 'DB_Optm' )->db_count( $key ); if ( ! in_array( $key, array( 'optimize_tables' ), true ) ) { $total += $_panels[ $key ]['count']; } } $_panels[ $key ]['link'] = Utility::build_url( Router::ACTION_DB_OPTM, $key ); } $_panels['all']['count'] = $total; $autoload_summary = DB_Optm::cls()->autoload_summary(); ?>

$v ) : ?>
()

list_myisam(); if ( ! empty( $list ) ) : foreach ( $list as $k => $v ) : ?>
#
table_name ); ?> engine ); ?>

: autoload_size ) ); ?>

: autload_entries ); ?>

:

autoload_toplist as $k => $v ) : ?>
#
option_name ); ?> autoload ); ?> option_value_length ); ?>
tpl/esi_widget_edit.php000064400000005075152075713350011223 0ustar00 Base::VAL_OFF, ESI::WIDGET_O_TTL => '28800', ); add_filter( 'litespeed_widget_default_options', 'LiteSpeed\ESI::widget_default_options', 10, 2 ); $options = apply_filters( 'litespeed_widget_default_options', $options, $widget ); } if ( empty( $options ) ) { $esi = Base::VAL_OFF; $ttl = '28800'; } else { $esi = $options[ ESI::WIDGET_O_ESIENABLE ]; $ttl = $options[ ESI::WIDGET_O_TTL ]; } $display = Admin_Display::cls(); ?>

:

:  
get_field_name( $esi_option ); $cache_status_list = array( array( Base::VAL_ON, esc_html__( 'Public', 'litespeed-cache' ) ), array( Base::VAL_ON2, esc_html__( 'Private', 'litespeed-cache' ) ), array( Base::VAL_OFF, esc_html__( 'Disable', 'litespeed-cache' ) ), ); foreach ( $cache_status_list as $v ) { list( $value, $label ) = $v; $id_attr = $widget->get_field_id( $esi_option ) . '_' . $value; $checked = $esi === $value ? 'checked' : ''; ?> />


:   get_field_name( $ttl_option ); ?>


tpl/inc/modal.deactivation.php000064400000013447152075713350012413 0ustar00 'Temporary', 'text' => esc_html__('The deactivation is temporary', 'litespeed-cache'), 'id' => 'temp', 'selected' => true, ), array( 'value' => 'Performance worse', 'text' => esc_html__('Site performance is worse', 'litespeed-cache'), 'id' => 'performance', ), array( 'value' => 'Plugin complicated', 'text' => esc_html__('Plugin is too complicated', 'litespeed-cache'), 'id' => 'complicated', ), array( 'value' => 'Other', 'text' => esc_html__('Other', 'litespeed-cache'), 'id' => 'other', ), ); ?>


', '' ); ?>
tpl/inc/metabox.php000064400000003122152075713350010272 0ustar00_postmeta_settings as $key => $label ) { $existing_val = get_post_meta( $pid, $key, true ); if ( in_array( $key, array( 'litespeed_vpi_list', 'litespeed_vpi_list_mobile' ), true ) ) { if ( is_array( $existing_val ) ) { $existing_val = implode( PHP_EOL, $existing_val ); } ?>
/>
tpl/inc/show_error_cookie.php000064400000001631152075713350012360 0ustar00', '' ); if (LITESPEED_SERVER_TYPE === 'LITESPEED_SERVER_OLS') { $err .= ' ' . esc_html__('If using OpenLiteSpeed, the server must be restarted once for the changes to take effect.', 'litespeed-cache'); } self::add_notice(self::NOTICE_YELLOW, $err); tpl/inc/show_display_installed.php000064400000003255152075713350013406 0ustar00%s

%s

%s

%s

%s

%s

  • %s
  • %s
', esc_html__( 'LiteSpeed Cache plugin is installed!', 'litespeed-cache' ), esc_html__( 'This message indicates that the plugin was installed by the server admin.', 'litespeed-cache' ), esc_html__( 'The LiteSpeed Cache plugin is used to cache pages - a simple way to improve the performance of the site.', 'litespeed-cache' ), esc_html__( 'However, there is no way of knowing all the possible customizations that were implemented.', 'litespeed-cache' ), esc_html__( 'For that reason, please test the site to make sure everything still functions properly.', 'litespeed-cache' ), esc_html__( 'Examples of test cases include:', 'litespeed-cache' ), esc_html__( 'Visit the site while logged out.', 'litespeed-cache' ), esc_html__( 'Create a post, make sure the front page is accurate.', 'litespeed-cache' ) ); $buf .= sprintf( /* translators: %s: Link tags */ esc_html__( 'If there are any questions, the team is always happy to answer any questions on the %ssupport forum%s.', 'litespeed-cache' ), '', '' ); $buf .= '

' . esc_html__( 'If you would rather not move at litespeed, you can deactivate this plugin.', 'litespeed-cache' ) . '

'; self::add_notice( self::NOTICE_BLUE . ' lscwp-whm-notice', $buf ); tpl/inc/admin_footer.php000064400000003400152075713350011300 0ustar00'; $rate_us = '' . sprintf( esc_html__( 'Rate %1$s on %2$s', 'litespeed-cache' ), '' . esc_html__( 'LiteSpeed Cache', 'litespeed-cache' ) . $stars . '', 'WordPress.org' ) . ''; $wiki = '' . esc_html__( 'Read LiteSpeed Documentation', 'litespeed-cache' ) . ''; $forum = '' . esc_html__( 'Visit LSCWP support forum', 'litespeed-cache' ) . ''; $community = '' . esc_html__( 'Join LiteSpeed Slack community', 'litespeed-cache' ) . ''; // Change the footer text if ( ! is_multisite() || is_network_admin() ) { $footer_text = $rate_us . ' | ' . $wiki . ' | ' . $forum . ' | ' . $community; } else { $footer_text = $wiki . ' | ' . $forum . ' | ' . $community; } tpl/inc/show_rule_conflict.php000064400000001647152075713350012535 0ustar00ExpiresDefault', '', '' ); // Other plugin left cache expired rules in .htaccess which will cause conflicts echo wp_kses_post( self::build_notice(self::NOTICE_YELLOW . ' lscwp-notice-ruleconflict', $err) ); tpl/inc/in_upgrading.php000064400000001020152075713350011274 0ustar00network_conf( Base::NETWORK_O_USE_PRIMARY ) ) { return; } ?>

tpl/inc/check_cache_disabled.php000064400000004003152075713350012661 0ustar00 esc_html__( 'To use the caching functions you must have a LiteSpeed web server or be using QUIC.cloud CDN.', 'litespeed-cache' ), 'link' => 'https://docs.litespeedtech.com/lscache/lscwp/faq/#why-do-the-cache-features-require-a-litespeed-server', ); } else { $reasons[] = array( 'title' => esc_html__( 'Please enable the LSCache Module at the server level, or ask your hosting provider.', 'litespeed-cache' ), 'link' => 'https://docs.litespeedtech.com/lscache/lscwp/#server-level-prerequisites', ); } } elseif ( ! defined( 'LITESPEED_ON' ) ) { $reasons[] = array( 'title' => esc_html__( 'Please enable LiteSpeed Cache in the plugin settings.', 'litespeed-cache' ), 'link' => 'https://docs.litespeedtech.com/lscache/lscwp/cache/#enable-cache', ); } if ( $reasons ) : ?>

tpl/general/entry.tpl.php000064400000002371152075713350011443 0ustar00 esc_html__( 'Online Services', 'litespeed-cache' ), 'settings' => esc_html__( 'General Settings', 'litespeed-cache' ), ); if ( is_network_admin() ) { $menu_list = array( 'network_settings' => esc_html__( 'General Settings', 'litespeed-cache' ), ); } ?>

v
$val ) { echo '
'; require LSCWP_DIR . 'tpl/general/' . $menu_key . '.tpl.php'; echo '
'; } ?>
tpl/general/settings.tpl.php000064400000013740152075713350012144 0ustar00form_action(); ?>

_is_multisite ) : ?> _is_multisite ) : ?>
title( $option_id ); ?> build_switch( $option_id ); ?>
🚨 conf( Base::O_GUEST ) && ! $this->conf( Base::O_OPTM_UCSS ) ) { $type_list[] = 'UCSS'; } if ( $this->conf( Base::O_GUEST ) && ! $this->conf( Base::O_OPTM_CSS_ASYNC ) ) { $type_list[] = 'CCSS'; } if ( ! empty( $type_list ) ) { $the_type = implode( '/', $type_list ); echo '
'; echo ''; echo '⚠️ ' . sprintf( esc_html__( 'Your %1$s quota on %2$s will still be in use.', 'litespeed-cache' ), esc_html( $the_type ), 'QUIC.cloud' ); echo ''; } ?> conf( Base::O_GUEST ) ) : ?>
⚠️ : ' . esc_html( Lang::title( Base::O_GUEST ) ) . '' ); ?> conf( Base::O_CACHE_MOBILE ) ) : ?>
⚠️ : ' . esc_html( Lang::title( Base::O_CACHE_MOBILE ) ) . '' ); ?> conf( Base::O_IMG_OPTM_WEBP ) ) : ?>
⚠️ : ' . esc_html( Lang::title( Base::O_IMG_OPTM_WEBP ) ) . '' ); ?>
title( $option_id ); ?> build_input( $option_id ); ?>

: - CyberPanel.sh ⚠️ :
_validate_ip( $option_id ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>
form_end(); ?> tpl/general/settings_inc.auto_upgrade.tpl.php000064400000001213152075713350015443 0ustar00 title( $option_id ); ?> build_switch( $option_id ); ?>
tpl/general/network_settings.tpl.php000064400000002273152075713350013714 0ustar00form_action(); ?>

form_action( Router::ACTION_SAVE_SETTINGS_NETWORK ); ?>
build_switch( Base::NETWORK_O_USE_PRIMARY ); ?>
form_end(); ?>tpl/general/online.tpl.php000064400000030136152075713350011566 0ustar00finish_qc_activation( 'online' ); ?>

activated() ) : ?>

' data-litespeed-cfm="">

%1$s %2$s %3$s %4$s %5$s %6$s

', esc_html__( 'Service:', 'litespeed-cache' ), esc_html( $svc ), esc_html__( 'Node:', 'litespeed-cache' ), esc_html( $cloud_summary[ 'server.' . $svc ] ), esc_html__( 'Connected Date:', 'litespeed-cache' ), esc_html( Utility::readable_time( $cloud_summary[ 'server_date.' . $svc ] ) ) ); } } if ( ! $has_service ) { esc_html_e( 'No cloud services currently in use', 'litespeed-cache' ); } ?>

activated() ) : ?>

  • Image Optimization gives you smaller image file sizes that transmit faster.', 'litespeed-cache' ) ); ?>
  • Page Optimization streamlines page styles and visual elements for faster loading.', 'litespeed-cache' ) ); ?>

  • Critical CSS (CCSS) loads visible above-the-fold content faster and with full styling.', 'litespeed-cache' ) ); ?>
  • Unique CSS (UCSS) removes unused style definitions for a speedier page load overall.', 'litespeed-cache' ) ); ?>
  • Low Quality Image Placeholder (LQIP) gives your imagery a more pleasing look as it lazy loads.', 'litespeed-cache' ) ); ?>
  • Viewport Images (VPI) provides a well-polished fully-loaded view above the fold.', 'litespeed-cache' ) ); ?>

  • ESI blocks.', 'litespeed-cache' ) ); ?>
  • network of 80+ PoPs.', 'litespeed-cache' ) ); ?>
  • security at the CDN level, protecting your server from attack.', 'litespeed-cache' ) ); ?>
  • built-in DNS service to simplify CDN onboarding.', 'litespeed-cache' ) ); ?>

anonymous user. The CDN function and certain features of optimization services are not available for anonymous users. Link to QUIC.cloud to use the CDN and all available Online Services features.', 'litespeed-cache' ) ); ?>

activated() ) : ?>
tpl/general/settings_inc.guest.tpl.php000064400000006356152075713350014130 0ustar00 title( $option_id ); ?> build_switch( $option_id ); ?>


conf( $option_id ) ) : ?>
: ...
: ...
tpl/optimax/summary.tpl.php000064400000001427152075713350012044 0ustar00

Coming soon

Placeholder

... Placeholder ...
tpl/optimax/entry.tpl.php000064400000002322152075713350011503 0ustar00 esc_html__( 'OptimaX Summary', 'litespeed-cache' ), 'settings' => esc_html__( 'OptimaX Settings', 'litespeed-cache' ), ); if ( is_network_admin() ) { $menu_list = array( 'network_settings' => esc_html__( 'OptimaX Settings', 'litespeed-cache' ), ); } ?>

v
$val ) { echo '
'; require LSCWP_DIR . 'tpl/optimax/' . $menu_key . '.tpl.php'; echo '
'; } ?>
tpl/optimax/settings.tpl.php000064400000001625152075713350012207 0ustar00form_action(); ?>

title( $option_id ); ?> build_switch( $option_id ); ?>
form_end(); tpl/page_optm/entry.tpl.php000064400000003761152075713350012005 0ustar00 esc_html__( 'CSS Settings', 'litespeed-cache' ), 'settings_js' => esc_html__( 'JS Settings', 'litespeed-cache' ), 'settings_html' => esc_html__( 'HTML Settings', 'litespeed-cache' ), 'settings_media' => esc_html__( 'Media Settings', 'litespeed-cache' ), 'settings_vpi' => esc_html__( 'VPI', 'litespeed-cache' ), 'settings_media_exc' => esc_html__( 'Media Excludes', 'litespeed-cache' ), 'settings_localization' => esc_html__( 'Localization', 'litespeed-cache' ), 'settings_tuning' => esc_html__( 'Tuning', 'litespeed-cache' ), 'settings_tuning_css' => esc_html__( 'Tuning', 'litespeed-cache' ) . ' - CSS', ); ?>

v

form_action(); // Include all tpl for faster UE foreach ( $menu_list as $tab_key => $tab_val ) { ?>
form_end(); ?>
tpl/page_optm/settings_media.tpl.php000064400000033217152075713350013642 0ustar00load_queue( 'lqip' ); $scaled_size = apply_filters( 'big_image_size_threshold', 2560, [], '', 0 ) . 'px'; ?>

title( $option_id ); ?> build_switch( $option_id ); ?>

💡
title( $option_id ); ?> build_input( $option_id, 'litespeed-input-long' ); ?>

LITESPEED_PLACEHOLDER', 'wp-config.php' ); ?>
data:image/gif;base64,R0lGODdhAQABAPAAAMPDwwAAACwAAAAAAQABAAACAkQBADs=' ); ?>
data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?> build_input( $option_id, 'litespeed-input-long' ); ?>

{width} {height}' ); ?>
{color}' ); ?>
recommended( $option_id ); ?>
title( $option_id ); ?> build_input( $option_id, null, null, 'color' ); ?>
recommended( $option_id ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>

title( $option_id ); ?> build_input( $option_id, 'litespeed-input-short' ); ?>

recommended( $option_id ); ?> _validate_ttl( $option_id, 1, 20 ); ?>
💡 ' . esc_html__( 'Purge All', 'litespeed-cache' ) . ' - ' . esc_html__( 'LQIP Cache', 'litespeed-cache' ) . '' ); ?>
title( $option_id ); ?> build_input( $option_id, 'litespeed-input-short' ); ?> x build_input( Base::O_MEDIA_LQIP_MIN_H, 'litespeed-input-short' ); ?>
recommended( $option_id ); ?> _validate_ttl( $option_id, 10, 800 ); ?> _validate_ttl( Base::O_MEDIA_LQIP_MIN_H, 10, 800 ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>
' . esc_html__( 'ON', 'litespeed-cache' ) . '', '' . esc_html( Lang::title( Base::O_MEDIA_PLACEHOLDER_RESP_SVG ) ) . '' ); ?> ' . esc_html__( 'OFF', 'litespeed-cache' ) . '' ); ?>

' . esc_html( Utility::readable_time( $placeholder_summary['last_request'] ) ) . ''; ?>

' data-litespeed-cfm="">

( )

$v ) { if ( $i++ > 20 ) { echo '...'; break; } echo esc_html( $v ); echo '
'; } ?>

title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>

⚠️ : ' . esc_html( Lang::title( Base::O_MEDIA_LAZY ) ) . '' ); ?>
: add_filter( "litespeed_media_ignore_remote_missing_sizes", "__return_true" );', '' . esc_html( Lang::title( Base::O_MEDIA_ADD_MISSING_SIZES ) ) . '' ); ?> _check_overwritten( Base::O_MEDIA_ADD_MISSING_SIZES ); ?>
title( $option_id ); ?> build_input( $option_id, 'litespeed-input-short' ); ?>
recommended( $option_id ); ?> _validate_ttl( $option_id, 0, 100 ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>
:
API: big_image_size_threshold' ); ?>
🚨
* * * */ ?>
tpl/page_optm/settings_tuning.tpl.php000064400000014057152075713350014070 0ustar00roles as $k => $v ) { $roles[ $k ] = $v['name']; } ksort( $roles ); ?>

title( $option_id ); ?> build_textarea( $option_id ); ?>

: litespeed_optm_js_delay_inc' ); ?>
title( $option_id ); ?> build_textarea( $option_id ); ?>

: litespeed_optimize_js_excludes' ); ?> data-no-optimize="1"' ); ?>
: https://github.com/litespeedtech/lscache_wp/blob/dev/data/js_excludes.txt
title( $option_id ); ?> build_textarea( $option_id ); ?>

: litespeed_optm_js_defer_exc' ); ?> data-no-defer="1"' ); ?>
: https://github.com/litespeedtech/lscache_wp/blob/dev/data/js_defer_excludes.txt
title( $option_id ); ?> build_textarea( $option_id ); ?>
' . esc_html( Lang::title( Base::O_GUEST ) ) . '' ); ?>
: litespeed_optm_gm_js_exc' ); ?> data-no-defer="1"' ); ?>
title( $option_id ); ?> build_textarea( $option_id ); ?>
_uri_usage_example(); ?>
: litespeed_optm_uri_exc' ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?>
$role_title ) { $this->build_checkbox( $option_id . '[]', $role_title, $this->cls( 'Conf' )->in_optm_exc_roles( $role_id ), $role_id ); } ?>
tpl/page_optm/settings_tuning_css.tpl.php000064400000016711152075713350014737 0ustar00

title( $option_id ); ?> build_textarea( $option_id ); ?>

: litespeed_optimize_css_excludes' ); ?> data-no-optimize="1"' ); ?>
: https://github.com/litespeedtech/lscache_wp/blob/dev/data/css_excludes.txt
title( $option_id ); ?> build_textarea( $option_id ); ?>
title( $option_id ); ?> build_textarea( $option_id ); ?>
title( $option_id ); ?> build_textarea( $option_id ); ?>

: litespeed_ucss_exc' ); ?>
: add_filter( 'litespeed_ucss_per_pagetype', function(){return get_post_type() == 'page';} );", 'page' ); ?>
: add_action( 'litespeed_optm', function(){get_post_type() == 'page' && do_action( 'litespeed_conf_force', 'optm-ucss', false );});", 'page' ); ?>
title( $option_id ); ?> build_textarea( $option_id ); ?>
page' ); ?>
title( $option_id ); ?> build_textarea( $option_id ); ?>
_uri_usage_example(); ?>
title( $option_id ); ?> build_textarea( $option_id ); ?>
title( $option_id ); ?> build_textarea( $option_id ); ?>
tpl/page_optm/settings_vpi.tpl.php000064400000012221152075713350013351 0ustar00load_queue( 'vpi' ); $vpi_service_hot = $this->cls( 'Cloud' )->service_hot( Cloud::SVC_VPI ); ?>

title( $option_id ); ?> build_switch( $option_id ); ?>


conf( Base::O_MEDIA_LAZY ) ) : ?>
⚠️ : ' . esc_html( Lang::title( Base::O_MEDIA_LAZY ) ) . '' ); ?>

' . esc_html( Utility::readable_time( $summary['last_request'] ) ) . ''; ?>

' data-litespeed-cfm="">

( )

$v ) { if ( $i++ > 20 ) { echo '...'; break; } if ( ! is_array( $v ) ) { continue; } if ( ! empty( $v['_status'] ) ) { echo ''; } echo esc_html( $v['url'] ); if ( ! empty( $v['_status'] ) ) { echo ''; } $pos = strpos( $k, ' ' ); if ( $pos ) { echo ' (' . esc_html__( 'Vary Group', 'litespeed-cache' ) . ':' . esc_html( substr( $k, 0, $pos ) ) . ')'; } if ( $v['is_mobile'] ) { echo ' 📱'; } echo '
'; } ?>

title( $option_id ); ?> build_switch( $option_id ); ?>
tpl/page_optm/settings_html.tpl.php000064400000014507152075713350013530 0ustar00

title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?> build_textarea( $option_id ); ?>
: //www.example.com
title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?> build_textarea( $option_id ); ?>
: https://example.com
title( $option_id ); ?> build_textarea( $option_id ); ?>
title( $option_id ); ?> build_textarea( $option_id ); ?>

<!-- A comment that needs to be here -->', 'A comment that needs to be here' ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>

⚠️
: &_litespeed_rm_qs=0' ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>
<noscript>' ); ?>
tpl/page_optm/settings_js.tpl.php000064400000006637152075713350013205 0ustar00

title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>

🚨
title( $option_id ); ?> build_switch( $option_id ); ?>
' . esc_html( Lang::title( Base::O_OPTM_JS_COMB ) ) . '' ); ?>
title( $option_id ); ?> build_switch( $option_id, array( esc_html__( 'OFF', 'litespeed-cache' ), esc_html__( 'Deferred', 'litespeed-cache' ), esc_html__( 'Delayed', 'litespeed-cache' ) ) ); ?>


🚨
tpl/page_optm/settings_localization.tpl.php000064400000012271152075713350015250 0ustar00queue_count(); ?> cls( 'Avatar' )->need_db() && ! $this->cls( 'Data' )->tb_exist( 'avatar' ) ) : ?>

Table Creation guidance from LiteSpeed Wiki to finish setup.', 'litespeed-cache' ), 'href="https://www.litespeedtech.com/support/wiki/doku.php/litespeed_wiki:cache:lscwp:installation" target="_blank"' ) ); ?>

title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>

' . esc_html( Utility::readable_time( $last_generated['last_request'] ) ) . ''; ?>

:

title( $option_id ); ?> build_input( $option_id ); ?> readable_seconds(); ?>
recommended( $option_id ); ?> _validate_ttl( $option_id, 3600 ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>

🚨 ' . esc_html( Lang::title( Base::O_OPTM_LOCALIZE_DOMAINS ) ) . '' ); ?>
title( $option_id ); ?>

#' ); ?>
: https://www.example.com/one.js
🚨
tpl/page_optm/settings_css.tpl.php000064400000037177152075713350013364 0ustar00load_queue( 'ccss' ); $ucss_queue = $this->load_queue( 'ucss' ); $next_gen = '' . $this->cls( 'Media' )->next_gen_image_title() . ''; $ucss_service_hot = $this->cls( 'Cloud' )->service_hot( Cloud::SVC_UCSS ); $ccss_service_hot = $this->cls( 'Cloud' )->service_hot( Cloud::SVC_CCSS ); ?>

title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>
cls( 'Cloud' )->activated() ) : ?>



: add_filter( "litespeed_ucss_per_pagetype", "__return_true" );' ); ?> _check_overwritten( 'optm-ucss_per_pagetype' ); ?> conf( Base::O_OPTM_UCSS ) && ! $this->conf( Base::O_OPTM_CSS_COMB ) ) : ?>
' . esc_html( Lang::title( Base::O_OPTM_CSS_COMB ) ) . '', '' . esc_html__( 'OFF', 'litespeed-cache' ) . '' ); ?>

' . esc_html( Utility::readable_time( $ucss_summary['last_request'] ) ) . ''; ?>

' . esc_html( $ucss_summary['last_spent'] ) . 's'; ?>

( )

$queue_val ) : if ( $i++ > 20 ) : echo '...'; break; endif; if ( ! is_array( $queue_val ) ) { continue; } if ( ! empty( $queue_val['_status'] ) ) { echo ''; } echo esc_html( $queue_val['url'] ); if ( ! empty( $queue_val['_status'] ) ) { echo ''; } $pos = strpos( $queue_key, ' ' ); if ( $pos ) { echo ' (' . esc_html__( 'Vary Group', 'litespeed-cache' ) . ':' . esc_html( substr( $queue_key, 0, $pos ) ) . ')'; } if ( $queue_val['is_mobile'] ) { echo ' 📱'; } if ( ! empty( $queue_val['is_webp'] ) ) { echo ' ' . wp_kses_post( $next_gen ); } echo '
'; endforeach; ?>

title( $option_id ); ?> build_switch( $option_id ); ?>
' . esc_html( Lang::title( Base::O_GUEST ) ) . '' ); ?>
' . esc_html( Lang::title( Base::O_OPTM_CSS_ASYNC ) ) . '' ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>
' . esc_html( Lang::title( Base::O_OPTM_CSS_COMB ) ) . '' ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>
cls( 'Cloud' )->activated() ) : ?>




' . esc_html__( 'ON', 'litespeed-cache' ) . '' ); ?>
: data-no-async="1"' ); ?> conf( Base::O_OPTM_CSS_ASYNC ) && $this->conf( Base::O_OPTM_CSS_COMB ) && $this->conf( Base::O_OPTM_UCSS ) && $this->conf( Base::O_OPTM_UCSS_INLINE ) ) : ?>
' . esc_html( Lang::title( Base::O_OPTM_UCSS_INLINE ) ) . '' ); ?>

' . esc_html( Utility::readable_time( $css_summary['last_request_ccss'] ) ) . ''; ?>

' . esc_html( $css_summary['last_spent_ccss'] ) . 's'; ?>

( )

$queue_val ) : if ( $i++ > 20 ) : echo '...'; break; endif; if ( ! is_array( $queue_val ) ) { continue; } if ( ! empty( $queue_val['_status'] ) ) { echo ''; } echo esc_html( $queue_val['url'] ); if ( ! empty( $queue_val['_status'] ) ) { echo ''; } $pos = strpos( $queue_key, ' ' ); if ( $pos ) { echo ' (' . esc_html__( 'Vary Group', 'litespeed-cache' ) . ':' . esc_html( substr( $queue_key, 0, $pos ) ) . ')'; } if ( $queue_val['is_mobile'] ) { echo ' 📱'; } if ( ! empty( $queue_val['is_webp'] ) ) { echo ' ' . wp_kses_post( $next_gen ); } echo '
'; endforeach; ?>

title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>
title( $option_id ); ?> build_switch( $option_id, array( esc_html__( 'Default', 'litespeed-cache' ), 'Swap' ) ); ?>
font-display', '@font-face' ); ?>
' . esc_html__( 'Swap', 'litespeed-cache' ) . '' ); ?>
tpl/page_optm/settings_media_exc.tpl.php000064400000007574152075713350014510 0ustar00

title( $option_id ); ?> build_textarea( $option_id ); ?>


: litespeed_media_lazy_img_excludes' ); ?> data-no-lazy="1"' ); ?>
title( $option_id ); ?>
title( $option_id ); ?> build_textarea( $option_id ); ?>
title( $option_id ); ?> build_textarea( $option_id ); ?>
title( $option_id ); ?> build_textarea( $option_id ); ?>
title( $option_id ); ?> build_textarea( $option_id ); ?>
_uri_usage_example(); ?>
title( $option_id ); ?> build_textarea( $option_id ); ?>
tpl/cdn/other.tpl.php000064400000016047152075713350010557 0ustar00conf( Base::O_CDN_MAPPING ); // Special handler: Append one row if somehow the DB default preset value got deleted if ( ! $cdn_mapping ) { $this->load_default_vals(); $cdn_mapping = self::$_default_options[ Base::O_CDN_MAPPING ]; } $this->form_action(); ?>

title( $option_id ); ?> build_switch( $option_id ); ?>
' . esc_html__( 'ON', 'litespeed-cache' ) . '' ); ?>
' . esc_html__( 'OFF', 'litespeed-cache' ) . '' ); ?>
enroll( Base::O_CDN_MAPPING . '[' . Base::CDN_MAPPING_URL . '][]' ); ?> enroll( Base::O_CDN_MAPPING . '[' . Base::CDN_MAPPING_INC_IMG . '][]' ); ?> enroll( Base::O_CDN_MAPPING . '[' . Base::CDN_MAPPING_INC_CSS . '][]' ); ?> enroll( Base::O_CDN_MAPPING . '[' . Base::CDN_MAPPING_INC_JS . '][]' ); ?> enroll( Base::O_CDN_MAPPING . '[' . Base::CDN_MAPPING_FILETYPE . '][]' ); ?>
:
title( Base::CDN_MAPPING_INC_IMG ); ?>: <img', 'url()' ); ?>
title( Base::CDN_MAPPING_INC_CSS ); ?>:
title( Base::CDN_MAPPING_INC_JS ); ?>:
title( Base::CDN_MAPPING_FILETYPE ); ?>: src="" data-src="" href=""' ); ?>
' . esc_html__( 'Include File Types', 'litespeed-cache' ) . '' ); ?>
title( $option_id ); ?>

element.attribute', '.attribute' ); ?>
title( $option_id ); ?> build_textarea( $option_id ); ?>
//', '' . esc_html( $home_url ) . '' ); ?>
*', '//www.aa.com', '//aa.com', '//*aa.com' ); ?>
title( $option_id ); ?>
title( $option_id ); ?> build_textarea( $option_id ); ?>
form_end(); ?>tpl/cdn/entry.tpl.php000064400000001740152075713350010571 0ustar00 esc_html__( 'QUIC.cloud', 'litespeed-cache' ), 'cf' => esc_html__( 'Cloudflare', 'litespeed-cache' ), 'other' => esc_html__( 'Other Static CDN', 'litespeed-cache' ), ); ?>


$menu_value ) { printf( '
', esc_attr( $menu_key ) ); require LSCWP_DIR . "tpl/cdn/$menu_key.tpl.php"; echo '
'; } ?>
tpl/cdn/cf.tpl.php000064400000015025152075713350010021 0ustar00form_action(); ?>

title( $option_id ); ?> build_switch( $option_id ); ?>
build_input( Base::O_CDN_CLOUDFLARE_KEY ); ?>
Cloudflare' ); ?>
build_input( Base::O_CDN_CLOUDFLARE_EMAIL ); ?>
conf( Base::O_CDN_CLOUDFLARE_ZONE ); $cls = $cf_zone ? ' litespeed-input-success' : ' litespeed-input-warning'; $this->build_input( Base::O_CDN_CLOUDFLARE_NAME, $cls ); ?>
title( $option_id ); ?> build_switch( $option_id ); ?>
form_end(); $cf_on = $this->conf( Base::O_CDN_CLOUDFLARE ); $cf_domain = $this->conf( Base::O_CDN_CLOUDFLARE_NAME ); $cf_zone = $this->conf( Base::O_CDN_CLOUDFLARE_ZONE ); if ( ! $cf_domain ) { $cf_domain = '-'; } if ( ! $cf_zone ) { $cf_zone = '-'; } $curr_status = CDN\Cloudflare::get_option( CDN\Cloudflare::ITEM_STATUS, array() ); ?>

:

:

: = $curr_status['devmode_expired'] ) { $expired_at = gmdate( 'm/d/Y H:i:s', $curr_status['devmode_expired'] + LITESPEED_TIME_OFFSET ); $curr_status['devmode'] = 'OFF'; printf( esc_html__( 'Current status is %1$s since %2$s.', 'litespeed-cache' ), '' . esc_html( strtoupper( $curr_status['devmode'] ) ) . '', '' . esc_html( $expired_at ) . '' ); } else { $expired_at = $curr_status['devmode_expired'] - time(); $expired_at = Utility::readable_time( $expired_at, 3600 * 3, true ); printf( esc_html__( 'Current status is %s.', 'litespeed-cache' ), '' . esc_html( strtoupper( $curr_status['devmode'] ) ) . '' ); printf( esc_html__( 'Development mode will be automatically turned off in %s.', 'litespeed-cache' ), '' . esc_html( $expired_at ) . '' ); } ?>

', '' ); ?>

:

tpl/cdn/qc.tpl.php000064400000021273152075713350010036 0ustar00finish_qc_activation( 'cdn' ); $cloud_summary = Cloud::get_summary(); ?>

activated() ) : ?>

activated() ) : ?>

QUIC.cloud Online Services and CDN.', 'litespeed-cache' ) ); ?>

fully disabled.', 'litespeed-cache' ) ); ?>

not available for anonymous (unlinked) users.', 'litespeed-cache' ) ); ?>

'cdn' ) ) ), '' . $btn_title, true, 'button button-primary litespeed-button-cta' ); ?>

', '' ); ?>

load_qc_status_for_dash( 'cdn_dash' ) ); ?>

<?php echo esc_attr( $cloud_summary['partner']['name'] ); ?> <?php echo esc_attr( $cloud_summary['partner']['name'] ); ?> activated() ) : ?>

activated() ) : ?>

load_qc_status_for_dash( 'promo_mini' ); ?>
guest.vary.php000064400000000261152075713350007373 0ustar00update_guest_vary(); readme.txt000064400000140726152075713350006564 0ustar00=== LiteSpeed Cache === Contributors: LiteSpeedTech Tags: caching, optimize, performance, pagespeed, seo, image optimize, object cache, redis, memcached, database cleaner Requires at least: 5.3 Requires PHP: 7.2 Tested up to: 6.9 Stable tag: 7.8.1 License: GPLv3 License URI: http://www.gnu.org/licenses/gpl.html All-in-one unbeatable acceleration & PageSpeed improvement: caching, image/CSS/JS optimization... == Description == LiteSpeed Cache for WordPress (LSCWP) is an all-in-one site acceleration plugin, featuring an exclusive server-level cache and a collection of optimization features. LSCWP supports WordPress Multisite and is compatible with most popular plugins, including WooCommerce, bbPress, and Yoast SEO. LiteSpeed Cache for WordPress is compatible with ClassicPress. == Requirements == **General Features** may be used by anyone with any web server (LiteSpeed, Apache, NGINX, etc.). **LiteSpeed Exclusive Features** require one of the following: OpenLiteSpeed, commercial LiteSpeed products, LiteSpeed-powered hosting, or QUIC.cloud CDN. [Why?](https://docs.litespeedtech.com/lscache/lscwp/faq/#why-do-the-cache-features-require-a-litespeed-server) == Plugin Features == = General Features = * Free QUIC.cloud CDN Cache * Object Cache (Memcached/LSMCD/Redis) Support+ * Image Optimization (Lossless/Lossy) * Minify CSS, JavaScript, and HTML * Minify inline & external CSS/JS * Combine CSS/JS * Automatically generate Critical CSS * Lazy-load images/iframes * Responsive Image Placeholders * Multiple CDN Support+ * Load CSS Asynchronously * Defer/delay JS loading * Browser Cache Support+ * Database Cleaner and Optimizer * PageSpeed score (including Core Web Vitals) optimization * OPcode Cache Support+ * HTTP/2 Push for CSS/JS (on web servers that support it) * DNS Prefetch * Cloudflare API * Single Site and Multisite (Network) support * Import/Export settings * Attractive, easy-to-understand interface * AVIF/WebP image format support * Heartbeat control + This service is not provided by the LSCache plugin, nor is it guaranteed to be installed by your service provider. However, the plugin is compatible with the service if it is in use on your site. = LiteSpeed Exclusive Features = * Automatic page caching to greatly improve site performance * Automatic purge of related pages based on certain events * Private cache for logged-in users * Caching of WordPress REST API calls * Separate caching of desktop and mobile views * Ability to schedule purge for specified URLs * WooCommerce and bbPress support * [WordPress CLI](https://docs.litespeedtech.com/lscache/lscwp/cli/) commands * API system for easy cache integration * Exclude from cache by URI, Category, Tag, Cookie, User Agent * Smart preload crawler with support for SEO-friendly sitemap * Multiple crawlers for cache varies * HTTP/2 support * [HTTP/3 & QUIC](https://www.litespeedtech.com/http3-faq) support * ESI (Edge Side Includes) support* * Widgets and Shortcodes as ESI blocks* (requires Classic Widgets plugin for WP 5.8+) * Feature not available in OpenLiteSpeed == Screenshots == 1. Plugin Benchmarks 2. Admin - Dashboard 3. Admin - Image Optimization 4. Admin - Crawler 5. Admin Settings - Cache 6. Admin Settings - Page Optimization 7. Admin Settings - CDN 8. Admin Settings - DB Optimizer 9. Admin Settings - Toolbox 10. Cache Miss Example 11. Cache Hit Example == LSCWP Resources == * [Join our Slack community](https://litespeedtech.com/slack) to connect with other LiteSpeed users. * [Ask a question on our support forum](https://wordpress.org/support/plugin/litespeed-cache/). * [View detailed documentation](https://docs.litespeedtech.com/lscache/lscwp/). * [Read about LSCWP and WordPress on our blog](https://blog.litespeedtech.com/tag/wordpress/). * [Help translate LSCWP](https://translate.wordpress.org/projects/wp-plugins/litespeed-cache/). * [Contribute to the LSCWP GitHub repo](https://github.com/litespeedtech/lscache_wp). == Installation == [View detailed documentation](https://docs.litespeedtech.com/lscache/lscwp/installation/). = For Optimization Without a LiteSpeed Web Server = 1. Install the LiteSpeed Cache for WordPress plugin and activate it. 1. From the WordPress Dashboard, navigate to **LiteSpeed Cache > Page Optimization**. Enable the available optimization features in the various tabs. = For Caching and Optimization With a LiteSpeed Web Server = 1. Install [LiteSpeed Web Server Enterprise](https://www.litespeedtech.com/products/litespeed-web-server) with LSCache Module, [LiteSpeed Web ADC](https://www.litespeedtech.com/products/litespeed-web-adc), or [OpenLiteSpeed](https://www.litespeedtech.com/open-source/openlitespeed) with cache module (Free). Or sign up for [QUIC.cloud CDN](https://quic.cloud). 1. Install the LiteSpeed Cache for WordPress plugin and activate it. 1. From the WordPress Dashboard, navigate to **LiteSpeed Cache > Cache**, make sure the option **Enable LiteSpeed Cache** is set to `ON`. 1. Enable any desired caching and optimization features in the various tabs. = Notes for LiteSpeed Web Server Enterprise = * Make sure that your license includes the LSCache module. A [2-CPU trial license with LSCache module](https://www.litespeedtech.com/products/litespeed-web-server/download/get-a-trial-license "trial license") is available for free for 15 days. * The server must be configured to have caching enabled. If you are the server admin, [click here](https://docs.litespeedtech.com/lscache/start/#configure-cache-root-and-cache-policy) for instructions. Otherwise, please request that the server admin configure the cache root for the server. = Notes for OpenLiteSpeed = * This integration utilizes OpenLiteSpeed's cache module. * If it is a fresh OLS installation, the easiest way to integrate is to use [ols1clk](https://openlitespeed.org/kb/1-click-install/). If using an existing WordPress installation, use the `--wordpresspath` parameter. * If OLS and WordPress are both already installed, please follow the instructions in [How To Set Up LSCache For WordPress](https://openlitespeed.org/kb/how-to-setup-lscache-for-wordpress/). == Third Party Compatibility == The vast majority of plugins and themes are compatible with LSCache. [Our API](https://docs.litespeedtech.com/lscache/lscwp/api/) is available for those that are not. Use the API to customize smart purging, customize cache rules, create cache varies, and make WP nonce cacheable, among other things. == Privacy == This plugin includes some suggested text that you can add to your site's Privacy Policy via the Guide in the WordPress Privacy settings. **For your own information:** LiteSpeed Cache for WordPress potentially stores a duplicate copy of every web page on display on your site. The pages are stored locally on the system where LiteSpeed server software is installed and are not transferred to or accessed by LiteSpeed employees in any way, except as necessary in providing routine technical support if you request it. All cache files are temporary, and may easily be purged before their natural expiration, if necessary, via a Purge All command. It is up to individual site administrators to come up with their own cache expiration rules. In addition to caching, our WordPress plugin has online features provided by QUIC.cloud for Image Optimization and Page Optimization services. When one of these optimizations is requested, data is transmitted to a remote QUIC.cloud server, processed, and then transmitted back for use on your site. QUIC.cloud keeps copies of that data for up to 7 days and then permanently deletes it. Similarly, the WordPress plugin has a Reporting feature whereby a site owner can transmit an environment report to LiteSpeed so that we may better provide technical support. None of these features collects any visitor data. Only server and site data are involved. QUIC.cloud CDN, if enabled, uses LSCache technology to access your site, and serve your content from remote global nodes. Your data is not accessed by QUIC.cloud employees in any way, except as necessary in providing maintenance or technical support. Please see the [QUIC.cloud Privacy Policy](https://quic.cloud/privacy-policy/) for our complete Privacy/GDPR statement. == Frequently Asked Questions == = Why do the cache features require LiteSpeed Server? = This plugin communicates with your LiteSpeed Web Server and its built-in page cache (LSCache) to deliver superior performance to your WordPress site. The plugin’s cache features indicate to the server that a page is cacheable and for how long, or they invalidate particular cached pages using tags. LSCache is a server-level cache, so it's faster than PHP-level caches. [Compare with other PHP-based caches](https://www.litespeedtech.com/benchmarks/wordpress). A page cache allows the server to bypass PHP and database queries altogether. LSCache, in particular, because of its close relationship with the server, can remember things about the cache entries that other plugins cannot, and it can analyze dependencies. It can utilize tags to manage the smart purging of the cache, and it can use vary cookies to serve multiple versions of cached content based on things like mobile vs. desktop, geographic location, and currencies. [See our Caching 101 blog series](https://blog.litespeedtech.com/tag/caching-101/). If all of that sounds complicated, no need to worry. LSCWP works right out of the box with default settings that are appropriate for most sites. [See the Beginner's Guide](https://docs.litespeedtech.com/lscache/lscwp/beginner/). **Don't have a LiteSpeed server?** Try our QUIC.cloud CDN service. It allows sites on *any server* (NGINX and Apache included) to experience the power of LiteSpeed caching! [Click here](https://quic.cloud) to learn more or to give QUIC.cloud a try. = What about the optimization features of LSCache? = LSCWP includes additional optimization features, such as Database Optimization, Minification and Combination of CSS and JS files, HTTP/2 Push, CDN Support, Browser Cache, Object Cache, Lazy Load for Images, and Image Optimization! These features do not require the use of a LiteSpeed web server. = Is the LiteSpeed Cache Plugin for WordPress free? = Yes, LSCWP will always be free and open source. That said, a LiteSpeed server is required for the cache features, and there are fees associated with some LiteSpeed server editions. Some of the premium online services provided through QUIC.cloud (CDN Service, Image Optimization, Critical CSS, Low-Quality Image Placeholder, etc.) require payment at certain usage levels. You can learn more about what these services cost, and what levels of service are free, on [your QUIC.cloud dashboard](https://my.quic.cloud). = What server software is required for this plugin? = A LiteSpeed solution is required in order to use the **LiteSpeed Exclusive** features of this plugin. Any one of the following will work: 1. LiteSpeed Web Server Enterprise with LSCache Module (v5.0.10+) 2. OpenLiteSpeed (v1.4.17+) 3. LiteSpeed WebADC (v2.0+) 4. QUIC.cloud CDN The **General Features** may be used with *any* web server. LiteSpeed is not required. = Does this plugin work in a clustered environment? = The cache entries are stored at the LiteSpeed server level. The simplest solution is to use LiteSpeed WebADC, as the cache entries will be stored at that level. If using another load balancer, the cache entries will only be stored at the backend nodes, not at the load balancer. The purges will also not be synchronized across the nodes, so this is not recommended. If a customized solution is required, please contact LiteSpeed Technologies at `info@litespeedtech.com` NOTICE: The rewrite rules created by this plugin must be copied to the Load Balancer. = Where are the cached files stored? = The actual cached pages are stored and managed by LiteSpeed Servers. Nothing is stored within the WordPress file structure. = Does LiteSpeed Cache for WordPress work with OpenLiteSpeed? = Yes it can work well with OpenLiteSpeed, although some features may not be supported. See **Plugin Features** above for details. Any setting changes that require modifying the `.htaccess` file will require a server restart. = Is WooCommerce supported? = In short, yes. However, for some WooCommerce themes, the cart may not be updated correctly. Please [visit our blog](https://blog.litespeedtech.com/2017/05/31/wpw-fixing-lscachewoocommerce-conflicts/) for a quick tutorial on how to detect this problem and fix it if necessary. = Are my images optimized? = Images are not optimized automatically unless you set **LiteSpeed Cache > Image Optimization > Image Optimization Settings > Auto Request Cron** to `ON`. You may also optimize your images manually. [Learn more](https://docs.litespeedtech.com/lscache/lscwp/imageopt/). = How do I make a WP nonce cacheable in my third-party plugin? = Our API includes a function that uses ESI to "punch a hole" in a cached page for a nonce. This allows the nonce to be cached separately, regardless of the TTL of the page it is on. Learn more in [the API documentation](https://docs.litespeedtech.com/lscache/lscwp/api/#esi). We also welcome contributions to our predefined list of known third party plugin nonces that users can optionally include via [the plugin's ESI settings](https://docs.litespeedtech.com/lscache/lscwp/cache/#esi-nonce). = How do I enable the crawler? = The crawler is disabled by default, and must be enabled by the server admin first. Once the crawler is enabled on the server side, navigate to **LiteSpeed Cache > Crawler > General Settings** and set **Crawler** to `ON`. For more detailed information about crawler setup, please see [the Crawler documentation](https://docs.litespeedtech.com/lscache/lscwp/crawler/). = What are the known compatible plugins and themes? = * [WPML](https://wpml.org/) * [DoLogin Security](https://wordpress.org/plugins/dologin/) * [bbPress](https://wordpress.org/plugins/bbpress/) * [WooCommerce](https://wordpress.org/plugins/woocommerce/) * [Contact Form 7](https://wordpress.org/plugins/contact-form-7/) * [All in One SEO](https://wordpress.org/plugins/all-in-one-seo-pack/) * [Google XML Sitemaps](https://wordpress.org/plugins/google-sitemap-generator/) * [Yoast SEO](https://wordpress.org/plugins/wordpress-seo/) * [Wordfence Security](https://wordpress.org/plugins/wordfence/) * [NextGen Gallery](https://wordpress.org/plugins/nextgen-gallery/) * [ShortPixel](https://shortpixel.com/h/af/CXNO4OI28044/) * Aelia CurrencySwitcher * [Fast Velocity Minify](https://wordpress.org/plugins/fast-velocity-minify/) - Thanks Raul Peixoto! * Autoptimize * [Better WP Minify](https://wordpress.org/plugins/bwp-minify/) * [WP Touch](https://wordpress.org/plugins/wptouch/) * [Theme My Login](https://wordpress.org/plugins/theme-my-login/) * [WPLister](https://www.wplab.com/plugins/wp-lister/) * [WP-PostRatings](https://wordpress.org/plugins/wp-postratings/) * [Avada 5.1 RC1+](https://avada.theme-fusion.com/) * [Elegant Themes Divi 3.0.67+](https://www.elegantthemes.com/gallery/divi/) * [Elegant Divi Builder](https://www.elegantthemes.com/plugins/divi-builder/) * [Caldera Forms](https://wordpress.org/plugins/caldera-forms/) 1.5.6.2+ * Login With Ajax * [Ninja Forms](https://wordpress.org/plugins/ninja-forms/) * [Post Types Order 1.9.3.6+](https://wordpress.org/plugins/post-types-order/) * [BoomBox — Viral Magazine WordPress Theme](https://themeforest.net/item/boombox-viral-buzz-wordpress-theme/16596434?ref=PX-lab) * FacetWP (LSWS 5.3.6+) * Beaver Builder * WpDiscuz * WP-Stateless * Elementor * WS Form * WP Statistics The vast majority of plugins and themes are compatible with LiteSpeed Cache. The most up-to-date compatibility information can be found [in our documentation](https://docs.litespeedtech.com/lscache/lscwp/thirdparty/) = How can I report security bugs? = You can report security bugs through the Patchstack Vulnerability Disclosure Program. The Patchstack team help validate, triage and handle any security vulnerabilities. [Report a security vulnerability.](https://patchstack.com/database/vdp/litespeed-cache) == Changelog == = 7.8.1 - Apr 1 2026 = * **CDN** Fixed Cloudflare API key type detection for the compatibility w/ the new key format. = 7.8.0.1 - Mar 17 2026 = * **Object Cache** Improved Object Cache resilience: auto disable when connection fails, network subsites fallback to database, and dropped TTL setting to respect never-expired transients. = 7.8 - Mar 3 2026 = * **Cloud** Changed Health service to run asynchronously. * **Object Cache** Dropped `Store Transients` option. Transients now always use Object Cache when available, preventing potential database bloat from expired transients not being cleared. (ravanh) * **Media** Added extension check for WebP/AVIF replacement efficiency - only processes jpg/jpeg/png/gif, skips svg/ico/bmp etc. * **Media** Added WebP/AVIF support for macOS Safari >= 16.4. (PR#948) * **Media** Fixed pie chart not displaying in media library. * **Image Optimize** Fixed infinite redirect loop issue for image optimization actions. (giangel84 PR#959) * **Image Optimize** Fixed image optimization data not being cleared when images are replaced using third-party plugins. * **Image Optimize** Fixed reset single image not deleting records from img_optm and img_optming database tables. * **Page Optimize** Fixed font optimization in certain themes. (rbabt PR#955) * **Page Optimize** Filtered HTML tags when saving CSS content. * **Object Cache** Fixed methods returning `null` instead of `false` on failure, matching WordPress Object Cache API convention.(jkolodziej) * **Conf** Improved network subsites config loading efficiency. (dassels43) * **Toolbox** Added download button for log files to download complete logs. * **3rd** Purge product cache when orders are cancelled in WooCommerce. (haralampiev12 PR#954) * **Misc** Added Apache rewrite rule support for security check. (PR#948) * **Misc** Split Cloud and Image Optimization classes into traits for better maintainability. = 7.7 - Dec 16 2025 = * **Task** Increased default cron interval from 1 minute to 15 minutes. * **Conf** Enabled `litespeed_conf_load_option_{$option}` to allow modifying configuration values. * **Conf** Removed deprecated `O_MEDIA_PRELOAD_FEATURED` option. * **GUI** Dark mode toggle now applies instantly without flicker on page reload. * **Page Optimize** Improved dummy CSS replacement logic. * **Page Optimize** Deferred Instant Click to avoid blocking initial page rendering. (pp01 PR#926) * **Cloud** Guest Mode IP/UA lists now sync automatically from the QUIC.cloud API. * **Cloud** Guest Mode IP now supports CIDR notation (e.g., 192.168.1.0/24, 2001:db8::/32). * **Cloud** Fixed a potential warning caused by variable type casting. (userb52 PR#925) * **Cloud** Added null-safe handling for the `allowed_redirect_hosts` filter to prevent unexpected errors. (PR#933 #6972377) * **Cloud** Fixed fatal error on PHP 8 when no timestamp is logged due to number casting. (Patryk Chowratowicz #938) * **Crawler** Removed redundant functions after reformatting. * **Database Optimize** Fixed DB Optimize redirection and transient cleanup issues. (PR#937) * **Vary** Fixed issue where users without a group could not log in. * **Avatar** Fixed queue warning when the table is missing and resolved HTML escaping warnings. (PR#928) * **3rd** Improved WPML image lazy-load detection. (#657426) * **3rd** Fixed Elementor Form submission caching issue. (PR#932) * **3rd** Fixed cache issue when WCML currency changes. (PR#929) = 7.6.2 - Oct 17 2025 = * 🐞**Cloud** Fixed the PHP 8+ typecast issue in QUIC.cloud signature verification which caused activation failures. * **Purge** Restored a delay purge hook while calling purge by CLI. (asafm7) * **REST** Dropped legacy code that had been used for development purposes. * **GUI** Use a stricter selector for dark mode to prevent side effects. = 7.6.1 - Oct 15 2025 = * **Cloud** Increased POST connection timeout to prevent potential failures. * ⚠️🐞**GUI** Fixed a frontend display issue caused by the dark mode CSS file loading on the website frontend. (Peter Wells PR#923) * 🐞**Page Optimize** Corrected a typo in the DNS prefetch filter. (Yaroslav Yachmenov PR#922) = 7.6 - Oct 15 2025 = * 🌱**Admin** Dark mode supported. * 🌱**Purge** Added `Purge All - VPI` to the Purge menu. (PR#898) * ⚠️🐞**Debug** Escaped comments to prevent a CSS vulnerability that could occur when debug is on. (#218778 Trustwave #CWE-79) * **Purge** Gravatar purge now also clears the database records. (Serafín Danessa, PR#915) * **Conf** Fixed an issue where the `Drop Query String` setting was not saved when in network mode. (Jory Hogeveen PR#910) * **VPI** Add fetchpriority and decode attributes to VPI. (Hirak Kalita, serpentdriver, PR#903) * **Cloud** Auto sync new Server IP to QUIC.cloud if changed. (cloud86) * **GUI** Auto update port value when `Object Cache Method` is changed. * **API** Dropped legacy `conf::val()` function. * 🐞**Misc** Fixed PHP 7.2 compatibility issue. (Ulrich Viridis, PR#913) * **Misc** Added UCSS file path to comment info for easier debug. (PR#914) = 7.5.0.1 - Sep 11 2025 = * 🐞**GUI** Fixed an issue where the network dashboard template was missing. (mcworks) = 7.5 - Sep 10 2025 = * 🌱**Image Optimize** New option `Optimize Image Sizes` to allow user to choose which image sizes to include in optimization request. * 🐞**Purge** Purge Time setting will respect WP timezone setting now. (PR#893) * 🐞**Conf** Fixed a minor type-casting bug, which could cause unnecessary QUIC.cloud sync configuration when the setting is empty. * **Misc** Dropped unused rewrite rule from htaccess. = 7.4 - Aug 28 2025 = * 🌱**Media** Added new Auto Rescale Original Image option. * 🌱**Toolbox** Added ability to Disable All for 24 Hours. (PR#886) * 🐞**CDN** Fixed a QUIC.cloud sync configuration failure on network child sites. * 🐞**Object Cache** Fixed a bug that failed to detect the Redis connection status. * **Cache** Better match iPhone browsers for mobile cache detection. * **Cache** Dropped use of `advanced-cache.php` support since WP v5.3+ doesn't need it, and LiteSpeed requires WP v5.3+. * **Cache** When page is not cacheable, set header to value used by WordPress `Cache-Control` header. (asafm7) * **Page Optimize** Better compatibility for dummy CSS removal in cases where other plugins manipulate the quotation marks. * **Page Optimize** Dropped v4.2 legacy `LITESPEED_BYPASS_OPTM`. * **Crawler** Now use an .html file to test the port, as some security plugins block .txt files and cause port test failure. (#661828) * **GUI** Show current live values for options if they are overridden by filters or the server environment. (PR#885) * **Data** Dropped legacy code and upgraded data migration support to LSCWP v5.7-. * **Misc** Support the `LITESPEED_DEV` constant to allow switching to a development environment. * **Misc** Allow leading underscore (`_`) for private functions and variables in format checker. * **Misc** Suppress frequent version check when a certain database option is cached. * **Misc** Dropped `sanitize_file_name` usage to prevent template failure when 3rd party plugins manipulate that filter. = 7.3.0.1 - Jul 30 2025 = * **Page Optimize** Fixed the page score impact caused by CSS placeholder. (wpconvert, Sean Thompson) * **Page Optimize** Fixed wrong prefetch/preload injection when a page contains other `` tags. (idatahuy) * **Crawler** Bypassed port test if no server IP set. (kptk, serkanix, Guillermo) = 7.3 - Jul 24 2025 = * 🌱**CLI** Added `wp litespeed-database` database optimization command. * 🌱**Misc** Added survey and data deletion reminder in deactivation process. * **Core** Refactored the template files to comply with WordPress standards. * **Core** Refactored the CLI files to comply with WordPress standards. Fixed a bug with CLI `option` command failure handler. * **ESI** Fixed a case where the Edit button is missing on the frontend when the permalink structure is `Plain`. (#934261 PR#860) * **API** Added `litespeed_purge_tags` filter to allow manipulation of purge tags. * **API** Allowed overriding `litespeed_ui_events` via window property. (Zsombor Franczia PR#865) * **API** Added `litespeed_vpi_should_queue` filter to allow control over appending to the VPI queue. (tompalmer #855, Viktor Szépe PR#856) * **Debug** Allowed debug at multisite network level. (PR#861) * **Vary** Fixed a possible duplicate WebP vary in Chrome when mimicking an iPhone visit. * 🐞**Vary** Used simpler rewrite rule to check for next generation image format support. * **Page Optimize** Tuned the optimized data injection location in HTML to improve SEO. (videofinanzas) * **Page Optimize** Improved DNS prefetch and preconnect sequence in HTML to be as early as possible. Simplified DNS optimization code. * 🐞**Page Optimize** Added the JS Delay library that was missing when page optimization was off while iframe lazy load was on. (Zsombor Franczia #867) * 🐞**Page Optimize** Allowed lazy load threshold overwrite. (Zsombor Franczia #852 PR#857) * 🐞**Page Optimize** Fixed an issue where the `async` attribute was replaced even when it contained a value, e.g. `async=true`. (@macorak) * 🐞**Cloud** Fixed the API call timestamp file creation warning. * **Cloud** No longer include public key when logging QUIC.cloud registration process. * **Image Optimize** Resend all images that failed to pull instead of bypassing them. (Ryan D) * **Crawler** Checked QUIC.cloud CDN for crawler hit. (PR#866) * 🐞**Crawler** Fixed an issue where the non-role-simulator crawler added the whole map to the blocklist on servers that only support port 80. * **GUI** Added Enable All Features icon to admin bar when all features are disabled. This replaces the banner that previously displayed in admin. (Tobolo, PR#868) * **GUI** Dropped font files. (Masoud Najjar Khodabakhsh) * **3rd** Resolved an issue with an empty WooCommerce ESI nonce and HTML comments on geolocation redirection. (#612331 PR#708) * **OPcache** Detected `opcache.restrict_api` setting to prevent PHP warning in purge. (ookris #9496550 PR#812) * **Misc** Simplified admin JavaScript. * **Misc** Fixed download import file extension issue on mobile. (autori76 #874) * **Misc** Added existing plugin version to ping API for debugging purposes. * **Misc** Fixed comment typos reported by static analysis. (Viktor Szépe PR#836) * **Misc** Removed global variables from plugin initialization file. (Viktor Szépe PR#837) = 7.2 - Jun 18 2025 = * 🌱**CDN** New option: Cloudflare Clear on purge all. (PR#828) * **Core** Used `site_url` instead of `home_url` to fix the content folder parsing and QUIC.cloud calls. * 🐞**Cloud** Fixed a bug where we tried to sync QUIC.cloud usage while debug mode was ON, even when QC was not activated. * **Cloud** Stored request timestamp in static files along w/ database to prevent duplicate requests when database is down. * **Cache** Dropped `Cache PHP Resources` option. * **Cache** Added verification to prevent admin pages from caching even if the site is set to be globally cacheable. * **Image Optimize** Disable image pull cron if there have been no image notifications. * **Crawler** Non-role simulator crawler will now use DNS resolve to hit original server instead of CDN nodes. * **Media** Resolved an issue where deleting an image from grid mode neglected to also remove the optimized versions of the image. (PR#844, Zsombor Franczia #841) * **Media** Allowed filter `litespeed_next_gen_format` to manipulate the value of next gen format. (Zsombor Franczia #853) * **3rd** Elementor: Clear all caches on regenerate CSS & Data. (PR#806) * **Config** `Purge All On Upgrade` now defaults to OFF. * **GUI** Showed `Disable all features` message on all WP-Admin pages for Admin-level users when enabled. * **Misc** Used PHPCS w/ WordPress core and security coding standards to reformat cache menu code. (Viktor Szépe #696) * **Misc** Replaced use of `SHOW TABLES` with `DESCRIBE` to prevent database halt in very large WP Multisite installations. (Boone Gorges PR#834, PR#850) * **Misc** Replaced constants with WordPress functions to check whether AJAX or CRON is running. * **API** Added action `litespeed_save_conf` to provide a trigger for configuration updates. = 7.1 - Apr 24 2025 = * 🌱**Page Optimize** Added allowlist support for CCSS. * **Cloud** CCSS results are now generated asynchronously via QUIC.cloud queue services. * **Cloud** Added TTL control to QUIC.cloud services to make next requests more flexible. * **Crawler** Dropped non-WebP/AVIF crawler if Next Gen Images are being used. * 🐞**Config** Fixed an .htaccess generation bug that occurred when reactivating after previous deactivation. (PR#825) * **GUI** Improved the QC registration notice banner for online services thanks to user feedback. * **GUI** QUIC.cloud management links will be opened in a single dedicated new window to prevent multiple sessions. * **Page Optimization** Enhanced URL fetch validation to avoid exposing possible local info. * **Debug** Added a Click to copy logs button under `Log View` tab. * **CLI** Removed a vary warning log in CLI for QC activation process with a customized login cookie. * **CLI** Removed a log failure in CLI in QC activation process when no existing admin message. * **Misc** Check version only after upgrade to reduce the requests. * **Misc** Switched to CyberPanel.sh to detect public IP for dash tool. = 7.0.1 - Apr 8 2025 = * **Page Optimize** Migrate legacy data to append trailing slash for better compatibility with v7.0-optimized UCSS/CCSS data. = 7.0.0.1 - Mar 27 2025 = * **GUI** Resolved a banner message display error in certain old version cases. * **GUI** Fixed a continual error banner when site doesn't use QC. * **Config** Fixed a continual CDN sync_conf/purge check issue after upgraded to v7.0. * **3rd** Improved WPML multi lang sync_conf compatibility. = 7.0 - Mar 25 2025 = * 🌱**Image Optimization** Added AVIF format. * **Core** Changed plugin classes auto load to preload all to prevent upgrade problems. * **Core** Refactored configuration data initialization method to realtime update instead of delayed update in plugin upgrade phase. * **Core** Used `const.default.json` instead of `const.default.ini` for better compatibility in case `parse_ini_file()` is disabled. * **Core** Minimum required PHP version escalated to PHP v7.2.0. * **Core** Minimum required WP version escalated to WP v5.3. * **Cloud** Dropped `Domain Key`. Now using sodium encryption for authentication and validation. * **Cloud** Added support for `list_preferred` in online service node detection. * **Cloud** Fixed a domain expiry removal PHP warning. (cheekymate06) * **Cloud** Auto dropped Cloud error message banner when successfully reconnected. * **Cloud** Simplified the configure sync parameters to only compare and post the necessary settings. * **Config** Simplified QUIC.cloud CDN Setup. CDN service is now automatically detected when activated in the QUIC.cloud Dashboard. * **Config** Dropped the initial version check when comparing md5 to decide if whether to sync the configuration when upgrading the plugin. * **Config** `LITESPEED_DISABLE_ALL` will now check the value to determine whether it's been applied. * **Database Optimize** Fixed Autoload summary for WP6.6+. (Mukesh Panchal/Viktor Szépe) * **CLI** Added QUIC.cloud CDN CLI command: `wp litespeed-online cdn_init --ssl-cert=xxx.pem --ssl-key=xxx -method=cname|ns|cfi`. * **CLI** Added QUIC.cloud CDN CLI command: `wp litespeed-online link --email=xxx@example.com --api-key=xxxx`. * **CLI** Added QUIC.cloud CDN CLI command: `wp litespeed-online cdn_status`. * **CLI** Added `--force` argument for QUIC.cloud CLI command `wp litespeed-online ping`. * **Image Optimization** Dropped `Auto Pull Cron` setting. Added PHP const `LITESPEED_IMG_OPTM_PULL_CRON` support. * **Image Optimization** Added Soft Reset Counter button to allow restarting image optimization without destroying previously optimized images. * **Image Optimization** Added support for `LITESPEED_IMG_OPTM_PULL_THREADS` to adjust the threads to avoid PHP max connection limits. * **Image Optimization** Added support for the latest firefox WebP Accept header change for serving WebP. * **Image Optimization** Allowed PHP Constant `LITESPEED_FORCE_WP_REMOTE_GET` to force using `wp_remote_get()` to pull images. * **Image Optimization** Dropped API filter `litespeed_img_optm_options_per_image`. * **Image Optimization** Auto redirect nodes if the server environment is switched between Preview and Production. * **Purge** Allowed `LSWCP_EMPTYCACHE` to be defined as false to disable the ability to Purge all sites. * **Purge** Each purge action now has a hook. * **Purge** Fixed `PURGESINGLE` and `PURGE` query string purge tag bug. * **Purge** `PURGE` will purge the single URL only like `PURGESINGLE`. * **ESI** Fixed a log logic failure when ESI buffer is empty. * **ESI** Added Elementor nonces (jujube0ajluxl PR#736) * **ESI** Fixed a no-cache issue in no-vary ESI requests that occurred when `Login Cookie` was set. * **ESI** ESI will no longer send cookie update headers. * **Vary** Vary name correction, which used to happen in the `after_setup_theme` hook, now happens later in the `init` hook. * **Crawler** Enhanced hash generation function for cryptographic security. * **Crawler** Added back `Role Simulator` w/ IP limited to `127.0.0.1` only. Use `LITESPEED_CRAWLER_LOCAL_PORT` to use 80 if original server does not support 443. * **Crawler** Enhanced Role Simulator security by disallowing editor or above access in settings. * **Crawler** Defaulted and limited crawler `Run Duration` maximum to 900 seconds and dropped the setting. * **Crawler** Crawler will be stopped when load limit setting is 0. * **Crawler** Dropped `Delay` setting. Added PHP const `LITESPEED_CRAWLER_USLEEP` support. * **Crawler** Dropped `Timeout` setting. Added PHP const `LITESPEED_CRAWLER_TIMEOUT` support. * **Crawler** Dropped `Threads` setting. Added PHP const `LITESPEED_CRAWLER_THREADS` support. * **Crawler** Dropped `Interval Between Runs` setting. Added PHP const `LITESPEED_CRAWLER_RUN_INTERVAL` support. * **Crawler** Dropped `Sitemap Timeout` setting. Added PHP const `LITESPEED_CRAWLER_MAP_TIMEOUT` support. * **Crawler** Dropped `Drop Domain from Sitemap` setting. Added PHP const `LITESPEED_CRAWLER_DROP_DOMAIN` support. * **Crawler** Fixed wrong path of .pid file under wp-admin folder in certain case. (igobybus) * **Crawler** Show an empty map error and disabled crawler when the map is not set yet. * **Page Optimize** Updated request link parser to follow the site permalink. (Mijnheer Eetpraat #766) * **Page Optimize** Updated latest CSS/JS optimization library to fix issues for RGB minification and external imports when combining CSS. * **Page Optimize** Exclude Google Analytics from JavaScript optimization. (James M. Joyce #269 PR#726) * **Page Optimize** Fixed typo in `LITESPEED_NO_OPTM` constant definition. (Roy Orbitson PR#796) * **CDN** Fixed CDN replacement for inline CSS url with round brackets case. (agodbu) * **GUI** Added an Online Service tab under General menu. * **GUI** Added a QUIC.cloud CDN tab. * **GUI** Combined all Crawler settings to a single setting tab. * **GUI** Switch buttons rtl compatibility. (Eliza/Mehrshad Darzi #603) * **GUI** Fixed an issue where an irremovable banner couldn't be echoed directly. * **GUI** Limited page speed chart to cacheable servers only. * **Tag** Fixed a potential warning in tags. (ikiterder) * **Tag** Appended AJAX action to cache tags. * **Tag** Dropped normal HTTP code. Only error codes (403/404/500) will be used for tags. * **Misc** Fixed fatal activation error on Network installation when no other plugins are active. (PR#808 #9496550) * **Misc** Improved README file by adding minimum supported PHP/WordPress versions. (Viktor Szépe) * **Misc** Added reliance on just-in-time translation loading. (Pascal Birchler #738) * **Misc** Will now check whether the filename is valid before saving a file to fix the possible Object Cache log issue. (Mahdi Akrami #761) * **Misc** Fixed PHP 7.2 compatibility in cloud message. (Viktor Szépe #771) * **Misc** Incompatibility warning banner for third party plugins is now dismissible. * **Misc** Generated robots.txt file under litespeed folder to discourage search engine indexing of static resource files. (djwilko12) * **Debug** Escalated debug initialization to as early as possible to allow more configuration information to be logged. * **3rd** Fixed warning in Buddy Press code integration. (Viktor Szépe/antipole PR#778) = 6.5.4 - Dec 16 2024 = * **Page Optimize** Fixed Google Fonts broken with the Async option. (HivePress #787) = 6.5.3 - Dec 4 2024 = * **Misc** Quote escaped in attributes when building HTML. (CVE-2024-51915) = 6.5.2 - Oct 17 2024 = * **Crawler** Removed barely used Role Simulator from Crawler, to prevent potential security issues. * **Misc** Removed `mt_srand` function in random hash generation to slightly improve the hash result. = 6.5.1 - Sep 25 2024 = * **Security** This release includes two security updates to enhance the post validation of the editor (CVE-2024-47373), and to secure the GUI queue display from malicious vary input (CVE-2024-47374). * **Media** Sanitized dimensions for the images when replacing with placeholders. (TaiYou) * **Page Optimize** Sanitized vary value in queue list. (TaiYou) * **Cloud** Silent API error when failing to retrieve news updates. = 6.5.0.2 - Sep 6 2024 = * **Debug** Compatibility improvement for WP installations w/o `AUTH_KEY` defined in `wp-config.php`. = 6.5.0.1 - Sep 4 2024 = * 🔥**Debug** Fixed a corner case fatal error when Object Cache is ON but failed to connect, and `wp-content/litespeed` directory is not writable, and debug option is ON. = 6.5 - Sep 4 2024 = *❗**Security** This release includes several debug log improvements for improved security, as listed below. Update strongly recommended. * **Debug** Moved debug log to litespeed individual folder `/wp-content/litespeed/debug/`. * **Debug** Disallowed visits to `/litespeed/debug/` folder log files in .htaccess. * **Debug** Dropped const `LSCWP_DEBUG_PATH` support. * **Debug** Renamed `debug.purge.log` to `purge.log`. * **Debug** Added dummy `index.php` for debug folder. * **Debug** Used random string for log filenames. * **Debug** Removed cookies-related info. (Thanks to Rafie) * **Debug** Dropped `Log Cookies` option. * **Report** Escaped report content to protect it from potential XSS attack. (Islam R alsaid #505746) * **ESI** Added nonce for Advanced Custom Fields + Advanced Forms. (David Lapointe Gilbert #439) * **Purge** Run ACTION_PURGE_EMPTYCACHE even if cache is disabled in network admin. (Philip #453) * **Page Optimize** Disable UCSS exclusion when UCSS is inactived. (#640) * **3rd** Fixed undefined warning in WooCommerce Widgets. (Lolosan #719) * **3rd** Correct the integration with User Switching. (John Blackbourn #725) * **3rd** Fixed Admin Bar Missing issue on DIVI + Elementor frontend. (thyran/robertstaddon PR#727) = 6.4.1 - Aug 19 2024 = * ❗**Security** This release patches a security issue that may affect previous LSCWP versions since v1.9. * 🐞**Page Optimize** Fixed HTML minification returning blank page issue. (#706) * 🐞**CDN** Fixed a bug when Cloudflare status option is empty. (#684 #992174) * **Core** Minimum required WP version escalated to WP v4.9. = 6.4 - Aug 13 2024 = * **Cache** Corrected QC and LSADC cache hit status. * **Cloud** Allow partner info removal in QUIC.cloud notification. * **Crawler** Separated CSS preparation validation from crawler validation. * **GUI** Moved `WordPress Image Quality Control` setting from `Image Optimization` menu to `Page Optimization` menu. * **3rd** Add Elementor Edit button back in ESI. (PR#635) * **3rd** Fixed Instant click potential conflict w/ other plugins. = 6.3.0.1 - Jul 29 2024 = * 🔥🐞**Rest** Disabled WP default Editor cache for REST requests to fix editor errors. (Shivam) * **Cache** Supported `cache_nocacheable.txt` predefined settings. = 6.3 - Jul 22 2024 = * 🌱**Page Optimize** HTML Keep Comments: When minifying HTML do not discard comments that match a specified pattern. (#328853) * 🌱**Cache** Cache POST requests. Now can configure POST/GET AJAX requests to be cached. (#647300) * **Cache** Bypass admin initialization when doing ajax call. (Tim) * **Cache** Better control over the cache location #541 (Gal Baras/Tanvir Israq) * **Cloud** Added nonce for callback validation to enhance security. (Chloe@Wordfence) * **Cloud** Fixed an error message for daily quota. * **Cloud** Display error message when communicating with QUIC.cloud causes a token error. * **ESI** Bypass ESI at an earlier stage when getting `DONOTCACHEPAGE`. * **ESI** Added ESI nonce for Events Calendar and jetMenu mobile hamburger menu. (#306983 #163710 PR#419) * **ESI** Added WP Data Access nonce (PR#665) * **ESI** Added WP User Frontend ESI nonce (PR#675) * **Media** Ignored images from JS in image size detection (PR#660) * **GUI** Moved Preset menu from network level to site level for multisite networks. * **GUI** Suppressed sitemap generation message if not triggered manually. * **GUI** Added CloudFlare purge to front end menu. * **GUI** Allowed customized partner CDN login link on dash. * **Page Optimize** Cleaned up litespeed_url table when clearing url files. (PR#664) * **Page Optimize** Updated Instant Click library to version 5.2.0. * **Page Optimize** Added Flatsome theme random string excludes. (PR#415) * **Page Optimize** Exclude Cloudflare turnstile from JS optimizations. (Tobolo) * **Page Optimize** Fixed Cloudflare Turnstile issues. (Contributolo PR#671/672) * **Object** Improved debug log for object cache status. (PR#669) * **Object** Added brief parseable header comments to the drop-in file. (OllieJones) * **Debug** Trimmed debug log. * **Misc** Improved compatibility and sped up resolving for JSON functions `json_encode/json_decode`. (hosni/szepeviktor #693) * **Misc** Fixed typos in params and comments. (szepeviktor #688) * **Image Optimization** Fixed an issue which suppressed new requests when there were no new images in the library but there were unprocessed images in the send queue. * **Image Optimization** Improved Cloud side quota check by disallowing new requests if notified but not pulled. * **Image Optimization** Keep image attributes when replacing dimensions. (PR#686 #381779) = 6.2.0.1 - Apr 25 2024 = * 🔥🐞**Page Optimize** Fixed the image display issue that occurs with Elementor's `data-settings` attribute when the WebP image is not yet ready. (kanten/cbwwebmaster/reedock #132840 #680939 #326525) = 6.2 - Apr 23 2024 = * 🌱**Crawler** Added Crawler hit/miss filter. (#328853) * 🌱**CLI** Image optimization now supports `wp litespeed-image batch_switch orig/optm`. (A2Hosting) * 🌱**VPI** Auto preload VPI images. (Ankit) * **Object** Added support for username/password authentication for Redis (PR#616 Donatas Abraitis/hostinger) * **Page Optimize** Now supporting Elementors data-settings WebP replacement. (Thanks to Ryan D) * **Cache** Send `Cache-Control: no-cache, no-store, must-revalidate, max-age=0` when page is not cacheable. (asafm7/Ruikai) * **Cache** Cache control will respect `X-Http-Method-Override` now. (George) * **Cache** No cache for `X-Http-Method-Override: HEAD`. (George) * **Cache** Specified LSCWP in adv-cache compatible file. * **Cache** Fixed redirection loop if query string has tailing ampersand (#389629) * **Cache** Dropped "Cache Favicon.ico" option as it is redundant with 404 cache. (Lauren) * **Cache** Fixed deprecated PHP v8 warning in page redirection. (Issue#617 dcx15) * **Cloud** REST callback used ACL for QC ips validation. * **Cloud** Fixed a typo in parsing cloud msg which prevented error messages to show. * **Cloud** Carried on PHP ver for better version detection purpose. * **Cloud** Escaped token to show correctly in report. * **Cloud** Fixed a QC cloud ip verification setup failure in PHP 5.3. * 🐞**Cloud** Fixed a continual new version detection. * 🐞**Image Optimize** Fixed a summary counter mismatch for finished images. (A2Hosting) * **CDN** Auto CDN setup compatibility with WP versions less than 5.3. * 🐞**CDN** Fixed wrong replacement of non image files in image replacement. (Lucas) * **GUI** Further filtered admin banner messages to prevent from existing danger code in database. * **REST** Fixed a potential PHP warning in REST check when param is empty. (metikar) = 6.1 - Feb 1 2024 = * 🌱**Database** New Clear Orphaned Post Meta optimizer function. * **Image Optimize** Fixed possible PHP warning for WP requests library response. * **Image Optimize** Unlocked `noabort` to all async tasks to avoid image optimization timeout. (Peter Wells) * **Image Optimize** Fixed an issue where images weren't being pulled with older versions of WordPress. (PR#608) * **Image Optimize** Improved exception handling when node server cert expire. * 🐞**Image Optimize** The failed to pull images due to 404 expiry will now be able to send the request again. * **Crawler** CLI will now be able to force crawling even if a crawl was recently initiated within the plugin GUI. * **Page Optimize** Fixed a dynamic property creation warning in PHP8. (PR#606) * **Page Optimize** Fixed an issue where getimagesize could cause page optimization to fail. (PR#607) * **Tag** Fixed an array to string conversion warning. (PR#604) * **Object Cache** Return false to prevent PHP warning when Redis fails to set a value. (PR#612) * **Cache Tag** Fixed an issue where $wp_query is null when getting cache tags. (PR#589) = 6.0.0.1 - Dec 15 2023 = * 🐞**Image Optimize** Grouped the taken notification to regional center servers to reduce the load after image pulled. = 6.0 - Dec 12 2023 = * 🌱**Image Optimize** Parallel pull. (⭐ Contributed by Peter Wells #581) * 🌱**Cache** CLI Crawler. * 🌱**Cache** New Vary Cookies option. * 🌱**Media** New Preload Featured Image option. (Ankit) * **Core** Codebase safety review. (Special thanks to Rafie Muhammad @ Patchstack) * **Purge** Purge will not show QC message if no queue is cleared. * **Purge** Fixed a potential warning when post type is not as expected. (victorzink) * **Conf** Server IP field may now be emptied. (#111647) * **Conf** CloudFlare CDN setting vulnerability patch. (Gulshan Kumar #541805) * **Crawler** Suppressed sitemap generation msg when running by cron. * **Crawler** PHP v8.2 Dynamic property creation warning fix. (oldrup #586) * **VPI** VPI can now support non-alphabet filenames. * **VPI** Fixed PHP8.2 deprecated warning. (Ryan D) * **ESI** Fixed ESI nonce showing only HTML comment issue. (Giorgos K.) * 🐞**Page Optimize** Fixed a fatal PHP error caused by the WHM plugin's Mass Enable for services not in use. (Michael) * 🐞**Network** Fix in-memory options for multisites. (Tynan #588) * **Network** Correct `Disable All Features` link for Multisite. * 🐞**Image Optimize** Removing original image will also remove optimized images. * **Image Optimize** Increased time limit for pull process. * **Image Optimize** Last pull time and cron tag now included in optimization summary. * **Image Optimize** Fixed Elementors Slideshow unusual background images. (Ryan D) * 🐞**Database Optimize** Fix an issue where cleaning post revisions would fail while cleaning postmeta. (Tynan #596) * **Crawler** Added status updates to CLI. (Lars) * **3rd** WPML product category purge for WooCommerce. (Tynan #577) = 5.7.0.1 - Oct 25 2023 = * **GUI** Improvements to admin banner messaging. (#694622) * **CDN** Improvements to CDN Setup. (#694622) * **Image Optimize** Improvements to the process of checking image identification. (#694622) = 5.7 - Oct 10 2023 = * 🌱**Page Optimize** New option available: Preconnect. (xguiboy/Mukesh Patel) * 🌱**3rd** New Vary for Mini Cart option for WooCommerce. (Ruikai) * **Cloud** Force syncing the configuration to QUIC.cloud if CDN is reenabled. * **Cloud** Force syncing the configuration to QUIC.cloud if domain key is readded. * **Cloud** Limit multi-line fields when posting to QC. * **Cache** Treat HEAD requests as cacheable as GET. (George Wang) * 🐞**ESI** Patched a possible vulnerability issue. (István Márton@Wordfence #841011) * 🐞**ESI** Overwrite SCRIPT_URI to prevent ESI sub request resulting in redirections. (Tobolo) * 🐞**Image Optimize** Bypass unnecessary image processing when images were only partially optimized. (Ruikai) * 🐞**Guest** Guest mode will not enable WebP directly anymore. (Michael Heymann) * **CDN** Auto disable CDN if CDN URL is invalid. (Ruikai) * **CDN** Fixed a null parameter warning for PHP v8.1 (#584) * **API** Added `litespeed_media_add_missing_sizes` filter to allow bypassing Media's "add missing sizes" option (for Guest Optimization and otherwise). (PR #564) * **Guest** Fixed soft 404 and robots.txt report for guest.vary.php. * **Vary** Enabled `litespeed_vary_cookies` for LSWS Enterprise. * **GUI** Stopped WebP tip from wrongly displaying when Guest Mode is off. * **GUI** Added QUIC.cloud promotion postbox on dashboard page. * **3rd** Added `pagespeed ninja` to blocklist due to its bad behavior. ������������������������������������������lib/guest.cls.php�����������������������������������������������������������������������������������0000644�����������������00000020276�15207571335�0007751 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php /** * Guest vary handler for LiteSpeed Cache. * * NOTE: This file is loaded directly without WordPress, so WP functions are NOT available. * * @package LiteSpeed * @since 4.1 */ namespace LiteSpeed\Lib; /** * Update guest vary * * @since 4.1 */ class Guest { const CONF_FILE = '.litespeed_conf.dat'; const HASH = 'hash'; // Not set-able const O_CACHE_LOGIN_COOKIE = 'cache-login_cookie'; const O_DEBUG = 'debug'; const O_DEBUG_IPS = 'debug-ips'; const O_UTIL_NO_HTTPS_VARY = 'util-no_https_vary'; /** * Client IP address. * * @var string */ private static $_ip; /** * Vary cookie name. * * @var string */ private static $_vary_name = '_lscache_vary'; /** * Configuration array. * * @var array|false */ private $_conf = false; /** * Guest Mode lists cache. * * @var array */ private $_gm_lists = [ 'ips' => null, 'uas' => null, ]; /** * Constructor * * @since 4.1 */ public function __construct() { ! defined( 'LSCWP_CONTENT_FOLDER' ) && define( 'LSCWP_CONTENT_FOLDER', dirname( __DIR__, 3 ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- No WP available $this->_conf = file_get_contents( LSCWP_CONTENT_FOLDER . '/' . self::CONF_FILE ); if ( $this->_conf ) { $this->_conf = json_decode( $this->_conf, true ); } if ( ! empty( $this->_conf[ self::O_CACHE_LOGIN_COOKIE ] ) ) { self::$_vary_name = $this->_conf[ self::O_CACHE_LOGIN_COOKIE ]; } } /** * Update Guest vary. * * @since 4.0 * @return void */ public function update_guest_vary() { // This process must not be cached // @reference https://wordpress.org/support/topic/soft-404-from-google-search-on-litespeed-cache-guest-vary-php/#post-16838583 header( 'X-Robots-Tag: noindex' ); header( 'X-LiteSpeed-Cache-Control: no-cache' ); header( 'Cache-Control: no-store, no-cache, must-revalidate, max-age=0' ); header( 'Pragma: no-cache' ); if ( $this->always_guest() ) { echo '[]'; exit; } // If contains vary already, don't reload to avoid infinite loop when parent page having browser cache if ( $this->_conf && self::has_vary() ) { echo '[]'; exit; } // Send vary cookie $vary = 'guest_mode:1'; if ( $this->_conf && empty( $this->_conf[ self::O_DEBUG ] ) ) { $vary = md5( $this->_conf[ self::HASH ] . $vary ); } $expire = time() + 2 * 86400; $is_ssl = ! empty( $this->_conf[ self::O_UTIL_NO_HTTPS_VARY ] ) ? false : $this->is_ssl(); setcookie( self::$_vary_name, $vary, $expire, '/', false, $is_ssl, true ); // phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode -- No WP available echo json_encode( [ 'reload' => 'yes' ] ); exit; } /** * WP's is_ssl() func * * @since 4.1 * @return bool */ private function is_ssl() { // phpcs:disable WordPress.Security.ValidatedSanitizedInput -- No WP available if ( isset( $_SERVER['HTTPS'] ) ) { if ( 'on' === strtolower( $_SERVER['HTTPS'] ) ) { return true; } if ( '1' === $_SERVER['HTTPS'] ) { return true; } } elseif ( isset( $_SERVER['SERVER_PORT'] ) && '443' === $_SERVER['SERVER_PORT'] ) { return true; } // phpcs:enable WordPress.Security.ValidatedSanitizedInput return false; } /** * Check if default vary has a value * * @since 1.1.3 * @access public * @return string|false */ public static function has_vary() { if ( empty( $_COOKIE[ self::$_vary_name ] ) ) { return false; } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- No WP available return $_COOKIE[ self::$_vary_name ]; } /** * Load Guest Mode list from file. * * Priority: cloud synced file > plugin data file * * @since 7.7 * @param string $type 'ips' or 'uas'. * @return array */ private function _load_gm_list( $type ) { if ( null !== $this->_gm_lists[ $type ] ) { return $this->_gm_lists[ $type ]; } $this->_gm_lists[ $type ] = []; $filename = 'gm_' . $type . '.txt'; // Try cloud synced file first, then fallback to plugin data file $files = [ LSCWP_CONTENT_FOLDER . '/litespeed/cloud/' . $filename, dirname( __DIR__ ) . '/data/' . $filename, ]; foreach ( $files as $file ) { if ( file_exists( $file ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- No WP available $content = file_get_contents( $file ); if ( $content ) { $this->_gm_lists[ $type ] = array_filter( array_map( 'trim', explode( "\n", $content ) ) ); break; } } } return $this->_gm_lists[ $type ]; } /** * Detect if is a guest visitor or not * * @since 4.0 * @return bool */ public function always_guest() { if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) { return false; } $guest_uas = $this->_load_gm_list( 'uas' ); if ( $guest_uas ) { $quoted_uas = []; foreach ( $guest_uas as $v ) { $quoted_uas[] = preg_quote( $v, '#' ); } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- No WP available $match = preg_match( '#' . implode( '|', $quoted_uas ) . '#i', $_SERVER['HTTP_USER_AGENT'] ); if ( $match ) { return true; } } $guest_ips = $this->_load_gm_list( 'ips' ); if ( $this->ip_access( $guest_ips ) ) { return true; } return false; } /** * Check if the ip is in the range (supports CIDR notation) * * @since 1.1.0 * @since 7.7 Added CIDR support * @access public * @param array $ip_list List of IPs or CIDRs. * @return bool */ public function ip_access( $ip_list ) { if ( ! $ip_list ) { return false; } if ( ! isset( self::$_ip ) ) { self::$_ip = self::get_ip(); } foreach ( $ip_list as $ip_entry ) { $ip_entry = trim( $ip_entry ); // Check CIDR format if ( strpos( $ip_entry, '/' ) !== false ) { if ( $this->_ip_in_cidr( self::$_ip, $ip_entry ) ) { return true; } } elseif ( self::$_ip === $ip_entry ) { // Exact match return true; } } return false; } /** * Check if IP is within CIDR range * * @since 7.7 * @access private * @param string $ip IP address to check. * @param string $cidr CIDR notation (e.g., 192.168.1.0/24). * @return bool */ private function _ip_in_cidr( $ip, $cidr ) { list( $subnet, $mask ) = explode( '/', $cidr, 2 ); // Mask must be numeric and > 0 if ( ! is_numeric( $mask ) || $mask <= 0 ) { return false; } $mask = (int) $mask; // Determine IP version and validate $is_ipv6 = filter_var( $subnet, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ); $max_mask = $is_ipv6 ? 128 : 32; $byte_len = $is_ipv6 ? 16 : 4; $ip_filter = $is_ipv6 ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4; if ( ! filter_var( $ip, FILTER_VALIDATE_IP, $ip_filter ) ) { return false; } if ( $mask > $max_mask ) { return false; } $ip_bin = inet_pton( $ip ); $subnet_bin = inet_pton( $subnet ); if ( false === $ip_bin || false === $subnet_bin ) { return false; } // Build mask $full_bytes = (int) ( $mask / 8 ); $rem_bits = $mask % 8; $mask_bin = str_repeat( "\xff", $full_bytes ); if ( $rem_bits > 0 ) { $mask_bin .= chr( 0xff << ( 8 - $rem_bits ) ); } $mask_bin = str_pad( $mask_bin, $byte_len, "\x00" ); return ( $ip_bin & $mask_bin ) === ( $subnet_bin & $mask_bin ); } /** * Get client ip * * @since 1.1.0 * @since 1.6.5 changed to public * @access public * @return string */ public static function get_ip() { $_ip = ''; if ( function_exists( 'apache_request_headers' ) ) { $apache_headers = apache_request_headers(); $_ip = ! empty( $apache_headers['True-Client-IP'] ) ? $apache_headers['True-Client-IP'] : false; if ( ! $_ip ) { $_ip = ! empty( $apache_headers['X-Forwarded-For'] ) ? $apache_headers['X-Forwarded-For'] : false; $_ip = explode( ',', $_ip ); $_ip = $_ip[0]; } } if ( ! $_ip ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- No WP available $_ip = ! empty( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : ''; } return $_ip; } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������lib/css_js_min/pathconverter/converter.cls.php������������������������������������������������������0000644�����������������00000012600�15207571335�0015634 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // phpcs:ignoreFile /** * modified PHP implementation of Matthias Mullie's convert path class * Convert paths relative from 1 file to another. * * E.g. * ../../images/icon.jpg relative to /css/imports/icons.css * becomes * ../images/icon.jpg relative to /css/minified.css * * @author Matthias Mullie <pathconverter@mullie.eu> * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved * @license MIT License */ namespace LiteSpeed\Lib\CSS_JS_MIN\PathConverter; defined( 'WPINC' ) || exit; interface ConverterInterface { /** * Convert file paths. * * @param string $path The path to be converted * * @return string The new path */ public function convert( $path ); } class Converter implements ConverterInterface { /** * @var string */ protected $from; /** * @var string */ protected $to; /** * @param string $from The original base path (directory, not file!) * @param string $to The new base path (directory, not file!) * @param string $root Root directory (defaults to `getcwd`) */ public function __construct( $from, $to, $root = '' ) { $shared = $this->shared( $from, $to ); if ( $shared === '' ) { // when both paths have nothing in common, one of them is probably // absolute while the other is relative $root = $root ?: getcwd(); $from = strpos( $from, $root ) === 0 ? $from : preg_replace( '/\/+/', '/', $root . '/' . $from ); $to = strpos( $to, $root ) === 0 ? $to : preg_replace( '/\/+/', '/', $root . '/' . $to ); // or traveling the tree via `..` // attempt to resolve path, or assume it's fine if it doesn't exist $from = @realpath( $from ) ?: $from; $to = @realpath( $to ) ?: $to; } $from = $this->dirname( $from ); $to = $this->dirname( $to ); $from = $this->normalize( $from ); $to = $this->normalize( $to ); $this->from = $from; $this->to = $to; } /** * Normalize path. * * @param string $path * * @return string */ protected function normalize( $path ) { // deal with different operating systems' directory structure $path = rtrim( str_replace( DIRECTORY_SEPARATOR, '/', $path ), '/' ); // remove leading current directory. if ( substr( $path, 0, 2 ) === './' ) { $path = substr( $path, 2 ); } // remove references to current directory in the path. $path = str_replace( '/./', '/', $path ); /* * Example: * /home/forkcms/frontend/cache/compiled_templates/../../core/layout/css/../images/img.gif * to * /home/forkcms/frontend/core/layout/images/img.gif */ do { $path = preg_replace( '/[^\/]+(?<!\.\.)\/\.\.\//', '', $path, -1, $count ); } while ( $count ); return $path; } /** * Figure out the shared path of 2 locations. * * Example: * /home/forkcms/frontend/core/layout/images/img.gif * and * /home/forkcms/frontend/cache/minified_css * share * /home/forkcms/frontend * * @param string $path1 * @param string $path2 * * @return string */ protected function shared( $path1, $path2 ) { // $path could theoretically be empty (e.g. no path is given), in which // case it shouldn't expand to array(''), which would compare to one's // root / $path1 = $path1 ? explode( '/', $path1 ) : array(); $path2 = $path2 ? explode( '/', $path2 ) : array(); $shared = array(); // compare paths & strip identical ancestors foreach ( $path1 as $i => $chunk ) { if ( isset( $path2[ $i ] ) && $path1[ $i ] == $path2[ $i ] ) { $shared[] = $chunk; } else { break; } } return implode( '/', $shared ); } /** * Convert paths relative from 1 file to another. * * E.g. * ../images/img.gif relative to /home/forkcms/frontend/core/layout/css * should become: * ../../core/layout/images/img.gif relative to * /home/forkcms/frontend/cache/minified_css * * @param string $path The relative path that needs to be converted * * @return string The new relative path */ public function convert( $path ) { // quit early if conversion makes no sense if ( $this->from === $this->to ) { return $path; } $path = $this->normalize( $path ); // if we're not dealing with a relative path, just return absolute if ( strpos( $path, '/' ) === 0 ) { return $path; } // normalize paths $path = $this->normalize( $this->from . '/' . $path ); // strip shared ancestor paths $shared = $this->shared( $path, $this->to ); $path = mb_substr( $path, mb_strlen( $shared ) ); $to = mb_substr( $this->to, mb_strlen( $shared ) ); // add .. for every directory that needs to be traversed to new path $to = str_repeat( '../', count( array_filter( explode( '/', $to ) ) ) ); return $to . ltrim( $path, '/' ); } /** * Attempt to get the directory name from a path. * * @param string $path * * @return string */ protected function dirname( $path ) { if ( @is_file( $path ) ) { return dirname( $path ); } if ( @is_dir( $path ) ) { return rtrim( $path, '/' ); } // no known file/dir, start making assumptions // ends in / = dir if ( mb_substr( $path, -1 ) === '/' ) { return rtrim( $path, '/' ); } // has a dot in the name, likely a file if ( preg_match( '/.*\..*$/', basename( $path ) ) !== 0 ) { return dirname( $path ); } // you're on your own here! return $path; } } class NoConverter implements ConverterInterface { /** * {@inheritdoc} */ public function convert( $path ) { return $path; } } ��������������������������������������������������������������������������������������������������������������������������������lib/css_js_min/pathconverter/LICENSE����������������������������������������������������������������0000644�����������������00000002043�15207571335�0013341 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Copyright (c) 2015 Matthias Mullie Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������lib/css_js_min/minify/LICENSE�����������������������������������������������������������������������0000644�����������������00000002043�15207571335�0011750 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Copyright (c) 2012 Matthias Mullie Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������lib/css_js_min/minify/css.cls.php�������������������������������������������������������������������0000644�����������������00000065145�15207571335�0013040 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // phpcs:ignoreFile /** * css.cls.php - modified PHP implementation of Matthias Mullie's CSS minifier * * @author Matthias Mullie <minify@mullie.eu> * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved * @license MIT License */ namespace LiteSpeed\Lib\CSS_JS_MIN\Minify; use LiteSpeed\Lib\CSS_JS_MIN\Minify\Minify; use LiteSpeed\Lib\CSS_JS_MIN\Minify\Exception\FileImportException; use LiteSpeed\Lib\CSS_JS_MIN\PathConverter\Converter; use LiteSpeed\Lib\CSS_JS_MIN\PathConverter\ConverterInterface; defined( 'WPINC' ) || exit; class CSS extends Minify { /** * @var int maximum import size in kB */ protected $maxImportSize = 5; /** * @var string[] valid import extensions */ protected $importExtensions = array( 'gif' => 'data:image/gif', 'png' => 'data:image/png', 'jpe' => 'data:image/jpeg', 'jpg' => 'data:image/jpeg', 'jpeg' => 'data:image/jpeg', 'svg' => 'data:image/svg+xml', 'woff' => 'data:application/x-font-woff', 'woff2' => 'data:application/x-font-woff2', 'avif' => 'data:image/avif', 'apng' => 'data:image/apng', 'webp' => 'data:image/webp', 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'xbm' => 'image/x-xbitmap', ); /** * Set the maximum size if files to be imported. * * Files larger than this size (in kB) will not be imported into the CSS. * Importing files into the CSS as data-uri will save you some connections, * but we should only import relatively small decorative images so that our * CSS file doesn't get too bulky. * * @param int $size Size in kB */ public function setMaxImportSize( $size ) { $this->maxImportSize = $size; } /** * Set the type of extensions to be imported into the CSS (to save network * connections). * Keys of the array should be the file extensions & respective values * should be the data type. * * @param string[] $extensions Array of file extensions */ public function setImportExtensions( array $extensions ) { $this->importExtensions = $extensions; } /** * Move any import statements to the top. * * @param string $content Nearly finished CSS content * * @return string */ public function moveImportsToTop( $content ) { if ( preg_match_all( '/(;?)(@import (?<url>url\()?(?P<quotes>["\']?).+?(?P=quotes)(?(url)\)));?/', $content, $matches ) ) { // remove from content foreach ( $matches[0] as $import ) { $content = str_replace( $import, '', $content ); } // add to top $content = implode( ';', $matches[2] ) . ';' . trim( $content, ';' ); } return $content; } /** * Combine CSS from import statements. * * \@import's will be loaded and their content merged into the original file, * to save HTTP requests. * * @param string $source The file to combine imports for * @param string $content The CSS content to combine imports for * @param string[] $parents Parent paths, for circular reference checks * * @return string * * @throws FileImportException */ protected function combineImports( $source, $content, $parents ) { $importRegexes = array( // @import url(xxx) '/ # import statement @import # whitespace \s+ # open url() url\( # (optional) open path enclosure (?P<quotes>["\']?) # fetch path (?P<path>.+?) # (optional) close path enclosure (?P=quotes) # close url() \) # (optional) trailing whitespace \s* # (optional) media statement(s) (?P<media>[^;]*) # (optional) trailing whitespace \s* # (optional) closing semi-colon ;? /ix', // @import 'xxx' '/ # import statement @import # whitespace \s+ # open path enclosure (?P<quotes>["\']) # fetch path (?P<path>.+?) # close path enclosure (?P=quotes) # (optional) trailing whitespace \s* # (optional) media statement(s) (?P<media>[^;]*) # (optional) trailing whitespace \s* # (optional) closing semi-colon ;? /ix', ); // find all relative imports in css $matches = array(); foreach ( $importRegexes as $importRegex ) { if ( preg_match_all( $importRegex, $content, $regexMatches, PREG_SET_ORDER ) ) { $matches = array_merge( $matches, $regexMatches ); } } $search = array(); $replace = array(); // loop the matches foreach ( $matches as $match ) { // get the path for the file that will be imported $importPath = dirname( $source ) . '/' . $match['path']; // only replace the import with the content if we can grab the // content of the file if ( ! $this->canImportByPath( $match['path'] ) || ! $this->canImportFile( $importPath ) ) { continue; } // check if current file was not imported previously in the same // import chain. if ( in_array( $importPath, $parents ) ) { throw new FileImportException( 'Failed to import file "' . $importPath . '": circular reference detected.' ); } // grab referenced file & minify it (which may include importing // yet other @import statements recursively) $minifier = new self( $importPath ); $minifier->setMaxImportSize( $this->maxImportSize ); $minifier->setImportExtensions( $this->importExtensions ); $importContent = $minifier->execute( $source, $parents ); // check if this is only valid for certain media if ( ! empty( $match['media'] ) ) { $importContent = '@media ' . $match['media'] . '{' . $importContent . '}'; } // add to replacement array $search[] = $match[0]; $replace[] = $importContent; } // replace the import statements return str_replace( $search, $replace, $content ); } /** * Import files into the CSS, base64 encoded. * * Included images @url(image.jpg) will be loaded and their content merged into the * original file, to save HTTP requests. * * @param string $source The file to import files for * @param string $content The CSS content to import files for * * @return string */ protected function importFiles( $source, $content ) { $regex = '/url\((["\']?)(.+?)\\1\)/i'; if ( $this->importExtensions && preg_match_all( $regex, $content, $matches, PREG_SET_ORDER ) ) { $search = array(); $replace = array(); // loop the matches foreach ( $matches as $match ) { $extension = substr( strrchr( $match[2], '.' ), 1 ); if ( $extension && ! array_key_exists( $extension, $this->importExtensions ) ) { continue; } // get the path for the file that will be imported $path = $match[2]; $path = dirname( $source ) . '/' . $path; // only replace the import with the content if we're able to get // the content of the file, and it's relatively small if ( $this->canImportFile( $path ) && $this->canImportBySize( $path ) ) { // grab content && base64-ize $importContent = $this->load( $path ); $importContent = base64_encode( $importContent ); // build replacement $search[] = $match[0]; $replace[] = 'url(' . $this->importExtensions[ $extension ] . ';base64,' . $importContent . ')'; } } // replace the import statements $content = str_replace( $search, $replace, $content ); } return $content; } /** * Minify the data. * Perform CSS optimizations. * * @param string[optional] $path Path to write the data to * @param string[] $parents Parent paths, for circular reference checks * * @return string The minified data */ public function execute( $path = null, $parents = array() ) { $content = ''; // loop CSS data (raw data and files) foreach ( $this->data as $source => $css ) { /* * Let's first take out strings & comments, since we can't just * remove whitespace anywhere. If whitespace occurs inside a string, * we should leave it alone. E.g.: * p { content: "a test" } */ $this->extractStrings(); $this->stripComments(); $this->extractMath(); $this->extractCustomProperties(); $css = $this->replace( $css ); $css = $this->stripWhitespace( $css ); $css = $this->convertLegacyColors( $css ); $css = $this->cleanupModernColors( $css ); $css = $this->shortenHEXColors( $css ); $css = $this->shortenZeroes( $css ); $css = $this->shortenFontWeights( $css ); $css = $this->stripEmptyTags( $css ); // restore the string we've extracted earlier $css = $this->restoreExtractedData( $css ); $source = is_int( $source ) ? '' : $source; $parents = $source ? array_merge( $parents, array( $source ) ) : $parents; $css = $this->combineImports( $source, $css, $parents ); $css = $this->importFiles( $source, $css ); /* * If we'll save to a new path, we'll have to fix the relative paths * to be relative no longer to the source file, but to the new path. * If we don't write to a file, fall back to same path so no * conversion happens (because we still want it to go through most * of the move code, which also addresses url() & @import syntax...) */ $converter = $this->getPathConverter( $source, $path ?: $source ); $css = $this->move( $converter, $css ); // combine css $content .= $css; } $content = $this->moveImportsToTop( $content ); return $content; } /** * Moving a css file should update all relative urls. * Relative references (e.g. ../images/image.gif) in a certain css file, * will have to be updated when a file is being saved at another location * (e.g. ../../images/image.gif, if the new CSS file is 1 folder deeper). * * @param ConverterInterface $converter Relative path converter * @param string $content The CSS content to update relative urls for * * @return string */ protected function move( ConverterInterface $converter, $content ) { /* * Relative path references will usually be enclosed by url(). @import * is an exception, where url() is not necessary around the path (but is * allowed). * This *could* be 1 regular expression, where both regular expressions * in this array are on different sides of a |. But we're using named * patterns in both regexes, the same name on both regexes. This is only * possible with a (?J) modifier, but that only works after a fairly * recent PCRE version. That's why I'm doing 2 separate regular * expressions & combining the matches after executing of both. */ $relativeRegexes = array( // url(xxx) '/ # open url() url\( \s* # open path enclosure (?P<quotes>["\'])? # fetch path (?P<path>.+?) # close path enclosure (?(quotes)(?P=quotes)) \s* # close url() \) /ix', // @import "xxx" '/ # import statement @import # whitespace \s+ # we don\'t have to check for @import url(), because the # condition above will already catch these # open path enclosure (?P<quotes>["\']) # fetch path (?P<path>.+?) # close path enclosure (?P=quotes) /ix', ); // find all relative urls in css $matches = array(); foreach ( $relativeRegexes as $relativeRegex ) { if ( preg_match_all( $relativeRegex, $content, $regexMatches, PREG_SET_ORDER ) ) { $matches = array_merge( $matches, $regexMatches ); } } $search = array(); $replace = array(); // loop all urls foreach ( $matches as $match ) { // determine if it's a url() or an @import match $type = ( strpos( $match[0], '@import' ) === 0 ? 'import' : 'url' ); $url = $match['path']; if ( $this->canImportByPath( $url ) ) { // attempting to interpret GET-params makes no sense, so let's discard them for awhile $params = strrchr( $url, '?' ); $url = $params ? substr( $url, 0, -strlen( $params ) ) : $url; // fix relative url $url = $converter->convert( $url ); // now that the path has been converted, re-apply GET-params $url .= $params; } /* * Urls with control characters above 0x7e should be quoted. * According to Mozilla's parser, whitespace is only allowed at the * end of unquoted urls. * Urls with `)` (as could happen with data: uris) should also be * quoted to avoid being confused for the url() closing parentheses. * And urls with a # have also been reported to cause issues. * Urls with quotes inside should also remain escaped. * * @see https://developer.mozilla.org/nl/docs/Web/CSS/url#The_url()_functional_notation * @see https://hg.mozilla.org/mozilla-central/rev/14abca4e7378 * @see https://github.com/matthiasmullie/minify/issues/193 */ $url = trim( $url ); if ( preg_match( '/[\s\)\'"#\x{7f}-\x{9f}]/u', $url ) ) { $url = $match['quotes'] . $url . $match['quotes']; } // build replacement $search[] = $match[0]; if ( $type === 'url' ) { $replace[] = 'url(' . $url . ')'; } elseif ( $type === 'import' ) { $replace[] = '@import "' . $url . '"'; } } // replace urls return str_replace( $search, $replace, $content ); } /** * Shorthand HEX color codes. * #FF0000FF -> #f00 -> red * #FF00FF00 -> transparent. * * @param string $content The CSS content to shorten the HEX color codes for * * @return string */ protected function shortenHexColors( $content ) { // shorten repeating patterns within HEX .. $content = preg_replace( '/(?<=[: ])#([0-9a-f])\\1([0-9a-f])\\2([0-9a-f])\\3(?:([0-9a-f])\\4)?(?=[; }])/i', '#$1$2$3$4', $content ); // remove alpha channel if it's pointless .. $content = preg_replace( '/(?<=[: ])#([0-9a-f]{6})ff(?=[; }])/i', '#$1', $content ); $content = preg_replace( '/(?<=[: ])#([0-9a-f]{3})f(?=[; }])/i', '#$1', $content ); // replace `transparent` with shortcut .. $content = preg_replace( '/(?<=[: ])#[0-9a-f]{6}00(?=[; }])/i', '#fff0', $content ); $colors = array( // make these more readable '#00f' => 'blue', '#dc143c' => 'crimson', '#0ff' => 'cyan', '#8b0000' => 'darkred', '#696969' => 'dimgray', '#ff69b4' => 'hotpink', '#0f0' => 'lime', '#fdf5e6' => 'oldlace', '#87ceeb' => 'skyblue', '#d8bfd8' => 'thistle', // we can shorten some even more by replacing them with their color name '#f0ffff' => 'azure', '#f5f5dc' => 'beige', '#ffe4c4' => 'bisque', '#a52a2a' => 'brown', '#ff7f50' => 'coral', '#ffd700' => 'gold', '#808080' => 'gray', '#008000' => 'green', '#4b0082' => 'indigo', '#fffff0' => 'ivory', '#f0e68c' => 'khaki', '#faf0e6' => 'linen', '#800000' => 'maroon', '#000080' => 'navy', '#808000' => 'olive', '#ffa500' => 'orange', '#da70d6' => 'orchid', '#cd853f' => 'peru', '#ffc0cb' => 'pink', '#dda0dd' => 'plum', '#800080' => 'purple', '#f00' => 'red', '#fa8072' => 'salmon', '#a0522d' => 'sienna', '#c0c0c0' => 'silver', '#fffafa' => 'snow', '#d2b48c' => 'tan', '#008080' => 'teal', '#ff6347' => 'tomato', '#ee82ee' => 'violet', '#f5deb3' => 'wheat', // or the other way around 'black' => '#000', 'fuchsia' => '#f0f', 'magenta' => '#f0f', 'white' => '#fff', 'yellow' => '#ff0', // and also `transparent` 'transparent' => '#fff0', ); return preg_replace_callback( '/(?<=[: ])(' . implode( '|', array_keys( $colors ) ) . ')(?=[; }])/i', function ( $match ) use ( $colors ) { return $colors[ strtolower( $match[0] ) ]; }, $content ); } /** * Convert RGB|HSL color codes. * rgb(255,0,0,.5) -> rgb(255 0 0 / .5). * rgb(255,0,0) -> #f00. * * @param string $content The CSS content to shorten the RGB color codes for * * @return string */ protected function convertLegacyColors( $content ) { /* https://drafts.csswg.org/css-color/#color-syntax-legacy https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgb https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsl */ // convert legacy color syntax $content = preg_replace( '/(rgb)a?\(\s*([0-9]{1,3}%?)\s*,\s*([0-9]{1,3}%?)\s*,\s*([0-9]{1,3}%?)\s*,\s*([0,1]?(?:\.[0-9]*)?)\s*\)/i', '$1($2 $3 $4 / $5)', $content ); $content = preg_replace( '/(rgb)a?\(\s*([0-9]{1,3}%?)\s*,\s*([0-9]{1,3}%?)\s*,\s*([0-9]{1,3}%?)\s*\)/i', '$1($2 $3 $4)', $content ); $content = preg_replace( '/(hsl)a?\(\s*([0-9]+(?:deg|grad|rad|turn)?)\s*,\s*([0-9]{1,3}%)\s*,\s*([0-9]{1,3}%)\s*,\s*([0,1]?(?:\.[0-9]*)?)\s*\)/i', '$1($2 $3 $4 / $5)', $content ); $content = preg_replace( '/(hsl)a?\(\s*([0-9]+(?:deg|grad|rad|turn)?)\s*,\s*([0-9]{1,3}%)\s*,\s*([0-9]{1,3}%)\s*\)/i', '$1($2 $3 $4)', $content ); // convert `rgb` to `hex` $dec = '([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])'; return preg_replace_callback( "/rgb\($dec $dec $dec\)/i", function ( $match ) { return sprintf( '#%02x%02x%02x', $match[1], $match[2], $match[3] ); }, $content ); } /** * Cleanup RGB|HSL|HWB|LCH|LAB * rgb(255 0 0 / 1) -> rgb(255 0 0). * rgb(255 0 0 / 0) -> transparent. * * @param string $content The CSS content to cleanup HSL|HWB|LCH|LAB * * @return string */ protected function cleanupModernColors( $content ) { /* https://drafts.csswg.org/css-color/#color-syntax-modern https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hwb https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/lch https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/lab https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklab */ $tag = '(rgb|hsl|hwb|(?:(?:ok)?(?:lch|lab)))'; // remove alpha channel if it's pointless .. $content = preg_replace( '/' . $tag . '\(\s*([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+\/\s+1(?:(?:\.\d?)*|00%)?\s*\)/i', '$1($2 $3 $4)', $content ); // replace `transparent` with shortcut .. $content = preg_replace( '/' . $tag . '\(\s*[^\s]+\s+[^\s]+\s+[^\s]+\s+\/\s+0(?:[\.0%]*)?\s*\)/i', '#fff0', $content ); return $content; } /** * Shorten CSS font weights. * * @param string $content The CSS content to shorten the font weights for * * @return string */ protected function shortenFontWeights( $content ) { $weights = array( 'normal' => 400, 'bold' => 700, ); $callback = function ( $match ) use ( $weights ) { return $match[1] . $weights[ $match[2] ]; }; return preg_replace_callback( '/(font-weight\s*:\s*)(' . implode( '|', array_keys( $weights ) ) . ')(?=[;}])/', $callback, $content ); } /** * Shorthand 0 values to plain 0, instead of e.g. -0em. * * @param string $content The CSS content to shorten the zero values for * * @return string */ protected function shortenZeroes( $content ) { // we don't want to strip units in `calc()` expressions: // `5px - 0px` is valid, but `5px - 0` is not // `10px * 0` is valid (equates to 0), and so is `10 * 0px`, but // `10 * 0` is invalid // we've extracted calcs earlier, so we don't need to worry about this // reusable bits of code throughout these regexes: // before & after are used to make sure we don't match lose unintended // 0-like values (e.g. in #000, or in http://url/1.0) // units can be stripped from 0 values, or used to recognize non 0 // values (where wa may be able to strip a .0 suffix) $before = '(?<=[:(, ])'; $after = '(?=[ ,);}])'; $units = '(em|ex|%|px|cm|mm|in|pt|pc|ch|rem|vh|vw|vmin|vmax|vm)'; // strip units after zeroes (0px -> 0) // NOTE: it should be safe to remove all units for a 0 value, but in // practice, Webkit (especially Safari) seems to stumble over at least // 0%, potentially other units as well. Only stripping 'px' for now. // @see https://github.com/matthiasmullie/minify/issues/60 $content = preg_replace( '/' . $before . '(-?0*(\.0+)?)(?<=0)px' . $after . '/', '\\1', $content ); // strip 0-digits (.0 -> 0) $content = preg_replace( '/' . $before . '\.0+' . $units . '?' . $after . '/', '0\\1', $content ); // strip trailing 0: 50.10 -> 50.1, 50.10px -> 50.1px $content = preg_replace( '/' . $before . '(-?[0-9]+\.[0-9]+)0+' . $units . '?' . $after . '/', '\\1\\2', $content ); // strip trailing 0: 50.00 -> 50, 50.00px -> 50px $content = preg_replace( '/' . $before . '(-?[0-9]+)\.0+' . $units . '?' . $after . '/', '\\1\\2', $content ); // strip leading 0: 0.1 -> .1, 01.1 -> 1.1 $content = preg_replace( '/' . $before . '(-?)0+([0-9]*\.[0-9]+)' . $units . '?' . $after . '/', '\\1\\2\\3', $content ); // strip negative zeroes (-0 -> 0) & truncate zeroes (00 -> 0) $content = preg_replace( '/' . $before . '-?0+' . $units . '?' . $after . '/', '0\\1', $content ); // IE doesn't seem to understand a unitless flex-basis value (correct - // it goes against the spec), so let's add it in again (make it `%`, // which is only 1 char: 0%, 0px, 0 anything, it's all just the same) // @see https://developer.mozilla.org/nl/docs/Web/CSS/flex $content = preg_replace( '/flex:([0-9]+\s[0-9]+\s)0([;\}])/', 'flex:${1}0%${2}', $content ); $content = preg_replace( '/flex-basis:0([;\}])/', 'flex-basis:0%${1}', $content ); return $content; } /** * Strip empty tags from source code. * * @param string $content * * @return string */ protected function stripEmptyTags( $content ) { $content = preg_replace( '/(?<=^)[^\{\};]+\{\s*\}/', '', $content ); $content = preg_replace( '/(?<=(\}|;))[^\{\};]+\{\s*\}/', '', $content ); return $content; } /** * Strip comments from source code. */ protected function stripComments() { $this->stripMultilineComments(); } /** * Strip whitespace. * * @param string $content The CSS content to strip the whitespace for * * @return string */ protected function stripWhitespace( $content ) { // remove leading & trailing whitespace $content = preg_replace( '/^\s*/m', '', $content ); $content = preg_replace( '/\s*$/m', '', $content ); // replace newlines with a single space $content = preg_replace( '/\s+/', ' ', $content ); // remove whitespace around meta characters // inspired by stackoverflow.com/questions/15195750/minify-compress-css-with-regex $content = preg_replace( '/\s*([\*$~^|]?+=|[{};,>~]|!important\b)\s*/', '$1', $content ); $content = preg_replace( '/([\[(:>\+])\s+/', '$1', $content ); $content = preg_replace( '/\s+([\]\)>\+])/', '$1', $content ); $content = preg_replace( '/\s+(:)(?![^\}]*\{)/', '$1', $content ); // whitespace around + and - can only be stripped inside some pseudo- // classes, like `:nth-child(3+2n)` // not in things like `calc(3px + 2px)`, shorthands like `3px -2px`, or // selectors like `div.weird- p` $pseudos = array( 'nth-child', 'nth-last-child', 'nth-last-of-type', 'nth-of-type' ); $content = preg_replace( '/:(' . implode( '|', $pseudos ) . ')\(\s*([+-]?)\s*(.+?)\s*([+-]?)\s*(.*?)\s*\)/', ':$1($2$3$4$5)', $content ); // remove semicolon/whitespace followed by closing bracket $content = str_replace( ';}', '}', $content ); return trim( $content ); } /** * Replace all occurrences of functions that may contain math, where * whitespace around operators needs to be preserved (e.g. calc, clamp). */ protected function extractMath() { $functions = array( 'calc', 'clamp', 'min', 'max' ); $pattern = '/\b(' . implode( '|', $functions ) . ')(\(.+?)(?=$|;|})/m'; // PHP only supports $this inside anonymous functions since 5.4 $minifier = $this; $callback = function ( $match ) use ( $minifier, $pattern, &$callback ) { $function = $match[1]; $length = strlen( $match[2] ); $expr = ''; $opened = 0; // the regular expression for extracting math has 1 significant problem: // it can't determine the correct closing parenthesis... // instead, it'll match a larger portion of code to where it's certain that // the calc() musts have ended, and we'll figure out which is the correct // closing parenthesis here, by counting how many have opened for ( $i = 0; $i < $length; ++$i ) { $char = $match[2][ $i ]; $expr .= $char; if ( $char === '(' ) { ++$opened; } elseif ( $char === ')' && --$opened === 0 ) { break; } } // now that we've figured out where the calc() starts and ends, extract it $count = count( $minifier->extracted ); $placeholder = 'math(' . $count . ')'; $minifier->extracted[ $placeholder ] = $function . '(' . trim( substr( $expr, 1, -1 ) ) . ')'; // and since we've captured more code than required, we may have some leftover // calc() in here too - go recursive on the remaining but of code to go figure // that out and extract what is needed $rest = $minifier->str_replace_first( $function . $expr, '', $match[0] ); $rest = preg_replace_callback( $pattern, $callback, $rest ); return $placeholder . $rest; }; $this->registerPattern( $pattern, $callback ); } /** * Replace custom properties, whose values may be used in scenarios where * we wouldn't want them to be minified (e.g. inside calc). */ protected function extractCustomProperties() { // PHP only supports $this inside anonymous functions since 5.4 $minifier = $this; $this->registerPattern( '/(?<=^|[;}{])\s*(--[^:;{}"\'\s]+)\s*:([^;{}]+)/m', function ( $match ) use ( $minifier ) { $placeholder = '--custom-' . count( $minifier->extracted ) . ':0'; $minifier->extracted[ $placeholder ] = $match[1] . ':' . trim( $match[2] ); return $placeholder; } ); } /** * Check if file is small enough to be imported. * * @param string $path The path to the file * * @return bool */ protected function canImportBySize( $path ) { return ( $size = @filesize( $path ) ) && $size <= $this->maxImportSize * 1024; } /** * Check if file a file can be imported, going by the path. * * @param string $path * * @return bool */ protected function canImportByPath( $path ) { return preg_match( '/^(data:|https?:|\\/)/', $path ) === 0; } /** * Return a converter to update relative paths to be relative to the new * destination. * * @param string $source * @param string $target * * @return ConverterInterface */ protected function getPathConverter( $source, $target ) { return new Converter( $source, $target ); } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������lib/css_js_min/minify/js.cls.php��������������������������������������������������������������������0000644�����������������00000111322�15207571335�0012651 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // phpcs:ignoreFile /** * js.cls.php - modified PHP implementation of Matthias Mullie's JavaScript minifier * * @author Matthias Mullie <minify@mullie.eu> * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved * @license MIT License */ namespace LiteSpeed\Lib\CSS_JS_MIN\Minify; defined( 'WPINC' ) || exit; class JS extends Minify { /** * Var-matching regex based on http://stackoverflow.com/a/9337047/802993. * * Note that regular expressions using that bit must have the PCRE_UTF8 * pattern modifier (/u) set. * * @internal * * @var string */ const REGEX_VARIABLE = '\b[$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\x{02c1}\x{02c6}-\x{02d1}\x{02e0}-\x{02e4}\x{02ec}\x{02ee}\x{0370}-\x{0374}\x{0376}\x{0377}\x{037a}-\x{037d}\x{0386}\x{0388}-\x{038a}\x{038c}\x{038e}-\x{03a1}\x{03a3}-\x{03f5}\x{03f7}-\x{0481}\x{048a}-\x{0527}\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0587}\x{05d0}-\x{05ea}\x{05f0}-\x{05f2}\x{0620}-\x{064a}\x{066e}\x{066f}\x{0671}-\x{06d3}\x{06d5}\x{06e5}\x{06e6}\x{06ee}\x{06ef}\x{06fa}-\x{06fc}\x{06ff}\x{0710}\x{0712}-\x{072f}\x{074d}-\x{07a5}\x{07b1}\x{07ca}-\x{07ea}\x{07f4}\x{07f5}\x{07fa}\x{0800}-\x{0815}\x{081a}\x{0824}\x{0828}\x{0840}-\x{0858}\x{08a0}\x{08a2}-\x{08ac}\x{0904}-\x{0939}\x{093d}\x{0950}\x{0958}-\x{0961}\x{0971}-\x{0977}\x{0979}-\x{097f}\x{0985}-\x{098c}\x{098f}\x{0990}\x{0993}-\x{09a8}\x{09aa}-\x{09b0}\x{09b2}\x{09b6}-\x{09b9}\x{09bd}\x{09ce}\x{09dc}\x{09dd}\x{09df}-\x{09e1}\x{09f0}\x{09f1}\x{0a05}-\x{0a0a}\x{0a0f}\x{0a10}\x{0a13}-\x{0a28}\x{0a2a}-\x{0a30}\x{0a32}\x{0a33}\x{0a35}\x{0a36}\x{0a38}\x{0a39}\x{0a59}-\x{0a5c}\x{0a5e}\x{0a72}-\x{0a74}\x{0a85}-\x{0a8d}\x{0a8f}-\x{0a91}\x{0a93}-\x{0aa8}\x{0aaa}-\x{0ab0}\x{0ab2}\x{0ab3}\x{0ab5}-\x{0ab9}\x{0abd}\x{0ad0}\x{0ae0}\x{0ae1}\x{0b05}-\x{0b0c}\x{0b0f}\x{0b10}\x{0b13}-\x{0b28}\x{0b2a}-\x{0b30}\x{0b32}\x{0b33}\x{0b35}-\x{0b39}\x{0b3d}\x{0b5c}\x{0b5d}\x{0b5f}-\x{0b61}\x{0b71}\x{0b83}\x{0b85}-\x{0b8a}\x{0b8e}-\x{0b90}\x{0b92}-\x{0b95}\x{0b99}\x{0b9a}\x{0b9c}\x{0b9e}\x{0b9f}\x{0ba3}\x{0ba4}\x{0ba8}-\x{0baa}\x{0bae}-\x{0bb9}\x{0bd0}\x{0c05}-\x{0c0c}\x{0c0e}-\x{0c10}\x{0c12}-\x{0c28}\x{0c2a}-\x{0c33}\x{0c35}-\x{0c39}\x{0c3d}\x{0c58}\x{0c59}\x{0c60}\x{0c61}\x{0c85}-\x{0c8c}\x{0c8e}-\x{0c90}\x{0c92}-\x{0ca8}\x{0caa}-\x{0cb3}\x{0cb5}-\x{0cb9}\x{0cbd}\x{0cde}\x{0ce0}\x{0ce1}\x{0cf1}\x{0cf2}\x{0d05}-\x{0d0c}\x{0d0e}-\x{0d10}\x{0d12}-\x{0d3a}\x{0d3d}\x{0d4e}\x{0d60}\x{0d61}\x{0d7a}-\x{0d7f}\x{0d85}-\x{0d96}\x{0d9a}-\x{0db1}\x{0db3}-\x{0dbb}\x{0dbd}\x{0dc0}-\x{0dc6}\x{0e01}-\x{0e30}\x{0e32}\x{0e33}\x{0e40}-\x{0e46}\x{0e81}\x{0e82}\x{0e84}\x{0e87}\x{0e88}\x{0e8a}\x{0e8d}\x{0e94}-\x{0e97}\x{0e99}-\x{0e9f}\x{0ea1}-\x{0ea3}\x{0ea5}\x{0ea7}\x{0eaa}\x{0eab}\x{0ead}-\x{0eb0}\x{0eb2}\x{0eb3}\x{0ebd}\x{0ec0}-\x{0ec4}\x{0ec6}\x{0edc}-\x{0edf}\x{0f00}\x{0f40}-\x{0f47}\x{0f49}-\x{0f6c}\x{0f88}-\x{0f8c}\x{1000}-\x{102a}\x{103f}\x{1050}-\x{1055}\x{105a}-\x{105d}\x{1061}\x{1065}\x{1066}\x{106e}-\x{1070}\x{1075}-\x{1081}\x{108e}\x{10a0}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{10fa}\x{10fc}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1380}-\x{138f}\x{13a0}-\x{13f4}\x{1401}-\x{166c}\x{166f}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16ea}\x{16ee}-\x{16f0}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17d7}\x{17dc}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191c}\x{1950}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19c1}-\x{19c7}\x{1a00}-\x{1a16}\x{1a20}-\x{1a54}\x{1aa7}\x{1b05}-\x{1b33}\x{1b45}-\x{1b4b}\x{1b83}-\x{1ba0}\x{1bae}\x{1baf}\x{1bba}-\x{1be5}\x{1c00}-\x{1c23}\x{1c4d}-\x{1c4f}\x{1c5a}-\x{1c7d}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf1}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{2160}-\x{2188}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{2e2f}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{31a0}-\x{31ba}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}\x{4e00}-\x{9fcc}\x{a000}-\x{a48c}\x{a4d0}-\x{a4fd}\x{a500}-\x{a60c}\x{a610}-\x{a61f}\x{a62a}\x{a62b}\x{a640}-\x{a66e}\x{a67f}-\x{a697}\x{a6a0}-\x{a6ef}\x{a717}-\x{a71f}\x{a722}-\x{a788}\x{a78b}-\x{a78e}\x{a790}-\x{a793}\x{a7a0}-\x{a7aa}\x{a7f8}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a822}\x{a840}-\x{a873}\x{a882}-\x{a8b3}\x{a8f2}-\x{a8f7}\x{a8fb}\x{a90a}-\x{a925}\x{a930}-\x{a946}\x{a960}-\x{a97c}\x{a984}-\x{a9b2}\x{a9cf}\x{aa00}-\x{aa28}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa60}-\x{aa76}\x{aa7a}\x{aa80}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aadd}\x{aae0}-\x{aaea}\x{aaf2}-\x{aaf4}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{abc0}-\x{abe2}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{f900}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb36}\x{fb38}-\x{fb3c}\x{fb3e}\x{fb40}\x{fb41}\x{fb43}\x{fb44}\x{fb46}-\x{fbb1}\x{fbd3}-\x{fd3d}\x{fd50}-\x{fd8f}\x{fd92}-\x{fdc7}\x{fdf0}-\x{fdfb}\x{fe70}-\x{fe74}\x{fe76}-\x{fefc}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}][$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\x{02c1}\x{02c6}-\x{02d1}\x{02e0}-\x{02e4}\x{02ec}\x{02ee}\x{0370}-\x{0374}\x{0376}\x{0377}\x{037a}-\x{037d}\x{0386}\x{0388}-\x{038a}\x{038c}\x{038e}-\x{03a1}\x{03a3}-\x{03f5}\x{03f7}-\x{0481}\x{048a}-\x{0527}\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0587}\x{05d0}-\x{05ea}\x{05f0}-\x{05f2}\x{0620}-\x{064a}\x{066e}\x{066f}\x{0671}-\x{06d3}\x{06d5}\x{06e5}\x{06e6}\x{06ee}\x{06ef}\x{06fa}-\x{06fc}\x{06ff}\x{0710}\x{0712}-\x{072f}\x{074d}-\x{07a5}\x{07b1}\x{07ca}-\x{07ea}\x{07f4}\x{07f5}\x{07fa}\x{0800}-\x{0815}\x{081a}\x{0824}\x{0828}\x{0840}-\x{0858}\x{08a0}\x{08a2}-\x{08ac}\x{0904}-\x{0939}\x{093d}\x{0950}\x{0958}-\x{0961}\x{0971}-\x{0977}\x{0979}-\x{097f}\x{0985}-\x{098c}\x{098f}\x{0990}\x{0993}-\x{09a8}\x{09aa}-\x{09b0}\x{09b2}\x{09b6}-\x{09b9}\x{09bd}\x{09ce}\x{09dc}\x{09dd}\x{09df}-\x{09e1}\x{09f0}\x{09f1}\x{0a05}-\x{0a0a}\x{0a0f}\x{0a10}\x{0a13}-\x{0a28}\x{0a2a}-\x{0a30}\x{0a32}\x{0a33}\x{0a35}\x{0a36}\x{0a38}\x{0a39}\x{0a59}-\x{0a5c}\x{0a5e}\x{0a72}-\x{0a74}\x{0a85}-\x{0a8d}\x{0a8f}-\x{0a91}\x{0a93}-\x{0aa8}\x{0aaa}-\x{0ab0}\x{0ab2}\x{0ab3}\x{0ab5}-\x{0ab9}\x{0abd}\x{0ad0}\x{0ae0}\x{0ae1}\x{0b05}-\x{0b0c}\x{0b0f}\x{0b10}\x{0b13}-\x{0b28}\x{0b2a}-\x{0b30}\x{0b32}\x{0b33}\x{0b35}-\x{0b39}\x{0b3d}\x{0b5c}\x{0b5d}\x{0b5f}-\x{0b61}\x{0b71}\x{0b83}\x{0b85}-\x{0b8a}\x{0b8e}-\x{0b90}\x{0b92}-\x{0b95}\x{0b99}\x{0b9a}\x{0b9c}\x{0b9e}\x{0b9f}\x{0ba3}\x{0ba4}\x{0ba8}-\x{0baa}\x{0bae}-\x{0bb9}\x{0bd0}\x{0c05}-\x{0c0c}\x{0c0e}-\x{0c10}\x{0c12}-\x{0c28}\x{0c2a}-\x{0c33}\x{0c35}-\x{0c39}\x{0c3d}\x{0c58}\x{0c59}\x{0c60}\x{0c61}\x{0c85}-\x{0c8c}\x{0c8e}-\x{0c90}\x{0c92}-\x{0ca8}\x{0caa}-\x{0cb3}\x{0cb5}-\x{0cb9}\x{0cbd}\x{0cde}\x{0ce0}\x{0ce1}\x{0cf1}\x{0cf2}\x{0d05}-\x{0d0c}\x{0d0e}-\x{0d10}\x{0d12}-\x{0d3a}\x{0d3d}\x{0d4e}\x{0d60}\x{0d61}\x{0d7a}-\x{0d7f}\x{0d85}-\x{0d96}\x{0d9a}-\x{0db1}\x{0db3}-\x{0dbb}\x{0dbd}\x{0dc0}-\x{0dc6}\x{0e01}-\x{0e30}\x{0e32}\x{0e33}\x{0e40}-\x{0e46}\x{0e81}\x{0e82}\x{0e84}\x{0e87}\x{0e88}\x{0e8a}\x{0e8d}\x{0e94}-\x{0e97}\x{0e99}-\x{0e9f}\x{0ea1}-\x{0ea3}\x{0ea5}\x{0ea7}\x{0eaa}\x{0eab}\x{0ead}-\x{0eb0}\x{0eb2}\x{0eb3}\x{0ebd}\x{0ec0}-\x{0ec4}\x{0ec6}\x{0edc}-\x{0edf}\x{0f00}\x{0f40}-\x{0f47}\x{0f49}-\x{0f6c}\x{0f88}-\x{0f8c}\x{1000}-\x{102a}\x{103f}\x{1050}-\x{1055}\x{105a}-\x{105d}\x{1061}\x{1065}\x{1066}\x{106e}-\x{1070}\x{1075}-\x{1081}\x{108e}\x{10a0}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{10fa}\x{10fc}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1380}-\x{138f}\x{13a0}-\x{13f4}\x{1401}-\x{166c}\x{166f}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16ea}\x{16ee}-\x{16f0}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17d7}\x{17dc}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191c}\x{1950}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19c1}-\x{19c7}\x{1a00}-\x{1a16}\x{1a20}-\x{1a54}\x{1aa7}\x{1b05}-\x{1b33}\x{1b45}-\x{1b4b}\x{1b83}-\x{1ba0}\x{1bae}\x{1baf}\x{1bba}-\x{1be5}\x{1c00}-\x{1c23}\x{1c4d}-\x{1c4f}\x{1c5a}-\x{1c7d}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf1}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{2160}-\x{2188}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{2e2f}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{31a0}-\x{31ba}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}\x{4e00}-\x{9fcc}\x{a000}-\x{a48c}\x{a4d0}-\x{a4fd}\x{a500}-\x{a60c}\x{a610}-\x{a61f}\x{a62a}\x{a62b}\x{a640}-\x{a66e}\x{a67f}-\x{a697}\x{a6a0}-\x{a6ef}\x{a717}-\x{a71f}\x{a722}-\x{a788}\x{a78b}-\x{a78e}\x{a790}-\x{a793}\x{a7a0}-\x{a7aa}\x{a7f8}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a822}\x{a840}-\x{a873}\x{a882}-\x{a8b3}\x{a8f2}-\x{a8f7}\x{a8fb}\x{a90a}-\x{a925}\x{a930}-\x{a946}\x{a960}-\x{a97c}\x{a984}-\x{a9b2}\x{a9cf}\x{aa00}-\x{aa28}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa60}-\x{aa76}\x{aa7a}\x{aa80}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aadd}\x{aae0}-\x{aaea}\x{aaf2}-\x{aaf4}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{abc0}-\x{abe2}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{f900}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb36}\x{fb38}-\x{fb3c}\x{fb3e}\x{fb40}\x{fb41}\x{fb43}\x{fb44}\x{fb46}-\x{fbb1}\x{fbd3}-\x{fd3d}\x{fd50}-\x{fd8f}\x{fd92}-\x{fdc7}\x{fdf0}-\x{fdfb}\x{fe70}-\x{fe74}\x{fe76}-\x{fefc}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}0-9\x{0300}-\x{036f}\x{0483}-\x{0487}\x{0591}-\x{05bd}\x{05bf}\x{05c1}\x{05c2}\x{05c4}\x{05c5}\x{05c7}\x{0610}-\x{061a}\x{064b}-\x{0669}\x{0670}\x{06d6}-\x{06dc}\x{06df}-\x{06e4}\x{06e7}\x{06e8}\x{06ea}-\x{06ed}\x{06f0}-\x{06f9}\x{0711}\x{0730}-\x{074a}\x{07a6}-\x{07b0}\x{07c0}-\x{07c9}\x{07eb}-\x{07f3}\x{0816}-\x{0819}\x{081b}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082d}\x{0859}-\x{085b}\x{08e4}-\x{08fe}\x{0900}-\x{0903}\x{093a}-\x{093c}\x{093e}-\x{094f}\x{0951}-\x{0957}\x{0962}\x{0963}\x{0966}-\x{096f}\x{0981}-\x{0983}\x{09bc}\x{09be}-\x{09c4}\x{09c7}\x{09c8}\x{09cb}-\x{09cd}\x{09d7}\x{09e2}\x{09e3}\x{09e6}-\x{09ef}\x{0a01}-\x{0a03}\x{0a3c}\x{0a3e}-\x{0a42}\x{0a47}\x{0a48}\x{0a4b}-\x{0a4d}\x{0a51}\x{0a66}-\x{0a71}\x{0a75}\x{0a81}-\x{0a83}\x{0abc}\x{0abe}-\x{0ac5}\x{0ac7}-\x{0ac9}\x{0acb}-\x{0acd}\x{0ae2}\x{0ae3}\x{0ae6}-\x{0aef}\x{0b01}-\x{0b03}\x{0b3c}\x{0b3e}-\x{0b44}\x{0b47}\x{0b48}\x{0b4b}-\x{0b4d}\x{0b56}\x{0b57}\x{0b62}\x{0b63}\x{0b66}-\x{0b6f}\x{0b82}\x{0bbe}-\x{0bc2}\x{0bc6}-\x{0bc8}\x{0bca}-\x{0bcd}\x{0bd7}\x{0be6}-\x{0bef}\x{0c01}-\x{0c03}\x{0c3e}-\x{0c44}\x{0c46}-\x{0c48}\x{0c4a}-\x{0c4d}\x{0c55}\x{0c56}\x{0c62}\x{0c63}\x{0c66}-\x{0c6f}\x{0c82}\x{0c83}\x{0cbc}\x{0cbe}-\x{0cc4}\x{0cc6}-\x{0cc8}\x{0cca}-\x{0ccd}\x{0cd5}\x{0cd6}\x{0ce2}\x{0ce3}\x{0ce6}-\x{0cef}\x{0d02}\x{0d03}\x{0d3e}-\x{0d44}\x{0d46}-\x{0d48}\x{0d4a}-\x{0d4d}\x{0d57}\x{0d62}\x{0d63}\x{0d66}-\x{0d6f}\x{0d82}\x{0d83}\x{0dca}\x{0dcf}-\x{0dd4}\x{0dd6}\x{0dd8}-\x{0ddf}\x{0df2}\x{0df3}\x{0e31}\x{0e34}-\x{0e3a}\x{0e47}-\x{0e4e}\x{0e50}-\x{0e59}\x{0eb1}\x{0eb4}-\x{0eb9}\x{0ebb}\x{0ebc}\x{0ec8}-\x{0ecd}\x{0ed0}-\x{0ed9}\x{0f18}\x{0f19}\x{0f20}-\x{0f29}\x{0f35}\x{0f37}\x{0f39}\x{0f3e}\x{0f3f}\x{0f71}-\x{0f84}\x{0f86}\x{0f87}\x{0f8d}-\x{0f97}\x{0f99}-\x{0fbc}\x{0fc6}\x{102b}-\x{103e}\x{1040}-\x{1049}\x{1056}-\x{1059}\x{105e}-\x{1060}\x{1062}-\x{1064}\x{1067}-\x{106d}\x{1071}-\x{1074}\x{1082}-\x{108d}\x{108f}-\x{109d}\x{135d}-\x{135f}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}\x{1753}\x{1772}\x{1773}\x{17b4}-\x{17d3}\x{17dd}\x{17e0}-\x{17e9}\x{180b}-\x{180d}\x{1810}-\x{1819}\x{18a9}\x{1920}-\x{192b}\x{1930}-\x{193b}\x{1946}-\x{194f}\x{19b0}-\x{19c0}\x{19c8}\x{19c9}\x{19d0}-\x{19d9}\x{1a17}-\x{1a1b}\x{1a55}-\x{1a5e}\x{1a60}-\x{1a7c}\x{1a7f}-\x{1a89}\x{1a90}-\x{1a99}\x{1b00}-\x{1b04}\x{1b34}-\x{1b44}\x{1b50}-\x{1b59}\x{1b6b}-\x{1b73}\x{1b80}-\x{1b82}\x{1ba1}-\x{1bad}\x{1bb0}-\x{1bb9}\x{1be6}-\x{1bf3}\x{1c24}-\x{1c37}\x{1c40}-\x{1c49}\x{1c50}-\x{1c59}\x{1cd0}-\x{1cd2}\x{1cd4}-\x{1ce8}\x{1ced}\x{1cf2}-\x{1cf4}\x{1dc0}-\x{1de6}\x{1dfc}-\x{1dff}\x{200c}\x{200d}\x{203f}\x{2040}\x{2054}\x{20d0}-\x{20dc}\x{20e1}\x{20e5}-\x{20f0}\x{2cef}-\x{2cf1}\x{2d7f}\x{2de0}-\x{2dff}\x{302a}-\x{302f}\x{3099}\x{309a}\x{a620}-\x{a629}\x{a66f}\x{a674}-\x{a67d}\x{a69f}\x{a6f0}\x{a6f1}\x{a802}\x{a806}\x{a80b}\x{a823}-\x{a827}\x{a880}\x{a881}\x{a8b4}-\x{a8c4}\x{a8d0}-\x{a8d9}\x{a8e0}-\x{a8f1}\x{a900}-\x{a909}\x{a926}-\x{a92d}\x{a947}-\x{a953}\x{a980}-\x{a983}\x{a9b3}-\x{a9c0}\x{a9d0}-\x{a9d9}\x{aa29}-\x{aa36}\x{aa43}\x{aa4c}\x{aa4d}\x{aa50}-\x{aa59}\x{aa7b}\x{aab0}\x{aab2}-\x{aab4}\x{aab7}\x{aab8}\x{aabe}\x{aabf}\x{aac1}\x{aaeb}-\x{aaef}\x{aaf5}\x{aaf6}\x{abe3}-\x{abea}\x{abec}\x{abed}\x{abf0}-\x{abf9}\x{fb1e}\x{fe00}-\x{fe0f}\x{fe20}-\x{fe26}\x{fe33}\x{fe34}\x{fe4d}-\x{fe4f}\x{ff10}-\x{ff19}\x{ff3f}]*\b'; /** * Full list of JavaScript reserved words. * Will be loaded from /data/js/keywords_reserved.txt. * * @see https://mathiasbynens.be/notes/reserved-keywords * * @var string[] */ protected $keywordsReserved = array(); /** * List of JavaScript reserved words that accept a <variable, value, ...> * after them. Some end of lines are not the end of a statement, like with * these keywords. * * E.g.: we shouldn't insert a ; after this else * else * console.log('this is quite fine') * * Will be loaded from /data/js/keywords_before.txt * * @var string[] */ protected $keywordsBefore = array(); /** * List of JavaScript reserved words that accept a <variable, value, ...> * before them. Some end of lines are not the end of a statement, like when * continued by one of these keywords on the newline. * * E.g.: we shouldn't insert a ; before this instanceof * variable * instanceof String * * Will be loaded from /data/js/keywords_after.txt * * @var string[] */ protected $keywordsAfter = array(); /** * List of all JavaScript operators. * * Will be loaded from /data/js/operators.txt * * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators * * @var string[] */ protected $operators = array(); /** * List of JavaScript operators that accept a <variable, value, ...> after * them. Some end of lines are not the end of a statement, like with these * operators. * * Note: Most operators are fine, we've only removed ++ and --. * ++ & -- have to be joined with the value they're in-/decrementing. * * Will be loaded from /data/js/operators_before.txt * * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators * * @var string[] */ protected $operatorsBefore = array(); /** * List of JavaScript operators that accept a <variable, value, ...> before * them. Some end of lines are not the end of a statement, like when * continued by one of these operators on the newline. * * Note: Most operators are fine, we've only removed ), ], ++, --, ! and ~. * There can't be a newline separating ! or ~ and whatever it is negating. * ++ & -- have to be joined with the value they're in-/decrementing. * ) & ] are "special" in that they have lots or usecases. () for example * is used for function calls, for grouping, in if () and for (), ... * * Will be loaded from /data/js/operators_after.txt * * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators * * @var string[] */ protected $operatorsAfter = array(); public function __construct() { call_user_func_array( array( '\\LiteSpeed\\Lib\\CSS_JS_MIN\\Minify\\Minify', '__construct' ), func_get_args() ); $dataDir = __DIR__ . '/data/js/'; $options = FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES; $this->keywordsReserved = file( $dataDir . 'keywords_reserved.txt', $options ); $this->keywordsBefore = file( $dataDir . 'keywords_before.txt', $options ); $this->keywordsAfter = file( $dataDir . 'keywords_after.txt', $options ); $this->operators = file( $dataDir . 'operators.txt', $options ); $this->operatorsBefore = file( $dataDir . 'operators_before.txt', $options ); $this->operatorsAfter = file( $dataDir . 'operators_after.txt', $options ); } /** * Minify the data. * Perform JS optimizations. * * @param string[optional] $path Path to write the data to * * @return string The minified data */ public function execute( $path = null ) { $content = ''; /* * Let's first take out strings, comments and regular expressions. * All of these can contain JS code-like characters, and we should make * sure any further magic ignores anything inside of these. * * Consider this example, where we should not strip any whitespace: * var str = "a test"; * * Comments will be removed altogether, strings and regular expressions * will be replaced by placeholder text, which we'll restore later. */ $this->extractStrings( '\'"`' ); $this->stripComments(); $this->extractRegex(); // loop files foreach ( $this->data as $source => $js ) { // take out strings, comments & regex (for which we've registered // the regexes just a few lines earlier) $js = $this->replace( $js ); $js = $this->propertyNotation( $js ); $js = $this->shortenBools( $js ); $js = $this->stripWhitespace( $js ); // combine js: separating the scripts by a ; $content .= $js . ';'; } // clean up leftover `;`s from the combination of multiple scripts $content = ltrim( $content, ';' ); $content = (string) substr( $content, 0, -1 ); /* * Earlier, we extracted strings & regular expressions and replaced them * with placeholder text. This will restore them. */ $content = $this->restoreExtractedData( $content ); return $content; } /** * Strip comments from source code. */ protected function stripComments() { $this->stripMultilineComments(); // single-line comments $this->registerPattern( '/\/\/.*$/m', '' ); } /** * JS can have /-delimited regular expressions, like: /ab+c/.match(string). * * The content inside the regex can contain characters that may be confused * for JS code: e.g. it could contain whitespace it needs to match & we * don't want to strip whitespace in there. * * The regex can be pretty simple: we don't have to care about comments, * (which also use slashes) because stripComments() will have stripped those * already. * * This method will replace all string content with simple REGEX# * placeholder text, so we've rid all regular expressions from characters * that may be misinterpreted. Original regex content will be saved in * $this->extracted and after doing all other minifying, we can restore the * original content via restoreRegex() */ protected function extractRegex() { // PHP only supports $this inside anonymous functions since 5.4 $minifier = $this; $callback = function ( $match ) use ( $minifier ) { $count = count( $minifier->extracted ); $placeholder = '"' . $count . '"'; $minifier->extracted[ $placeholder ] = $match[0]; return $placeholder; }; // match all chars except `/` and `\` // `\` is allowed though, along with whatever char follows (which is the // one being escaped) // this should allow all chars, except for an unescaped `/` (= the one // closing the regex) // then also ignore bare `/` inside `[]`, where they don't need to be // escaped: anything inside `[]` can be ignored safely $pattern = '\\/(?!\*)(?:[^\\[\\/\\\\\n\r]++|(?:\\\\.)++|(?:\\[(?:[^\\]\\\\\n\r]++|(?:\\\\.)++)++\\])++)++\\/[gimuy]*'; // a regular expression can only be followed by a few operators or some // of the RegExp methods (a `\` followed by a variable or value is // likely part of a division, not a regex) $keywords = array( 'do', 'in', 'new', 'else', 'throw', 'yield', 'delete', 'return', 'typeof' ); $before = '(^|[=:,;\+\-\*\?\/\}\(\{\[&\|!]|' . implode( '|', $keywords ) . ')\s*'; $propertiesAndMethods = array( // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Properties_2 'constructor', 'flags', 'global', 'ignoreCase', 'multiline', 'source', 'sticky', 'unicode', // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Methods_2 'compile(', 'exec(', 'test(', 'toSource(', 'toString(', ); $delimiters = array_fill( 0, count( $propertiesAndMethods ), '/' ); $propertiesAndMethods = array_map( 'preg_quote', $propertiesAndMethods, $delimiters ); $after = '(?=\s*([\.,;:\)\}&\|+]|\/\/|$|\.(' . implode( '|', $propertiesAndMethods ) . ')))'; $this->registerPattern( '/' . $before . '\K' . $pattern . $after . '/', $callback ); // regular expressions following a `)` are rather annoying to detect... // quite often, `/` after `)` is a division operator & if it happens to // be followed by another one (or a comment), it is likely to be // confused for a regular expression // however, it's perfectly possible for a regex to follow a `)`: after // a single-line `if()`, `while()`, ... statement, for example // since, when they occur like that, they're always the start of a // statement, there's only a limited amount of ways they can be useful: // by calling the regex methods directly // if a regex following `)` is not followed by `.<property or method>`, // it's quite likely not a regex $before = '\)\s*'; $after = '(?=\s*\.(' . implode( '|', $propertiesAndMethods ) . '))'; $this->registerPattern( '/' . $before . '\K' . $pattern . $after . '/', $callback ); // 1 more edge case: a regex can be followed by a lot more operators or // keywords if there's a newline (ASI) in between, where the operator // actually starts a new statement // (https://github.com/matthiasmullie/minify/issues/56) $operators = $this->getOperatorsForRegex( $this->operatorsBefore, '/' ); $operators += $this->getOperatorsForRegex( $this->keywordsReserved, '/' ); $after = '(?=\s*\n\s*(' . implode( '|', $operators ) . '))'; $this->registerPattern( '/' . $pattern . $after . '/', $callback ); } /** * Strip whitespace. * * We won't strip *all* whitespace, but as much as possible. The thing that * we'll preserve are newlines we're unsure about. * JavaScript doesn't require statements to be terminated with a semicolon. * It will automatically fix missing semicolons with ASI (automatic semi- * colon insertion) at the end of line causing errors (without semicolon.) * * Because it's sometimes hard to tell if a newline is part of a statement * that should be terminated or not, we'll just leave some of them alone. * * @param string $content The content to strip the whitespace for * * @return string */ protected function stripWhitespace( $content ) { // uniform line endings, make them all line feed $content = str_replace( array( "\r\n", "\r" ), "\n", $content ); // collapse all non-line feed whitespace into a single space $content = preg_replace( '/[^\S\n]+/', ' ', $content ); // strip leading & trailing whitespace $content = str_replace( array( " \n", "\n " ), "\n", $content ); // collapse consecutive line feeds into just 1 $content = preg_replace( '/\n+/', "\n", $content ); $operatorsBefore = $this->getOperatorsForRegex( $this->operatorsBefore, '/' ); $operatorsAfter = $this->getOperatorsForRegex( $this->operatorsAfter, '/' ); $operators = $this->getOperatorsForRegex( $this->operators, '/' ); $keywordsBefore = $this->getKeywordsForRegex( $this->keywordsBefore, '/' ); $keywordsAfter = $this->getKeywordsForRegex( $this->keywordsAfter, '/' ); // strip whitespace that ends in (or next line begin with) an operator // that allows statements to be broken up over multiple lines unset( $operatorsBefore['+'], $operatorsBefore['-'], $operatorsAfter['+'], $operatorsAfter['-'] ); $content = preg_replace( array( '/(' . implode( '|', $operatorsBefore ) . ')\s+/', '/\s+(' . implode( '|', $operatorsAfter ) . ')/', ), '\\1', $content ); // make sure + and - can't be mistaken for, or joined into ++ and -- $content = preg_replace( array( '/(?<![\+\-])\s*([\+\-])(?![\+\-])/', '/(?<![\+\-])([\+\-])\s*(?![\+\-])/', ), '\\1', $content ); // collapse whitespace around reserved words into single space $content = preg_replace( '/(^|[;\}\s])\K(' . implode( '|', $keywordsBefore ) . ')\s+/', '\\2 ', $content ); $content = preg_replace( '/\s+(' . implode( '|', $keywordsAfter ) . ')(?=([;\{\s]|$))/', ' \\1', $content ); /* * We didn't strip whitespace after a couple of operators because they * could be used in different contexts and we can't be sure it's ok to * strip the newlines. However, we can safely strip any non-line feed * whitespace that follows them. */ $operatorsDiffBefore = array_diff( $operators, $operatorsBefore ); $operatorsDiffAfter = array_diff( $operators, $operatorsAfter ); $content = preg_replace( '/(' . implode( '|', $operatorsDiffBefore ) . ')[^\S\n]+/', '\\1', $content ); $content = preg_replace( '/[^\S\n]+(' . implode( '|', $operatorsDiffAfter ) . ')/', '\\1', $content ); /* * Whitespace after `return` can be omitted in a few occasions * (such as when followed by a string or regex) * Same for whitespace in between `)` and `{`, or between `{` and some * keywords. */ $content = preg_replace( '/\breturn\s+(["\'\/\+\-])/', 'return$1', $content ); $content = preg_replace( '/\)\s+\{/', '){', $content ); $content = preg_replace( '/}\n(else|catch|finally)\b/', '}$1', $content ); /* * Get rid of double semicolons, except where they can be used like: * "for(v=1,_=b;;)", "for(v=1;;v++)" or "for(;;ja||(ja=true))". * I'll safeguard these double semicolons inside for-loops by * temporarily replacing them with an invalid condition: they won't have * a double semicolon and will be easy to spot to restore afterwards. */ $content = preg_replace( '/\bfor\(([^;]*);;([^;]*)\)/', 'for(\\1;-;\\2)', $content ); $content = preg_replace( '/;+/', ';', $content ); $content = preg_replace( '/\bfor\(([^;]*);-;([^;]*)\)/', 'for(\\1;;\\2)', $content ); /* * Next, we'll be removing all semicolons where ASI kicks in. * for-loops however, can have an empty body (ending in only a * semicolon), like: `for(i=1;i<3;i++);`, of `for(i in list);` * Here, nothing happens during the loop; it's just used to keep * increasing `i`. With that ; omitted, the next line would be expected * to be the for-loop's body... Same goes for while loops. * I'm going to double that semicolon (if any) so after the next line, * which strips semicolons here & there, we're still left with this one. * Note the special recursive construct in the three inner parts of the for: * (\{([^\{\}]*(?-2))*[^\{\}]*\})? - it is intended to match inline * functions bodies, e.g.: i<arr.map(function(e){return e}).length. * Also note that the construct is applied only once and multiplied * for each part of the for, otherwise it risks a catastrophic backtracking. * The limitation is that it will not allow closures in more than one * of the three parts for a specific for() case. * REGEX throwing catastrophic backtracking: $content = preg_replace('/(for\([^;\{]*(\{([^\{\}]*(?-2))*[^\{\}]*\})?[^;\{]*;[^;\{]*(\{([^\{\}]*(?-2))*[^\{\}]*\})?[^;\{]*;[^;\{]*(\{([^\{\}]*(?-2))*[^\{\}]*\})?[^;\{]*\));(\}|$)/s', '\\1;;\\8', $content); */ $content = preg_replace( '/(for\((?:[^;\{]*|[^;\{]*function[^;\{]*(\{([^\{\}]*(?-2))*[^\{\}]*\})?[^;\{]*);[^;\{]*;[^;\{]*\));(\}|$)/s', '\\1;;\\4', $content ); $content = preg_replace( '/(for\([^;\{]*;(?:[^;\{]*|[^;\{]*function[^;\{]*(\{([^\{\}]*(?-2))*[^\{\}]*\})?[^;\{]*);[^;\{]*\));(\}|$)/s', '\\1;;\\4', $content ); $content = preg_replace( '/(for\([^;\{]*;[^;\{]*;(?:[^;\{]*|[^;\{]*function[^;\{]*(\{([^\{\}]*(?-2))*[^\{\}]*\})?[^;\{]*)\));(\}|$)/s', '\\1;;\\4', $content ); $content = preg_replace( '/(for\([^;\{]+\s+in\s+[^;\{]+\));(\}|$)/s', '\\1;;\\2', $content ); /* * Do the same for the if's that don't have a body but are followed by ;} */ $content = preg_replace( '/(\bif\s*\([^{;]*\));\}/s', '\\1;;}', $content ); /* * Below will also keep `;` after a `do{}while();` along with `while();` * While these could be stripped after do-while, detecting this * distinction is cumbersome, so I'll play it safe and make sure `;` * after any kind of `while` is kept. */ $content = preg_replace( '/(while\([^;\{]+\));(\}|$)/s', '\\1;;\\2', $content ); /* * We also can't strip empty else-statements. Even though they're * useless and probably shouldn't be in the code in the first place, we * shouldn't be stripping the `;` that follows it as it breaks the code. * We can just remove those useless else-statements completely. * * @see https://github.com/matthiasmullie/minify/issues/91 */ $content = preg_replace( '/else;/s', '', $content ); /* * We also don't really want to terminate statements followed by closing * curly braces (which we've ignored completely up until now) or end-of- * script: ASI will kick in here & we're all about minifying. * Semicolons at beginning of the file don't make any sense either. */ $content = preg_replace( '/;(\}|$)/s', '\\1', $content ); $content = ltrim( $content, ';' ); // get rid of remaining whitespace af beginning/end return trim( $content ); } /** * We'll strip whitespace around certain operators with regular expressions. * This will prepare the given array by escaping all characters. * * @param string[] $operators * @param string $delimiter * * @return string[] */ protected function getOperatorsForRegex( array $operators, $delimiter = '/' ) { // escape operators for use in regex $delimiters = array_fill( 0, count( $operators ), $delimiter ); $escaped = array_map( 'preg_quote', $operators, $delimiters ); $operators = array_combine( $operators, $escaped ); // ignore + & - for now, they'll get special treatment unset( $operators['+'], $operators['-'] ); // dot can not just immediately follow a number; it can be confused for // decimal point, or calling a method on it, e.g. 42 .toString() $operators['.'] = '(?<![0-9]\s)\.'; // don't confuse = with other assignment shortcuts (e.g. +=) $chars = preg_quote( '+-*\=<>%&|', $delimiter ); $operators['='] = '(?<![' . $chars . '])\='; return $operators; } /** * We'll strip whitespace around certain keywords with regular expressions. * This will prepare the given array by escaping all characters. * * @param string[] $keywords * @param string $delimiter * * @return string[] */ protected function getKeywordsForRegex( array $keywords, $delimiter = '/' ) { // escape keywords for use in regex $delimiter = array_fill( 0, count( $keywords ), $delimiter ); $escaped = array_map( 'preg_quote', $keywords, $delimiter ); // add word boundaries array_walk( $keywords, function ( $value ) { return '\b' . $value . '\b'; } ); $keywords = array_combine( $keywords, $escaped ); return $keywords; } /** * Replaces all occurrences of array['key'] by array.key. * * @param string $content * * @return string */ protected function propertyNotation( $content ) { // PHP only supports $this inside anonymous functions since 5.4 $minifier = $this; $keywords = $this->keywordsReserved; $callback = function ( $match ) use ( $minifier, $keywords ) { $property = trim( $minifier->extracted[ $match[1] ], '\'"' ); /* * Check if the property is a reserved keyword. In this context (as * property of an object literal/array) it shouldn't matter, but IE8 * freaks out with "Expected identifier". */ if ( in_array( $property, $keywords ) ) { return $match[0]; } /* * See if the property is in a variable-like format (e.g. * array['key-here'] can't be replaced by array.key-here since '-' * is not a valid character there. */ if ( ! preg_match( '/^' . $minifier::REGEX_VARIABLE . '$/u', $property ) ) { return $match[0]; } return '.' . $property; }; /* * Figure out if previous character is a variable name (of the array * we want to use property notation on) - this is to make sure * standalone ['value'] arrays aren't confused for keys-of-an-array. * We can (and only have to) check the last character, because PHP's * regex implementation doesn't allow unfixed-length look-behind * assertions. */ preg_match( '/(\[[^\]]+\])[^\]]*$/', static::REGEX_VARIABLE, $previousChar ); $previousChar = $previousChar[1]; /* * Make sure word preceding the ['value'] is not a keyword, e.g. * return['x']. Because -again- PHP's regex implementation doesn't allow * unfixed-length look-behind assertions, I'm just going to do a lot of * separate look-behind assertions, one for each keyword. */ $keywords = $this->getKeywordsForRegex( $keywords ); $keywords = '(?<!' . implode( ')(?<!', $keywords ) . ')'; return preg_replace_callback( '/(?<=' . $previousChar . '|\])' . $keywords . '\[\s*(([\'"])[0-9]+\\2)\s*\]/u', $callback, $content ); } /** * Replaces true & false by !0 and !1. * * @param string $content * * @return string */ protected function shortenBools( $content ) { /* * 'true' or 'false' could be used as property names (which may be * followed by whitespace) - we must not replace those! * Since PHP doesn't allow variable-length (to account for the * whitespace) lookbehind assertions, I need to capture the leading * character and check if it's a `.` */ $callback = function ( $match ) { if ( trim( $match[1] ) === '.' ) { return $match[0]; } return $match[1] . ( $match[2] === 'true' ? '!0' : '!1' ); }; $content = preg_replace_callback( '/(^|.\s*)\b(true|false)\b(?!:)/', $callback, $content ); // for(;;) is exactly the same as while(true), but shorter :) $content = preg_replace( '/\bwhile\(!0\){/', 'for(;;){', $content ); // now make sure we didn't turn any do ... while(true) into do ... for(;;) preg_match_all( '/\bdo\b/', $content, $dos, PREG_OFFSET_CAPTURE | PREG_SET_ORDER ); // go backward to make sure positional offsets aren't altered when $content changes $dos = array_reverse( $dos ); foreach ( $dos as $do ) { $offsetDo = $do[0][1]; // find all `while` (now `for`) following `do`: one of those must be // associated with the `do` and be turned back into `while` preg_match_all( '/\bfor\(;;\)/', $content, $whiles, PREG_OFFSET_CAPTURE | PREG_SET_ORDER, $offsetDo ); foreach ( $whiles as $while ) { $offsetWhile = $while[0][1]; $open = substr_count( $content, '{', $offsetDo, $offsetWhile - $offsetDo ); $close = substr_count( $content, '}', $offsetDo, $offsetWhile - $offsetDo ); if ( $open === $close ) { // only restore `while` if amount of `{` and `}` are the same; // otherwise, that `for` isn't associated with this `do` $content = substr_replace( $content, 'while(!0)', $offsetWhile, strlen( 'for(;;)' ) ); break; } } } return $content; } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������lib/css_js_min/minify/exception.cls.php�������������������������������������������������������������0000644�����������������00000000720�15207571336�0014233 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // phpcs:ignoreFile /** * exception.cls.php - modified PHP implementation of Matthias Mullie's Exceptions Classes. * * @author Matthias Mullie <minify@mullie.eu> */ namespace LiteSpeed\Lib\CSS_JS_MIN\Minify\Exception; defined( 'WPINC' ) || exit; abstract class Exception extends \Exception { } abstract class BasicException extends Exception { } class FileImportException extends BasicException { } class IOException extends BasicException { } ������������������������������������������������lib/css_js_min/minify/data/js/keywords_before.txt���������������������������������������������������0000644�����������������00000000247�15207571336�0016227 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������do in let new var case else enum void with class const yield delete export import public static typeof extends package private function protected implements instanceof���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������lib/css_js_min/minify/data/js/operators_before.txt��������������������������������������������������0000644�����������������00000000163�15207571336�0016373 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������+ - * / % = += -= *= /= %= <<= >>= >>>= &= ^= |= & | ^ ~ << >> >>> == === != !== > < >= <= && || ! . [ ? : , ; ( { �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������lib/css_js_min/minify/data/js/keywords_reserved.txt�������������������������������������������������0000644�����������������00000000636�15207571336�0016606 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������do if in for let new try var case else enum eval null this true void with break catch class const false super throw while yield delete export import public return static switch typeof default extends finally package private continue debugger function arguments interface protected implements instanceof abstract boolean byte char double final float goto int long native short synchronized throws transient volatile��������������������������������������������������������������������������������������������������lib/css_js_min/minify/data/js/operators_after.txt���������������������������������������������������0000644�����������������00000000162�15207571336�0016231 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������+ - * / % = += -= *= /= %= <<= >>= >>>= &= ^= |= & | ^ << >> >>> == === != !== > < >= <= && || . [ ] ? : , ; ( ) }��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������lib/css_js_min/minify/data/js/keywords_after.txt����������������������������������������������������0000644�����������������00000000071�15207571336�0016061 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������in public extends private protected implements instanceof�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������lib/css_js_min/minify/data/js/operators.txt���������������������������������������������������������0000644�����������������00000000170�15207571336�0015047 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������+ - * / % = += -= *= /= %= <<= >>= >>>= &= ^= |= & | ^ ~ << >> >>> == === != !== > < >= <= && || ! . [ ] ? : , ; ( ) { }��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������lib/css_js_min/minify/minify.cls.php����������������������������������������������������������������0000644�����������������00000035626�15207571336�0013545 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // phpcs:ignoreFile /** * modified PHP implementation of Matthias Mullie's Abstract minifier class. * * @author Matthias Mullie <minify@mullie.eu> * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved * @license MIT License */ namespace LiteSpeed\Lib\CSS_JS_MIN\Minify; use LiteSpeed\Lib\CSS_JS_MIN\Minify\Exception\IOException; defined( 'WPINC' ) || exit; abstract class Minify { /** * The data to be minified. * * @var string[] */ protected $data = array(); /** * Array of patterns to match. * * @var string[] */ protected $patterns = array(); /** * This array will hold content of strings and regular expressions that have * been extracted from the JS source code, so we can reliably match "code", * without having to worry about potential "code-like" characters inside. * * @internal * * @var string[] */ public $extracted = array(); /** * Init the minify class - optionally, code may be passed along already. */ public function __construct( /* $data = null, ... */ ) { // it's possible to add the source through the constructor as well ;) if ( func_num_args() ) { call_user_func_array( array( $this, 'add' ), func_get_args() ); } } /** * Add a file or straight-up code to be minified. * * @param string|string[] $data * * @return static */ public function add( $data /* $data = null, ... */ ) { // bogus "usage" of parameter $data: scrutinizer warns this variable is // not used (we're using func_get_args instead to support overloading), // but it still needs to be defined because it makes no sense to have // this function without argument :) $args = array( $data ) + func_get_args(); // this method can be overloaded foreach ( $args as $data ) { if ( is_array( $data ) ) { call_user_func_array( array( $this, 'add' ), $data ); continue; } // redefine var $data = (string) $data; // load data $value = $this->load( $data ); $key = ( $data != $value ) ? $data : count( $this->data ); // replace CR linefeeds etc. // @see https://github.com/matthiasmullie/minify/pull/139 $value = str_replace( array( "\r\n", "\r" ), "\n", $value ); // store data $this->data[ $key ] = $value; } return $this; } /** * Add a file to be minified. * * @param string|string[] $data * * @return static * * @throws IOException */ public function addFile( $data /* $data = null, ... */ ) { // bogus "usage" of parameter $data: scrutinizer warns this variable is // not used (we're using func_get_args instead to support overloading), // but it still needs to be defined because it makes no sense to have // this function without argument :) $args = array( $data ) + func_get_args(); // this method can be overloaded foreach ( $args as $path ) { if ( is_array( $path ) ) { call_user_func_array( array( $this, 'addFile' ), $path ); continue; } // redefine var $path = (string) $path; // check if we can read the file if ( ! $this->canImportFile( $path ) ) { throw new IOException( 'The file "' . $path . '" could not be opened for reading. Check if PHP has enough permissions.' ); } $this->add( $path ); } return $this; } /** * Minify the data & (optionally) saves it to a file. * * @param string[optional] $path Path to write the data to * * @return string The minified data */ public function minify( $path = null ) { $content = $this->execute( $path ); // save to path if ( $path !== null ) { $this->save( $content, $path ); } return $content; } /** * Minify & gzip the data & (optionally) saves it to a file. * * @param string[optional] $path Path to write the data to * @param int[optional] $level Compression level, from 0 to 9 * * @return string The minified & gzipped data */ public function gzip( $path = null, $level = 9 ) { $content = $this->execute( $path ); $content = gzencode( $content, $level, FORCE_GZIP ); // save to path if ( $path !== null ) { $this->save( $content, $path ); } return $content; } /** * Minify the data. * * @param string[optional] $path Path to write the data to * * @return string The minified data */ abstract public function execute( $path = null ); /** * Load data. * * @param string $data Either a path to a file or the content itself * * @return string */ protected function load( $data ) { // check if the data is a file if ( $this->canImportFile( $data ) ) { $data = file_get_contents( $data ); // strip BOM, if any if ( substr( $data, 0, 3 ) == "\xef\xbb\xbf" ) { $data = substr( $data, 3 ); } } return $data; } /** * Save to file. * * @param string $content The minified data * @param string $path The path to save the minified data to * * @throws IOException */ protected function save( $content, $path ) { $handler = $this->openFileForWriting( $path ); $this->writeToFile( $handler, $content ); @fclose( $handler ); } /** * Register a pattern to execute against the source content. * * If $replacement is a string, it must be plain text. Placeholders like $1 or \2 don't work. * If you need that functionality, use a callback instead. * * @param string $pattern PCRE pattern * @param string|callable $replacement Replacement value for matched pattern */ protected function registerPattern( $pattern, $replacement = '' ) { // study the pattern, we'll execute it more than once $pattern .= 'S'; $this->patterns[] = array( $pattern, $replacement ); } /** * Both JS and CSS use the same form of multi-line comment, so putting the common code here. */ protected function stripMultilineComments() { // First extract comments we want to keep, so they can be restored later // PHP only supports $this inside anonymous functions since 5.4 $minifier = $this; $callback = function ( $match ) use ( $minifier ) { $count = count( $minifier->extracted ); $placeholder = '/*' . $count . '*/'; $minifier->extracted[ $placeholder ] = $match[0]; return $placeholder; }; $this->registerPattern( '/ # optional newline \n? # start comment \/\* # comment content (?: # either starts with an ! ! | # or, after some number of characters which do not end the comment (?:(?!\*\/).)*? # there is either a @license or @preserve tag @(?:license|preserve) ) # then match to the end of the comment .*?\*\/\n? /ixs', $callback ); // Then strip all other comments $this->registerPattern( '/\/\*.*?\*\//s', '' ); } /** * We can't "just" run some regular expressions against JavaScript: it's a * complex language. E.g. having an occurrence of // xyz would be a comment, * unless it's used within a string. Of you could have something that looks * like a 'string', but inside a comment. * The only way to accurately replace these pieces is to traverse the JS one * character at a time and try to find whatever starts first. * * @param string $content The content to replace patterns in * * @return string The (manipulated) content */ protected function replace( $content ) { $contentLength = strlen( $content ); $output = ''; $processedOffset = 0; $positions = array_fill( 0, count( $this->patterns ), -1 ); $matches = array(); while ( $processedOffset < $contentLength ) { // find first match for all patterns foreach ( $this->patterns as $i => $pattern ) { list($pattern, $replacement) = $pattern; // we can safely ignore patterns for positions we've unset earlier, // because we know these won't show up anymore if ( array_key_exists( $i, $positions ) == false ) { continue; } // no need to re-run matches that are still in the part of the // content that hasn't been processed if ( $positions[ $i ] >= $processedOffset ) { continue; } $match = null; if ( preg_match( $pattern, $content, $match, PREG_OFFSET_CAPTURE, $processedOffset ) ) { $matches[ $i ] = $match; // we'll store the match position as well; that way, we // don't have to redo all preg_matches after changing only // the first (we'll still know where those others are) $positions[ $i ] = $match[0][1]; } else { // if the pattern couldn't be matched, there's no point in // executing it again in later runs on this same content; // ignore this one until we reach end of content unset( $matches[ $i ], $positions[ $i ] ); } } // no more matches to find: everything's been processed, break out if ( ! $matches ) { // output the remaining content $output .= substr( $content, $processedOffset ); break; } // see which of the patterns actually found the first thing (we'll // only want to execute that one, since we're unsure if what the // other found was not inside what the first found) $matchOffset = min( $positions ); $firstPattern = array_search( $matchOffset, $positions ); $match = $matches[ $firstPattern ]; // execute the pattern that matches earliest in the content string list(, $replacement) = $this->patterns[ $firstPattern ]; // add the part of the input between $processedOffset and the first match; // that content wasn't matched by anything $output .= substr( $content, $processedOffset, $matchOffset - $processedOffset ); // add the replacement for the match $output .= $this->executeReplacement( $replacement, $match ); // advance $processedOffset past the match $processedOffset = $matchOffset + strlen( $match[0][0] ); } return $output; } /** * If $replacement is a callback, execute it, passing in the match data. * If it's a string, just pass it through. * * @param string|callable $replacement Replacement value * @param array $match Match data, in PREG_OFFSET_CAPTURE form * * @return string */ protected function executeReplacement( $replacement, $match ) { if ( ! is_callable( $replacement ) ) { return $replacement; } // convert $match from the PREG_OFFSET_CAPTURE form to the form the callback expects foreach ( $match as &$matchItem ) { $matchItem = $matchItem[0]; } return $replacement( $match ); } /** * Strings are a pattern we need to match, in order to ignore potential * code-like content inside them, but we just want all of the string * content to remain untouched. * * This method will replace all string content with simple STRING# * placeholder text, so we've rid all strings from characters that may be * misinterpreted. Original string content will be saved in $this->extracted * and after doing all other minifying, we can restore the original content * via restoreStrings(). * * @param string[optional] $chars * @param string[optional] $placeholderPrefix */ protected function extractStrings( $chars = '\'"', $placeholderPrefix = '' ) { // PHP only supports $this inside anonymous functions since 5.4 $minifier = $this; $callback = function ( $match ) use ( $minifier, $placeholderPrefix ) { // check the second index here, because the first always contains a quote if ( $match[2] === '' ) { /* * Empty strings need no placeholder; they can't be confused for * anything else anyway. * But we still needed to match them, for the extraction routine * to skip over this particular string. */ return $match[0]; } $count = count( $minifier->extracted ); $placeholder = $match[1] . $placeholderPrefix . $count . $match[1]; $minifier->extracted[ $placeholder ] = $match[1] . $match[2] . $match[1]; return $placeholder; }; /* * The \\ messiness explained: * * Don't count ' or " as end-of-string if it's escaped (has backslash * in front of it) * * Unless... that backslash itself is escaped (another leading slash), * in which case it's no longer escaping the ' or " * * So there can be either no backslash, or an even number * * multiply all of that times 4, to account for the escaping that has * to be done to pass the backslash into the PHP string without it being * considered as escape-char (times 2) and to get it in the regex, * escaped (times 2) */ $this->registerPattern( '/([' . $chars . '])(.*?(?<!\\\\)(\\\\\\\\)*+)\\1/s', $callback ); } /** * This method will restore all extracted data (strings, regexes) that were * replaced with placeholder text in extract*(). The original content was * saved in $this->extracted. * * @param string $content * * @return string */ protected function restoreExtractedData( $content ) { if ( ! $this->extracted ) { // nothing was extracted, nothing to restore return $content; } $content = strtr( $content, $this->extracted ); $this->extracted = array(); return $content; } /** * Check if the path is a regular file and can be read. * * @param string $path * * @return bool */ protected function canImportFile( $path ) { $parsed = parse_url( $path ); if ( // file is elsewhere isset( $parsed['host'] ) // file responds to queries (may change, or need to bypass cache) || isset( $parsed['query'] ) ) { return false; } try { return strlen( $path ) < PHP_MAXPATHLEN && @is_file( $path ) && is_readable( $path ); } // catch openbasedir exceptions which are not caught by @ on is_file() catch ( \Exception $e ) { return false; } } /** * Attempts to open file specified by $path for writing. * * @param string $path The path to the file * * @return resource Specifier for the target file * * @throws IOException */ protected function openFileForWriting( $path ) { if ( $path === '' || ( $handler = @fopen( $path, 'w' ) ) === false ) { throw new IOException( 'The file "' . $path . '" could not be opened for writing. Check if PHP has enough permissions.' ); } return $handler; } /** * Attempts to write $content to the file specified by $handler. $path is used for printing exceptions. * * @param resource $handler The resource to write to * @param string $content The content to write * @param string $path The path to the file (for exception printing only) * * @throws IOException */ protected function writeToFile( $handler, $content, $path = '' ) { if ( ! is_resource( $handler ) || ( $result = @fwrite( $handler, $content ) ) === false || ( $result < strlen( $content ) ) ) { throw new IOException( 'The file "' . $path . '" could not be written to. Check your disk space and file permissions.' ); } } protected static function str_replace_first( $search, $replace, $subject ) { $pos = strpos( $subject, $search ); if ( $pos !== false ) { return substr_replace( $subject, $replace, $pos, strlen( $search ) ); } return $subject; } } ����������������������������������������������������������������������������������������������������������lib/object-cache.php��������������������������������������������������������������������������������0000644�����������������00000003514�15207571336�0010346 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // phpcs:ignoreFile /** * Plugin Name: LiteSpeed Cache - Object Cache (Drop-in) * Plugin URI: https://www.litespeedtech.com/products/cache-plugins/wordpress-acceleration * Description: High-performance page caching and site optimization from LiteSpeed. * Author: LiteSpeed Technologies * Author URI: https://www.litespeedtech.com */ defined( 'WPINC' ) || exit; /** * LiteSpeed Object Cache * * @since 1.8 */ ! defined( 'LSCWP_OBJECT_CACHE' ) && define( 'LSCWP_OBJECT_CACHE', true ); // Initialize const `LSCWP_DIR` and locate LSCWP plugin folder $lscwp_dir = ( defined( 'WP_PLUGIN_DIR' ) ? WP_PLUGIN_DIR : WP_CONTENT_DIR . '/plugins' ) . '/litespeed-cache/'; // Use plugin as higher priority than MU plugin if ( ! file_exists( $lscwp_dir . 'litespeed-cache.php' ) ) { // Check if is mu plugin or not $lscwp_dir = ( defined( 'WPMU_PLUGIN_DIR' ) ? WPMU_PLUGIN_DIR : WP_CONTENT_DIR . '/mu-plugins' ) . '/litespeed-cache/'; if ( ! file_exists( $lscwp_dir . 'litespeed-cache.php' ) ) { $lscwp_dir = ''; } } $data_file = WP_CONTENT_DIR . '/.litespeed_conf.dat'; $lib_file = $lscwp_dir . 'src/object.lib.php'; // Can't find LSCWP location, terminate object cache process if ( ! $lscwp_dir || ! file_exists( $data_file ) || ( ! file_exists( $lib_file ) ) ) { if ( ! is_admin() ) { // Bypass object cache for frontend require_once ABSPATH . WPINC . '/cache.php'; } else { $err = 'Can NOT find LSCWP path for object cache initialization in ' . __FILE__; error_log( $err ); add_action( is_network_admin() ? 'network_admin_notices' : 'admin_notices', function () use ( &$err ) { echo $err; } ); } } elseif ( ! LSCWP_OBJECT_CACHE ) { // Disable cache wp_using_ext_object_cache( false ); } // Init object cache & LSCWP elseif ( file_exists( $lib_file ) ) { require_once $lib_file; } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������lib/urirewriter.cls.php�����������������������������������������������������������������������������0000644�����������������00000021745�15207571336�0011210 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // phpcs:ignoreFile /** * Rewrite file-relative URIs as root-relative in CSS files * * @package Minify * @author Stephen Clay <steve@mrclay.org> */ namespace LiteSpeed\Lib; defined( 'WPINC' ) || exit; class UriRewriter { /** * rewrite() and rewriteRelative() append debugging information here * * @var string */ public static $debugText = ''; /** * In CSS content, rewrite file relative URIs as root relative * * @param string $css * * @param string $currentDir The directory of the current CSS file. * * @param string $docRoot The document root of the web site in which * the CSS file resides (default = $_SERVER['DOCUMENT_ROOT']). * * @param array $symlinks (default = array()) If the CSS file is stored in * a symlink-ed directory, provide an array of link paths to * target paths, where the link paths are within the document root. Because * paths need to be normalized for this to work, use "//" to substitute * the doc root in the link paths (the array keys). E.g.: * <code> * array('//symlink' => '/real/target/path') // unix * array('//static' => 'D:\\staticStorage') // Windows * </code> * * @return string */ public static function rewrite( $css, $currentDir, $docRoot = null, $symlinks = array() ) { self::$_docRoot = self::_realpath( $docRoot ? $docRoot : $_SERVER['DOCUMENT_ROOT'] ); self::$_currentDir = self::_realpath( $currentDir ); self::$_symlinks = array(); // normalize symlinks in order to map to link foreach ( $symlinks as $link => $target ) { $link = ( $link === '//' ) ? self::$_docRoot : str_replace( '//', self::$_docRoot . '/', $link ); $link = strtr( $link, '/', DIRECTORY_SEPARATOR ); self::$_symlinks[ $link ] = self::_realpath( $target ); } self::$debugText .= 'docRoot : ' . self::$_docRoot . "\n" . 'currentDir : ' . self::$_currentDir . "\n"; if ( self::$_symlinks ) { self::$debugText .= 'symlinks : ' . var_export( self::$_symlinks, 1 ) . "\n"; } self::$debugText .= "\n"; $css = self::_trimUrls( $css ); $css = self::_owlifySvgPaths( $css ); // rewrite $pattern = '/@import\\s+([\'"])(.*?)[\'"]/'; $css = preg_replace_callback( $pattern, __CLASS__ . '::_processUriCB', $css ); $pattern = '/url\\(\\s*([\'"](.*?)[\'"]|[^\\)\\s]+)\\s*\\)/'; $css = preg_replace_callback( $pattern, __CLASS__ . '::_processUriCB', $css ); $css = self::_unOwlify( $css ); return $css; } /** * In CSS content, prepend a path to relative URIs * * @param string $css * * @param string $path The path to prepend. * * @return string */ public static function prepend( $css, $path ) { self::$_prependPath = $path; $css = self::_trimUrls( $css ); $css = self::_owlifySvgPaths( $css ); // append $pattern = '/@import\\s+([\'"])(.*?)[\'"]/'; $css = preg_replace_callback( $pattern, __CLASS__ . '::_processUriCB', $css ); $pattern = '/url\\(\\s*([\'"](.*?)[\'"]|[^\\)\\s]+)\\s*\\)/'; $css = preg_replace_callback( $pattern, __CLASS__ . '::_processUriCB', $css ); $css = self::_unOwlify( $css ); self::$_prependPath = null; return $css; } /** * Get a root relative URI from a file relative URI * * <code> * UriRewriter::rewriteRelative( * '../img/hello.gif' * , '/home/user/www/css' // path of CSS file * , '/home/user/www' // doc root * ); * // returns '/img/hello.gif' * * // example where static files are stored in a symlinked directory * UriRewriter::rewriteRelative( * 'hello.gif' * , '/var/staticFiles/theme' * , '/home/user/www' * , array('/home/user/www/static' => '/var/staticFiles') * ); * // returns '/static/theme/hello.gif' * </code> * * @param string $uri file relative URI * * @param string $realCurrentDir realpath of the current file's directory. * * @param string $realDocRoot realpath of the site document root. * * @param array $symlinks (default = array()) If the file is stored in * a symlink-ed directory, provide an array of link paths to * real target paths, where the link paths "appear" to be within the document * root. E.g.: * <code> * array('/home/foo/www/not/real/path' => '/real/target/path') // unix * array('C:\\htdocs\\not\\real' => 'D:\\real\\target\\path') // Windows * </code> * * @return string */ public static function rewriteRelative( $uri, $realCurrentDir, $realDocRoot, $symlinks = array() ) { // prepend path with current dir separator (OS-independent) $path = strtr( $realCurrentDir, '/', DIRECTORY_SEPARATOR ); $path .= DIRECTORY_SEPARATOR . strtr( $uri, '/', DIRECTORY_SEPARATOR ); self::$debugText .= "file-relative URI : {$uri}\n" . "path prepended : {$path}\n"; // "unresolve" a symlink back to doc root foreach ( $symlinks as $link => $target ) { if ( 0 === strpos( $path, $target ) ) { // replace $target with $link $path = $link . substr( $path, strlen( $target ) ); self::$debugText .= "symlink unresolved : {$path}\n"; break; } } // strip doc root $path = substr( $path, strlen( $realDocRoot ) ); self::$debugText .= "docroot stripped : {$path}\n"; // fix to root-relative URI $uri = strtr( $path, '/\\', '//' ); $uri = self::removeDots( $uri ); self::$debugText .= "traversals removed : {$uri}\n\n"; return $uri; } /** * Remove instances of "./" and "../" where possible from a root-relative URI * * @param string $uri * * @return string */ public static function removeDots( $uri ) { $uri = str_replace( '/./', '/', $uri ); // inspired by patch from Oleg Cherniy do { $uri = preg_replace( '@/[^/]+/\\.\\./@', '/', $uri, 1, $changed ); } while ( $changed ); return $uri; } /** * Get realpath with any trailing slash removed. If realpath() fails, * just remove the trailing slash. * * @param string $path * * @return mixed path with no trailing slash */ protected static function _realpath( $path ) { $realPath = realpath( $path ); if ( $realPath !== false ) { $path = $realPath; } return rtrim( $path, '/\\' ); } /** * Directory of this stylesheet * * @var string */ private static $_currentDir = ''; /** * DOC_ROOT * * @var string */ private static $_docRoot = ''; /** * directory replacements to map symlink targets back to their * source (within the document root) E.g. '/var/www/symlink' => '/var/realpath' * * @var array */ private static $_symlinks = array(); /** * Path to prepend * * @var string */ private static $_prependPath = null; /** * @param string $css * * @return string */ private static function _trimUrls( $css ) { $pattern = '/ url\\( # url( \\s* ([^\\)]+?) # 1 = URI (assuming does not contain ")") \\s* \\) # ) /x'; return preg_replace( $pattern, 'url($1)', $css ); } /** * @param array $m * * @return string */ private static function _processUriCB( $m ) { // $m matched either '/@import\\s+([\'"])(.*?)[\'"]/' or '/url\\(\\s*([^\\)\\s]+)\\s*\\)/' $isImport = ( $m[0][0] === '@' ); // determine URI and the quote character (if any) if ( $isImport ) { $quoteChar = $m[1]; $uri = $m[2]; } else { // $m[1] is either quoted or not $quoteChar = ( $m[1][0] === "'" || $m[1][0] === '"' ) ? $m[1][0] : ''; $uri = ( $quoteChar === '' ) ? $m[1] : substr( $m[1], 1, strlen( $m[1] ) - 2 ); } if ( $uri === '' ) { return $m[0]; } // if not anchor id, not root/scheme relative, and not starts with scheme if ( ! preg_match( '~^(#|/|[a-z]+\:)~', $uri ) ) { // URI is file-relative: rewrite depending on options if ( self::$_prependPath === null ) { $uri = self::rewriteRelative( $uri, self::$_currentDir, self::$_docRoot, self::$_symlinks ); } else { $uri = self::$_prependPath . $uri; if ( $uri[0] === '/' ) { $root = ''; $rootRelative = $uri; $uri = $root . self::removeDots( $rootRelative ); } elseif ( preg_match( '@^((https?\:)?//([^/]+))/@', $uri, $m ) && ( false !== strpos( $m[3], '.' ) ) ) { $root = $m[1]; $rootRelative = substr( $uri, strlen( $root ) ); $uri = $root . self::removeDots( $rootRelative ); } } } if ( $isImport ) { return "@import {$quoteChar}{$uri}{$quoteChar}"; } else { return "url({$quoteChar}{$uri}{$quoteChar})"; } } /** * Mungs some inline SVG URL declarations so they won't be touched * * @link https://github.com/mrclay/minify/issues/517 * @see _unOwlify * * @param string $css * @return string */ private static function _owlifySvgPaths( $css ) { $pattern = '~\b((?:clip-path|mask|-webkit-mask)\s*\:\s*)url(\(\s*#\w+\s*\))~'; return preg_replace( $pattern, '$1owl$2', $css ); } /** * Undo work of _owlify * * @see _owlifySvgPaths * * @param string $css * @return string */ private static function _unOwlify( $css ) { $pattern = '~\b((?:clip-path|mask|-webkit-mask)\s*\:\s*)owl~'; return preg_replace( $pattern, '$1url', $css ); } } ���������������������������lib/html-min.cls.php��������������������������������������������������������������������������������0000644�����������������00000017744�15207571336�0010356 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // phpcs:ignoreFile /** * Compress HTML * * This is a heavy regex-based removal of whitespace, unnecessary comments and * tokens. IE conditional comments are preserved. There are also options to have * STYLE and SCRIPT blocks compressed by callback functions. * * A test suite is available. * * @package Minify * @author Stephen Clay <steve@mrclay.org> */ namespace LiteSpeed\Lib; defined( 'WPINC' ) || exit; class HTML_MIN { /** * @var string */ protected $_html = ''; /** * @var boolean */ protected $_jsCleanComments = true; protected $_skipComments = array(); /** * "Minify" an HTML page * * @param string $html * * @param array $options * * 'cssMinifier' : (optional) callback function to process content of STYLE * elements. * * 'jsMinifier' : (optional) callback function to process content of SCRIPT * elements. Note: the type attribute is ignored. * * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If * unset, minify will sniff for an XHTML doctype. * * @return string */ public static function minify( $html, $options = array() ) { $min = new self( $html, $options ); return $min->process(); } /** * Create a minifier object * * @param string $html * * @param array $options * * 'cssMinifier' : (optional) callback function to process content of STYLE * elements. * * 'jsMinifier' : (optional) callback function to process content of SCRIPT * elements. Note: the type attribute is ignored. * * 'jsCleanComments' : (optional) whether to remove HTML comments beginning and end of script block * * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If * unset, minify will sniff for an XHTML doctype. */ public function __construct( $html, $options = array() ) { $this->_html = str_replace( "\r\n", "\n", trim( $html ) ); if ( isset( $options['xhtml'] ) ) { $this->_isXhtml = (bool) $options['xhtml']; } if ( isset( $options['cssMinifier'] ) ) { $this->_cssMinifier = $options['cssMinifier']; } if ( isset( $options['jsMinifier'] ) ) { $this->_jsMinifier = $options['jsMinifier']; } if ( isset( $options['jsCleanComments'] ) ) { $this->_jsCleanComments = (bool) $options['jsCleanComments']; } if ( isset( $options['skipComments'] ) ) { $this->_skipComments = $options['skipComments']; } } /** * Minify the markeup given in the constructor * * @return string */ public function process() { if ( $this->_isXhtml === null ) { $this->_isXhtml = ( false !== strpos( $this->_html, '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML' ) ); } $this->_replacementHash = 'MINIFYHTML' . md5( $_SERVER['REQUEST_TIME'] ); $this->_placeholders = array(); // replace SCRIPTs (and minify) with placeholders $this->_html = preg_replace_callback( '/(\\s*)<script(\\b[^>]*?>)([\\s\\S]*?)<\\/script>(\\s*)/i', array( $this, '_removeScriptCB' ), $this->_html ); // replace STYLEs (and minify) with placeholders $this->_html = preg_replace_callback( '/\\s*<style(\\b[^>]*>)([\\s\\S]*?)<\\/style>\\s*/i', array( $this, '_removeStyleCB' ), $this->_html ); // remove HTML comments (not containing IE conditional comments). $this->_html = preg_replace_callback( '/<!--([\\s\\S]*?)-->/', array( $this, '_commentCB' ), $this->_html ); // replace PREs with placeholders $this->_html = preg_replace_callback( '/\\s*<pre(\\b[^>]*?>[\\s\\S]*?<\\/pre>)\\s*/i', array( $this, '_removePreCB' ), $this->_html ); // replace TEXTAREAs with placeholders $this->_html = preg_replace_callback( '/\\s*<textarea(\\b[^>]*?>[\\s\\S]*?<\\/textarea>)\\s*/i', array( $this, '_removeTextareaCB' ), $this->_html ); // trim each line. // @todo take into account attribute values that span multiple lines. $this->_html = preg_replace( '/^\\s+|\\s+$/m', '', $this->_html ); // remove ws around block/undisplayed elements $this->_html = preg_replace( '/\\s+(<\\/?(?:area|article|aside|base(?:font)?|blockquote|body' . '|canvas|caption|center|col(?:group)?|dd|dir|div|dl|dt|fieldset|figcaption|figure|footer|form' . '|frame(?:set)?|h[1-6]|head|header|hgroup|hr|html|legend|li|link|main|map|menu|meta|nav' . '|ol|opt(?:group|ion)|output|p|param|section|t(?:able|body|head|d|h||r|foot|itle)' . '|ul|video)\\b[^>]*>)/i', '$1', $this->_html ); // remove ws outside of all elements $this->_html = preg_replace( '/>(\\s(?:\\s*))?([^<]+)(\\s(?:\s*))?</', '>$1$2$3<', $this->_html ); // use newlines before 1st attribute in open tags (to limit line lengths) // $this->_html = preg_replace('/(<[a-z\\-]+)\\s+([^>]+>)/i', "$1\n$2", $this->_html); // fill placeholders $this->_html = str_replace( array_keys( $this->_placeholders ), array_values( $this->_placeholders ), $this->_html ); // issue 229: multi-pass to catch scripts that didn't get replaced in textareas $this->_html = str_replace( array_keys( $this->_placeholders ), array_values( $this->_placeholders ), $this->_html ); return $this->_html; } /** * From LSCWP 6.2: Changed the function to test for special comments that will be skipped. See: https://github.com/litespeedtech/lscache_wp/pull/622 */ protected function _commentCB( $m ) { // If is IE conditional comment return it. if ( 0 === strpos( $m[1], '[' ) || false !== strpos( $m[1], '<![' ) ) { return $m[0]; } // Check if comment text is present in Page Optimization -> HTML Settings -> HTML Keep comments if ( count( $this->_skipComments ) > 0 ) { foreach ( $this->_skipComments as $comment ) { if ( $comment && strpos( $m[1], $comment ) !== false ) { return $m[0]; } } } // Comment can be removed. return ''; } protected function _reservePlace( $content ) { $placeholder = '%' . $this->_replacementHash . count( $this->_placeholders ) . '%'; $this->_placeholders[ $placeholder ] = $content; return $placeholder; } protected $_isXhtml = null; protected $_replacementHash = null; protected $_placeholders = array(); protected $_cssMinifier = null; protected $_jsMinifier = null; protected function _removePreCB( $m ) { return $this->_reservePlace( "<pre{$m[1]}" ); } protected function _removeTextareaCB( $m ) { return $this->_reservePlace( "<textarea{$m[1]}" ); } protected function _removeStyleCB( $m ) { $openStyle = "<style{$m[1]}"; $css = $m[2]; // remove HTML comments $css = preg_replace( '/(?:^\\s*<!--|-->\\s*$)/', '', $css ); // remove CDATA section markers $css = $this->_removeCdata( $css ); // minify $minifier = $this->_cssMinifier ? $this->_cssMinifier : 'trim'; $css = call_user_func( $minifier, $css ); return $this->_reservePlace( $this->_needsCdata( $css ) ? "{$openStyle}/*<![CDATA[*/{$css}/*]]>*/</style>" : "{$openStyle}{$css}</style>" ); } protected function _removeScriptCB( $m ) { $openScript = "<script{$m[2]}"; $js = $m[3]; // whitespace surrounding? preserve at least one space $ws1 = ( $m[1] === '' ) ? '' : ' '; $ws2 = ( $m[4] === '' ) ? '' : ' '; // remove HTML comments (and ending "//" if present) if ( $this->_jsCleanComments ) { $js = preg_replace( '/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $js ); } // remove CDATA section markers $js = $this->_removeCdata( $js ); // minify /** * Added 2nd param by LiteSpeed * * @since 2.2.3 */ if ( $this->_jsMinifier ) { $js = call_user_func( $this->_jsMinifier, $js, trim( $m[2] ) ); } else { $js = trim( $js ); } return $this->_reservePlace( $this->_needsCdata( $js ) ? "{$ws1}{$openScript}/*<![CDATA[*/{$js}/*]]>*/</script>{$ws2}" : "{$ws1}{$openScript}{$js}</script>{$ws2}" ); } protected function _removeCdata( $str ) { return ( false !== strpos( $str, '<![CDATA[' ) ) ? str_replace( array( '<![CDATA[', ']]>' ), '', $str ) : $str; } protected function _needsCdata( $str ) { return ( $this->_isXhtml && preg_match( '/(?:[<&]|\\-\\-|\\]\\]>)/', $str ) ); } } ����������������������������lib/php-compatibility.func.php����������������������������������������������������������������������0000644�����������������00000012563�15207571336�0012433 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?php // phpcs:ignoreFile /** * LiteSpeed PHP compatibility functions for lower PHP version * * @since 1.1.3 * @package LiteSpeed * @subpackage LiteSpeed_Cache/lib */ defined( 'WPINC' ) || exit; /** * http_build_url() compatibility */ if ( ! function_exists( 'http_build_url' ) ) { if ( ! defined( 'HTTP_URL_REPLACE' ) ) { define( 'HTTP_URL_REPLACE', 1 ); // Replace every part of the first URL when there's one of the second URL } if ( ! defined( 'HTTP_URL_JOIN_PATH' ) ) { define( 'HTTP_URL_JOIN_PATH', 2 ); // Join relative paths } if ( ! defined( 'HTTP_URL_JOIN_QUERY' ) ) { define( 'HTTP_URL_JOIN_QUERY', 4 ); // Join query strings } if ( ! defined( 'HTTP_URL_STRIP_USER' ) ) { define( 'HTTP_URL_STRIP_USER', 8 ); // Strip any user authentication information } if ( ! defined( 'HTTP_URL_STRIP_PASS' ) ) { define( 'HTTP_URL_STRIP_PASS', 16 ); // Strip any password authentication information } if ( ! defined( 'HTTP_URL_STRIP_AUTH' ) ) { define( 'HTTP_URL_STRIP_AUTH', 32 ); // Strip any authentication information } if ( ! defined( 'HTTP_URL_STRIP_PORT' ) ) { define( 'HTTP_URL_STRIP_PORT', 64 ); // Strip explicit port numbers } if ( ! defined( 'HTTP_URL_STRIP_PATH' ) ) { define( 'HTTP_URL_STRIP_PATH', 128 ); // Strip complete path } if ( ! defined( 'HTTP_URL_STRIP_QUERY' ) ) { define( 'HTTP_URL_STRIP_QUERY', 256 ); // Strip query string } if ( ! defined( 'HTTP_URL_STRIP_FRAGMENT' ) ) { define( 'HTTP_URL_STRIP_FRAGMENT', 512 ); // Strip any fragments (#identifier) } if ( ! defined( 'HTTP_URL_STRIP_ALL' ) ) { define( 'HTTP_URL_STRIP_ALL', 1024 ); // Strip anything but scheme and host } // Build an URL // The parts of the second URL will be merged into the first according to the flags argument. // // @param mixed (Part(s) of) an URL in form of a string or associative array like parse_url() returns // @param mixed Same as the first argument // @param int A bitmask of binary or'ed HTTP_URL constants (Optional)HTTP_URL_REPLACE is the default // @param array If set, it will be filled with the parts of the composed url like parse_url() would return function http_build_url( $url, $parts = array(), $flags = HTTP_URL_REPLACE, &$new_url = false ) { $keys = array( 'user', 'pass', 'port', 'path', 'query', 'fragment' ); // HTTP_URL_STRIP_ALL becomes all the HTTP_URL_STRIP_Xs if ( $flags & HTTP_URL_STRIP_ALL ) { $flags |= HTTP_URL_STRIP_USER; $flags |= HTTP_URL_STRIP_PASS; $flags |= HTTP_URL_STRIP_PORT; $flags |= HTTP_URL_STRIP_PATH; $flags |= HTTP_URL_STRIP_QUERY; $flags |= HTTP_URL_STRIP_FRAGMENT; } // HTTP_URL_STRIP_AUTH becomes HTTP_URL_STRIP_USER and HTTP_URL_STRIP_PASS elseif ( $flags & HTTP_URL_STRIP_AUTH ) { $flags |= HTTP_URL_STRIP_USER; $flags |= HTTP_URL_STRIP_PASS; } // Parse the original URL // - Suggestion by Sayed Ahad Abbas // In case you send a parse_url array as input $parse_url = ! is_array( $url ) ? parse_url( $url ) : $url; // Scheme and Host are always replaced if ( isset( $parts['scheme'] ) ) { $parse_url['scheme'] = $parts['scheme']; } if ( isset( $parts['host'] ) ) { $parse_url['host'] = $parts['host']; } // (If applicable) Replace the original URL with it's new parts if ( $flags & HTTP_URL_REPLACE ) { foreach ( $keys as $key ) { if ( isset( $parts[ $key ] ) ) { $parse_url[ $key ] = $parts[ $key ]; } } } else { // Join the original URL path with the new path if ( isset( $parts['path'] ) && ( $flags & HTTP_URL_JOIN_PATH ) ) { if ( isset( $parse_url['path'] ) ) { $parse_url['path'] = rtrim( str_replace( basename( $parse_url['path'] ), '', $parse_url['path'] ), '/' ) . '/' . ltrim( $parts['path'], '/' ); } else { $parse_url['path'] = $parts['path']; } } // Join the original query string with the new query string if ( isset( $parts['query'] ) && ( $flags & HTTP_URL_JOIN_QUERY ) ) { if ( isset( $parse_url['query'] ) ) { $parse_url['query'] .= '&' . $parts['query']; } else { $parse_url['query'] = $parts['query']; } } } // Strips all the applicable sections of the URL // Note: Scheme and Host are never stripped foreach ( $keys as $key ) { if ( $flags & (int) constant( 'HTTP_URL_STRIP_' . strtoupper( $key ) ) ) { unset( $parse_url[ $key ] ); } } $new_url = $parse_url; return ( isset( $parse_url['scheme'] ) ? $parse_url['scheme'] . '://' : '' ) . ( isset( $parse_url['user'] ) ? $parse_url['user'] . ( isset( $parse_url['pass'] ) ? ':' . $parse_url['pass'] : '' ) . '@' : '' ) . ( isset( $parse_url['host'] ) ? $parse_url['host'] : '' ) . ( isset( $parse_url['port'] ) ? ':' . $parse_url['port'] : '' ) . ( isset( $parse_url['path'] ) ? $parse_url['path'] : '' ) . ( isset( $parse_url['query'] ) ? '?' . $parse_url['query'] : '' ) . ( isset( $parse_url['fragment'] ) ? '#' . $parse_url['fragment'] : '' ); } } if ( ! function_exists( 'array_key_first' ) ) { function array_key_first( array $arr ) { foreach ( $arr as $k => $unused ) { return $k; } return null; } } if ( ! function_exists( 'array_column' ) ) { function array_column( $array, $column_name ) { return array_map( function ( $element ) use ( $column_name ) { return $element[ $column_name ]; }, $array ); } } ���������������������������������������������������������������������������������������������������������������������������������������������data/.htaccess��������������������������������������������������������������������������������������0000644�����������������00000000151�15207663462�0007263 0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Order Deny,Allow Deny from All <IfModule LiteSpeed> RewriteEngine on RewriteRule .* - [F,L] </IfModule> ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������