helper = $helper; $this->request = $request; $this->logger = $logger; $this->_cacheState = $cacheState; $this->_cache = $cache ?: $helper->_loadObject(Type::class); $this->serializer = $serializer ?: $helper->_loadObject(Json::class); } /** * @param string[] $value * @return string */ public function mapCondition($value) { if (is_array($value) && array_key_exists('condition', $value)) { $value = $value['condition']; } return $value; } /** * @return bool */ public function useCache() { if (!is_bool($this->_useCache)) { //$this->_useCache = (bool) $this->helper->getModuleConfig(static::DEFERJS_USE_CACHE); $this->_useCache = false; } return (bool)$this->_useCache; } /** * @param Observer $observer */ public function execute(Observer $observer) { if (!$this->allowedDefer()) { return; } /** @var ResponseHttp $response */ $response = $observer->getEvent()->getData('response'); if (!$response) { return; } $html = $response->getBody(); if (empty($html)) { return; } if ($this->useCache()) { $_html = $this->_loadCache(); if (!empty($_html)) { $response->setBody($html); return; } } $html = $this->defer($html); $html = $this->showActionPath($html); if ($this->useCache()) { $this->_saveCache($html, $response); } $response->setBody($html); } /** * @return bool */ protected function allowedDefer() { if (!$this->helper->isEnabled() || !$this->getConfig(static::DEFERJS) || $this->request->isXmlHttpRequest() || ( $this->getConfig(static::DEFERJS_EXCLUDE_HOMEPAGE) && $this->helper->isHomePage() ) ) { return false; } $exclude_action = $this->getConfig(static::DEFERJS_EXCLUDE_ACTION); $exclude_path = $this->getConfig(static::DEFERJS_EXCLUDE_PATH); try { $exclude_action = empty($exclude_action) ? [] : $this->serializer->unserialize($exclude_action); } catch (InvalidArgumentException $e) { $exclude_action = []; } $exclude_action = array_map([$this, 'mapCondition'], $exclude_action); try { $exclude_path = empty($exclude_path) ? [] : $this->serializer->unserialize($exclude_path); } catch (InvalidArgumentException $e) { $exclude_path = []; } $exclude_path = array_map([$this, 'mapCondition'], $exclude_path); $action = $this->request->getFullActionName(); $path = $this->request->getPathInfo(); $org_path = $this->request->getOriginalPathInfo(); if (in_array($action, $exclude_action) || in_array($path, $exclude_path) || in_array($org_path, $exclude_path) ) { return false; } return true; } /** * @param string $path * @return mixed */ protected function getConfig($path = '') { return $this->helper->getModuleConfig($path); } /** * Load response from cache storage * * @return string */ protected function _loadCache() { $cacheKey = $this->getCacheKey(); $html = $this->_cache->load($cacheKey); return $html; } /** * Get Key for caching block content * * @return string */ public function getCacheKey() { $data = [ $this->request->isSecure(), $this->request->getUriString(), $this->request->get(ResponseHttp::COOKIE_VARY_STRING), ]; $key = sha1($this->serializer->serialize($data)); return static::CACHE_KEY_PREFIX . $key; } /** * @param string $html * @return string */ protected function defer($html) { $pattern = '@]*+(?(.*?)@ims'; if (preg_match_all($pattern, $html, $_matches)) { $html_js = implode('', $_matches[0]); // Combine magento init scripts if ($this->getConfig(static::DEFERJS_COMBINE_MAGENTOINIT)) { $html_js = $this->replaceMagentoInit($html_js); } // Combine inline scripts if ($this->getConfig(static::DEFERJS_COMBINE_INLINE)) { $html_js = $this->implodeScript($html_js); } $html = preg_replace($pattern, '', $html); if (preg_match('@@i', $html)) { $html = preg_replace('@@i', '', $html); $html = str_replace('', $html_js . '', $html); } else { $html .= $html_js; } } return $html; } /** * @param string $html * @return string */ protected function replaceMagentoInit($html) { $pattern = '@]*type="text/x-magento-init"[^<>]*>(.+)@msU'; if (preg_match_all($pattern, $html, $matches)) { foreach ($matches[1] as $key => $value) { try { $_value = $this->serializer->unserialize($value); $matches[1][$key] = is_array($_value) ? $_value : []; $html = str_replace($matches[0][$key], '', $html); } catch (Exception $exception) { $this->logger->error(__('Wrong Json structure: ' . $value)); unset($matches[1][$key]); } } /** @var array $matches */ $matches = call_user_func_array('array_replace_recursive', $matches[1]); $js = ''; $html .= $js; } return $html; } /** * @param string $html * @return string */ protected function implodeScript($html) { $pattern = '@(]*>)(.*)@msU'; if (preg_match_all($pattern, $html, $matches, PREG_SET_ORDER)) { $new_html = []; $script = []; foreach ($matches as $match) { if (preg_match('@src="@i', $match[1])) { if (!empty($script)) { $script[1] = preg_replace('@^\s+@', '', $script[1]); $script[1] = preg_replace('@\s+$@', '', $script[1]); $new_html[] = $script[0] . $script[1] . ''; $script = []; } $new_html[] = $match[0]; continue; } if (preg_match('@type="(text|application)/javascript"@i', $match[1]) || !preg_match('@type="([^"]+)"@i', $match[1]) ) { if (empty($script)) { $script[0] = $match[1]; $script[1] = ''; } $script[1] .= rtrim(trim($match[2]), ';') . ";\n"; } else { $new_html[] = $match[0]; } } if (!empty($script)) { $new_html[] = $script[0] . $script[1] . ''; } $html = implode('', $new_html); } return $html; } /** * @param string $html * @return string */ protected function showActionPath($html) { if ($this->getConfig(static::DEFERJS_SHOW_ACTION_PATH)) { $new_html = '
' . __('Action') . '' . $this->request->getFullActionName() . '
' . __('Path') . '' . $this->request->getPathInfo() . '
' . __('Original Path') . '' . $this->request->getOriginalPathInfo() . '
'; if (preg_match('@@i', $html)) { $html = preg_replace('@@i', $new_html . '', $html); } else { $html .= $new_html; } } return $html; } /** * Save response content to cache storage * * @param string $html * @param ResponseHttp $response * @return void */ protected function _saveCache($html, ResponseHttp $response) { if ($this->_cacheState->isEnabled(self::TYPE_IDENTIFIER) && ( $response->getHttpResponseCode() == 200 || $response->getHttpResponseCode() == 404 ) && ( $this->request->isGet() || $this->request->isHead() ) ) { $tagsHeader = $response->getHeader('X-Magento-Tags'); $tags = $tagsHeader ? explode(',', $tagsHeader->getFieldValue()) : []; $cacheKey = $this->getCacheKey(); $tags[] = self::CACHE_TAG; $this->_cache->save($html, $cacheKey, array_unique($tags)); } } }