Files
interblue.pl/modules/litespeedcache/classes/Helper.php
2024-10-25 14:16:28 +02:00

390 lines
14 KiB
PHP

<?php
/**
* LiteSpeed Cache for Prestashop.
*
* NOTICE OF LICENSE
*
* 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 https://opensource.org/licenses/GPL-3.0 .
*
* @author LiteSpeed Technologies
* @copyright Copyright (c) 2017-2018 LiteSpeed Technologies, Inc. (https://www.litespeedtech.com)
* @license https://opensource.org/licenses/GPL-3.0
*/
use LiteSpeedCacheConfig as Conf;
use LiteSpeedCacheLog as LSLog;
class LiteSpeedCacheHelper
{
private static $internal = array();
private static function initInternals()
{
$ctx = Context::getContext();
$cookie = $ctx->cookie;
$config = Conf::getInstance();
$defaultParam = array('s' => $ctx->shop->id);
if (isset($cookie->iso_code_country)) {
$defaultParam['ct'] = $cookie->iso_code_country;
}
if (isset($cookie->id_currency)) {
$defaultParam['c'] = $cookie->id_currency;
}
if (isset($cookie->id_lang)) {
$defaultParam['l'] = $cookie->id_lang;
}
if ($config->get(Conf::CFG_DIFFMOBILE)) {
$defaultParam['mobi'] = $ctx->getMobileDevice() ? 1 : 0;
}
self::$internal['pub_ttl'] = $config->get(Conf::CFG_PUBLIC_TTL);
self::$internal['priv_ttl'] = $config->get(Conf::CFG_PRIVATE_TTL);
$unique = str_split(md5(_PS_ROOT_DIR_));
$prefix = 'PS' . implode('', array_slice($unique, 0, 5)); // take 5 char
self::$internal['tag_prefix'] = $prefix;
self::$internal['cache_dir'] = _PS_CACHE_DIR_ . LiteSpeedCache::MODULE_NAME;
$tag0 = $prefix; // for purge all PS cache
$tag1 = $prefix . '_' . Conf::TAG_PREFIX_SHOP . $defaultParam['s']; // for purge one shop
self::$internal['tag_shared_pub'] = $tag0 . ',' . $tag1;
self::$internal['tag_shared_priv'] = 'public:' . $prefix . '_PRIV'; // in private cache, use public:prefix_PRIV
if (LiteSpeedCache::canInjectEsi() || LiteSpeedCache::isCacheable() || LiteSpeedCache::isEsiRequest()) {
// For some purge events, it may not load from dispatcher, getModuleLink will fail
$esiurl = $ctx->link->getModuleLink(LiteSpeedCache::MODULE_NAME, 'esi', $defaultParam);
self::$internal['esi_base_url'] = self::getRelativeUri($esiurl);
self::$internal['cache_entry'] = $prefix . md5(self::$internal['esi_base_url']);
}
}
public static function getRelativeUri($url)
{
if (($pos0 = strpos($url, '://')) !== false) {
$pos0 += 4;
if ($pos1 = strpos($url, '/', $pos0)) {
$newurl = Tools::substr($url, $pos1);
return $newurl;
}
}
return false;
}
public static function getCacheFilePath(&$dir)
{
$dir = self::getCacheDir();
return $dir . '/' . self::$internal['cache_entry'] . '.data';
}
public static function getCacheDir()
{
if (!isset(self::$internal['cache_dir'])) {
self::initInternals();
}
$dir = self::$internal['cache_dir'];
if (!is_dir($dir)) {
mkdir($dir);
}
return $dir;
}
public static function clearInternalCache()
{
$count = 0;
$dir = self::getCacheDir();
foreach (scandir($dir) as $entry) {
if (preg_match('/\.data$/', $entry)) {
@unlink($dir . '/' . $entry);
++$count;
}
}
if (_LITESPEED_DEBUG_ >= LSLog::LEVEL_PURGE_EVENT) {
LSLog::log(__FUNCTION__ . "=$count", LSLog::LEVEL_PURGE_EVENT);
}
}
public static function genEsiElements(LiteSpeedCacheEsiItem $item)
{
if (!isset(self::$internal['esi_base_url'])) {
self::initInternals();
}
// example
// <esi:inline name="/litemage/esi/.../" cache-control="private,max-age=1800,no-vary"
// cache-tag="E.welcome">Welcome</esi:inline>
// <esi:include src='/litemage/esi/.../'
// cache-tag='E.footer' as-var='1' cache-control='no-vary,public'/>
// id is base64_encode(json_encode($params))
$url = self::$internal['esi_base_url'] . '&pd=' . urlencode($item->getId());
$tagInline = '';
$tagInclude = '';
$esiconf = $item->getConf();
$ttl = $esiconf->getTTL();
if ($ttl === 0 || $ttl === '0') {
$ccInclude = $ccInline = 'no-cache';
} else {
$isPrivate = $esiconf->isPrivate();
if ($ttl === '') {
$ttl = $isPrivate ? self::$internal['priv_ttl'] : self::$internal['pub_ttl'];
}
$ccInclude = $isPrivate ? 'private' : 'public';
$ccInclude .= ',no-vary';
// onlyCacheIfEmpty is conditional cache, so in esi:include, make it cacheable, this
// will only have one cache copy (no need vary) for public page
// esi:inline cacheable will be different based on content
if ($esiconf->onlyCacheEmtpy() && $item->getContent() !== '') {
$ccInline = 'no-cache';
$ttl = 0;
} else {
$ccInline = $ccInclude . ',max-age=' . $ttl;
}
$tagInline = ' cache-tag=\''; // need space in front
$tagInline .= $isPrivate ?
self::$internal['tag_shared_priv'] : self::$internal['tag_shared_pub'];
if ($tag = $esiconf->getTag()) {
$tagInline .= ',' . self::$internal['tag_prefix'] . '_' . $tag;
}
$tagInline .= '\'';
if ($esiconf->asVar()) { // only asvar need to show tag
$tagInclude = $tagInline . ' as-var=\'1\'';
}
}
$esiInclude = sprintf('<esi:include src=\'%s\' cache-control=\'%s\'%s/>', $url, $ccInclude, $tagInclude);
$inlineStart = sprintf('<esi:inline name=\'%s\' cache-control=\'%s\'%s>', $url, $ccInline, $tagInline);
$item->setIncludeInlineTag($esiInclude, $inlineStart, $url, $ttl);
}
// unique prefix for this PS installation, avoid conflict of multiple installations within same cache root
public static function getTagPrefix()
{
return self::getInternalValue('tag_prefix');
}
public static function getCacheEntry()
{
return self::getInternalValue('cache_entry');
}
private static function getInternalValue($field)
{
if (!isset(self::$internal[$field])) {
self::initInternals();
}
return self::$internal[$field];
}
// validator does not allow to use file_get_contents, so use this workaround.
protected static function getFileContent($filepath)
{
$contents = '';
$len = @filesize($filepath);
if ($len) {
$h = @fopen($filepath, 'rb');
$contents = @fread($h, $len);
@fclose($h);
}
return $contents;
}
private static function genHtAccessContent($guestMode, $mobileView)
{
$ls = array();
$ls[] = '### LITESPEED_CACHE_START - Do not remove this line, LSCache plugin will automatically update it';
$ls[] = '# automatically genereated by LiteSpeedCache plugin: '
. 'https://www.litespeedtech.com/support/wiki/doku.php/litespeed_wiki:cache:lscps';
$ls[] = '<IfModule LiteSpeed>';
$ls[] = 'CacheLookup on';
if ($guestMode) {
$ls[] = 'RewriteEngine on';
if ($mobileView) {
$ls[] = 'RewriteCond %{HTTP_COOKIE} !PrestaShop-';
$ls[] = 'RewriteCond %{HTTP_USER_AGENT} "phone|mobile|android|Opera Mini" [NC]';
$ls[] = 'RewriteRule .* - [E=Cache-Control:vary=guestm]';
$ls[] = 'RewriteCond %{HTTP_COOKIE} !PrestaShop-';
$ls[] = 'RewriteCond %{HTTP_USER_AGENT} "!(phone|mobile|android|Opera Mini)" [NC]';
$ls[] = 'RewriteRule .* - [E=Cache-Control:vary=guest]';
} else {
$ls[] = 'RewriteCond %{HTTP_COOKIE} !PrestaShop-';
$ls[] = 'RewriteRule .* - [E=Cache-Control:vary=guest]';
}
}
$ls[] = '</IfModule>';
$ls[] = '### LITESPEED_CACHE_END';
$newcontent = implode("\n", $ls) . "\n";
return $newcontent;
}
public static function htAccessBackup($suffix)
{
$path = _PS_ROOT_DIR_ . '/.htaccess';
$newfile = $path . '.' . $suffix . time();
if (!file_exists($newfile)) {
$content = self::getFileContent($path);
if ($content) {
$res = file_put_contents($newfile, $content);
if ($res) {
LSLog::log(__FUNCTION__ . ' backed up as ' . $newfile, LSLog::LEVEL_UPDCONFIG);
return true;
}
}
}
return false;
}
public static function htAccessUpdate($enableCache, $guestMode, $mobileView)
{
$path = _PS_ROOT_DIR_ . '/.htaccess';
$oldlines = file($path);
if ($oldlines === '') {
LSLog::log(__FUNCTION__ . ' please manually fix .htaccess, may due to permission', LSLog::LEVEL_FORCE);
return false;
}
$newlines = array();
$ind = false;
// always remove first
foreach ($oldlines as $line) {
if (!$ind) {
if (strpos($line, 'LITESPEED_CACHE_START') || stripos($line, 'IfModule LiteSpeed')) {
$ind = true;
} else {
$newlines[] = $line;
}
} else {
if (strpos($line, 'LITESPEED_CACHE_END')) {
$ind = false;
} elseif (strpos($line, '~~start~~')) {
$ind = false;
$newlines[] = $line;
}
}
}
$newcontent = '';
if ($enableCache) {
$newcontent = self::genHtAccessContent($guestMode, $mobileView);
}
$newcontent .= implode('', $newlines);
$res = file_put_contents($path, $newcontent);
if ($res) {
LSLog::log(__FUNCTION__ . ' updated', LSLog::LEVEL_UPDCONFIG);
return true;
} else {
LSLog::log(__FUNCTION__ . ' cannot save! Please manually fix .htaccess file', LSLog::LEVEL_FORCE);
return false;
}
}
// if id is false, load all
public static function getRelatedItems($id)
{
$items = array();
$dir = '';
$cacheFile = self::getCacheFilePath($dir);
$snapshot = self::getFileContent($cacheFile);
$saved = json_decode($snapshot, true);
if (!is_array($saved)
|| json_last_error() !== JSON_ERROR_NONE
|| ($id != false && !isset($saved['data'][$id]))) {
return $items;
}
$related = array();
$tag = ($id) ? $saved['data'][$id]['tag'] : Conf::TAG_ENV;
if ($tag == Conf::TAG_ENV) {
$related = array_keys($saved['data']);
} elseif (isset($saved['tags'][$tag])) {
$related = array_keys($saved['tags'][$tag]);
}
foreach ($related as $rid) {
if ($rid != $id) {
$ri = LiteSpeedCacheEsiItem::newFromSavedData($saved['data'][$rid]);
if ($ri != null) {
$items[] = $ri;
}
}
}
return $items;
}
public static function syncItemCache($itemList)
{
$dir = '';
$cacheFile = self::getCacheFilePath($dir);
$snapshot = self::getFileContent($cacheFile);
$saved = json_decode($snapshot, true);
if (!is_array($saved) || json_last_error() !== JSON_ERROR_NONE) {
$saved = array('data' => array(), 'tags' => array());
}
foreach ($itemList as $item) {
$id = $item->getId();
$descr = $item->getInfoLog(true);
$saved['data'][$id] = $item;
$tag = $item->getTag();
if ($tag == Conf::TAG_ENV) {
continue;
}
if (!isset($saved['tags'][$tag])) {
$saved['tags'][$tag] = array($id => $descr);
} elseif (!isset($saved['tags'][$tag][$id])) {
$saved['tags'][$tag][$id] = $descr;
}
}
ksort($saved['data']);
ksort($saved['tags']);
$newsnapshot = json_encode($saved, JSON_UNESCAPED_SLASHES);
if ($snapshot != $newsnapshot) {
if (_LITESPEED_DEBUG_ >= LSLog::LEVEL_SAVED_DATA) {
LSLog::log(__FUNCTION__ . ' updated data ' . var_export($saved, true), LSLog::LEVEL_SAVED_DATA);
}
file_put_contents($cacheFile, $newsnapshot);
}
}
public static function isStaticResource($url)
{
$pattern = '/(js|css|jpg|png|svg|gif|woff|woff2)$/';
return preg_match($pattern, $url);
}
public static function licenseEnabled()
{
// possible string "on,crawler,esi", will enforce checking in future
return ((isset($_SERVER['X-LSCACHE']) && $_SERVER['X-LSCACHE']) // for lsws
|| (isset($_SERVER['HTTP_X_LSCACHE']) && $_SERVER['HTTP_X_LSCACHE'])); // lslb
}
}