From a7369e2669c0cc02442b4540503403f58e4f7e30 Mon Sep 17 00:00:00 2001
From: JaJuMa
Date: Fri, 5 Sep 2025 15:42:07 +0700
Subject: [PATCH 1/5] Implement Back/Forward Cache functionality with
configuration options and view models
---
Plugin/Framework/App/Response/Http.php | 172 +++++++++++++++++
ViewModel/BfCache.php | 76 ++++++++
etc/adminhtml/system.xml | 52 ++++++
etc/frontend/di.xml | 7 +
etc/module.xml | 1 +
view/frontend/layout/default.xml | 7 +
view/frontend/layout/default_hyva.xml | 14 ++
view/frontend/templates/bfcache/handler.phtml | 162 ++++++++++++++++
.../templates/hyva/bfcache/handler.phtml | 173 ++++++++++++++++++
9 files changed, 664 insertions(+)
create mode 100644 Plugin/Framework/App/Response/Http.php
create mode 100644 ViewModel/BfCache.php
create mode 100644 etc/frontend/di.xml
create mode 100644 view/frontend/layout/default_hyva.xml
create mode 100644 view/frontend/templates/bfcache/handler.phtml
create mode 100644 view/frontend/templates/hyva/bfcache/handler.phtml
diff --git a/Plugin/Framework/App/Response/Http.php b/Plugin/Framework/App/Response/Http.php
new file mode 100644
index 0000000..e6fce11
--- /dev/null
+++ b/Plugin/Framework/App/Response/Http.php
@@ -0,0 +1,172 @@
+config = $config;
+ $this->scopeConfig = $scopeConfig;
+ $this->request = $request;
+ }
+
+ /**
+ * Intercept before setting no-cache headers to determine if request is cacheable
+ *
+ * @param \Magento\Framework\App\Response\Http $subject
+ * @return void
+ */
+ public function beforeSetNoCacheHeaders(\Magento\Framework\App\Response\Http $subject): void
+ {
+ if ($this->config->getType() !== Config::BUILT_IN || !$this->isEnabled()) {
+ return;
+ }
+
+ $cacheControlHeader = $subject->getHeader('Cache-Control');
+ if (!$cacheControlHeader) {
+ return;
+ }
+
+ $cacheControl = $cacheControlHeader->getFieldValue();
+ $requestURI = ltrim($this->request->getRequestURI(), '/');
+
+ if ($this->isRequestCacheable($cacheControl) && !$this->isRequestInBlackListUrls($requestURI)) {
+ $this->isRequestCacheable = true;
+ }
+ }
+
+ /**
+ * Update cache headers after setting no-cache headers
+ *
+ * @param \Magento\Framework\App\Response\Http $subject
+ * @param mixed $result
+ * @return mixed
+ */
+ public function afterSetNoCacheHeaders(\Magento\Framework\App\Response\Http $subject, $result)
+ {
+ if ($this->config->getType() !== Config::BUILT_IN || !$this->isEnabled()) {
+ return $result;
+ }
+
+ $cacheControlHeader = $subject->getHeader('Cache-Control');
+ if (!$cacheControlHeader) {
+ return $result;
+ }
+
+ if ($this->isRequestCacheable == true) {
+ $cacheControlHeader = $subject->getHeader('cache-control');
+ $cacheControlHeader->removeDirective('no-store');
+ }
+ $this->isRequestCacheable = false;
+
+ return $result;
+ }
+
+ /**
+ * Check if request is cacheable based on cache control header
+ *
+ * @param string $cacheControl
+ * @return bool
+ */
+ private function isRequestCacheable(string $cacheControl): bool
+ {
+ return (bool) preg_match('/public.*s-maxage=(\d+)/', $cacheControl);
+ }
+
+ /**
+ * Check if request URI matches blacklisted URLs
+ *
+ * @param string $requestURI
+ * @return bool
+ */
+ private function isRequestInBlackListUrls(string $requestURI): bool
+ {
+ $blackListUrls = $this->convertListUrls(self::XML_PATH_BLACK_LIST_URLS);
+ if (!$blackListUrls) {
+ return false;
+ }
+
+ return (bool) preg_match('/' . $blackListUrls . '/', $requestURI);
+ }
+
+ /**
+ * Check if BFCache is enabled
+ *
+ * @return bool
+ */
+ private function isEnabled(): bool
+ {
+ return $this->scopeConfig->isSetFlag(
+ self::XML_PATH_ENABLE,
+ ScopeInterface::SCOPE_STORE
+ );
+ }
+
+ /**
+ * Get configuration value by path
+ *
+ * @param string $configPath
+ * @param int|string|null $store
+ * @return string|null
+ */
+ private function getConfig(string $configPath, $store = null): ?string
+ {
+ return $this->scopeConfig->getValue(
+ $configPath,
+ ScopeInterface::SCOPE_STORE,
+ $store
+ );
+ }
+
+ /**
+ * Convert comma-separated URLs to pipe-separated regex pattern
+ *
+ * @param string $configPath
+ * @return string
+ */
+ private function convertListUrls(string $configPath): string
+ {
+ $listUrls = $this->getConfig($configPath);
+ if (!$listUrls) {
+ return '';
+ }
+
+ $urlList = array_map('trim', explode(',', $listUrls));
+ $urlList = array_filter($urlList);
+
+ return implode('|', array_map('preg_quote', $urlList, array_fill(0, count($urlList), '/')));
+ }
+}
diff --git a/ViewModel/BfCache.php b/ViewModel/BfCache.php
new file mode 100644
index 0000000..509cd98
--- /dev/null
+++ b/ViewModel/BfCache.php
@@ -0,0 +1,76 @@
+scopeConfig = $scopeConfig;
+ $this->httpContext = $httpContext;
+ }
+
+ /**
+ * Check if user interaction should refresh minicart
+ *
+ * @return bool
+ */
+ public function getEnableUserInteractionRefreshMiniCart(): bool
+ {
+ return $this->scopeConfig->isSetFlag(
+ self::XML_PATH_ENABLE_USER_INTERACTION_RELOAD_MINICART,
+ ScopeInterface::SCOPE_STORE
+ );
+ }
+
+ /**
+ * Check if mobile menu should auto-close
+ *
+ * @return bool
+ */
+ public function autoCloseMenuMobile(): bool
+ {
+ return $this->scopeConfig->isSetFlag(
+ self::XML_PATH_AUTO_CLOSE_MENU_MOBILE,
+ ScopeInterface::SCOPE_STORE
+ );
+ }
+
+ /**
+ * Check if customer is logged in
+ *
+ * @return bool
+ */
+ public function isCustomerLoggedIn(): bool
+ {
+ return (bool) $this->httpContext->getValue(CustomerContext::CONTEXT_AUTH);
+ }
+}
diff --git a/etc/adminhtml/system.xml b/etc/adminhtml/system.xml
index 974bf87..c6cdee9 100644
--- a/etc/adminhtml/system.xml
+++ b/etc/adminhtml/system.xml
@@ -51,5 +51,57 @@
+
+
+ Back/Forward Cache Configuration
+ Configure browser's back/forward cache to improve navigation performance by storing pages in memory.
+ ]]>
+
+ General Settings
+ 1
+
+ Enable Back/Forward Cache
+ Magento\Config\Model\Config\Source\Yesno
+
+
+
+
+ Update Mini Cart on User Interaction
+ Magento\Config\Model\Config\Source\Yesno
+ Yes: Mini cart updates only after user interaction when page is restored from cache.
+ No: Mini cart updates immediately on page restore.
+ Recommended "Yes" to maintain optimal Page Speed and Core Web Vitals scores.
+ ]]>
+
+
+
+
+
+ Cache Exclusions
+ Note: This configuration is optional for most sites.
+ The extension automatically excludes non-cacheable URLs from back/forward cache.
+ Use the blacklist below if you have custom cached URLs that load private data via JavaScript.
+ ]]>
+
+ Excluded URLs
+
+
+
+
+
diff --git a/etc/frontend/di.xml b/etc/frontend/di.xml
new file mode 100644
index 0000000..b86d0f1
--- /dev/null
+++ b/etc/frontend/di.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/etc/module.xml b/etc/module.xml
index 2dd2d99..f74465d 100644
--- a/etc/module.xml
+++ b/etc/module.xml
@@ -4,6 +4,7 @@
+
diff --git a/view/frontend/layout/default.xml b/view/frontend/layout/default.xml
index 56a29d7..a494de5 100644
--- a/view/frontend/layout/default.xml
+++ b/view/frontend/layout/default.xml
@@ -10,6 +10,13 @@
\MageOS\ThemeOptimization\ViewModel\SpeculationRules
+
+
+ \MageOS\ThemeOptimization\ViewModel\BfCache
+
+