Files
lulandia.pl/modules/pagecache/classes/PageCacheDAO.php
2025-04-01 00:38:54 +02:00

1285 lines
68 KiB
PHP

<?php
/**
* Page Cache Ultimate, Page Cache standard and Speed pack are powered by Jpresta (jpresta . com)
*
* @author Jpresta
* @copyright Jpresta
* @license See the license of this module in file LICENSE.txt, thank you.
*/
use JPresta\SpeedPack\JprestaUtils;
if (!defined('_PS_VERSION_')) {exit;}
// Check existence of the class to be able to handle compatibility problems in a friendly way
if (!class_exists('PageCacheDAO')) {
class PageCacheDAO
{
/**
* MyIsam locks whole table while InnoDb locks rows. InnoDb is fine but you must not use relation between tables
* or it will be too slow.
*/
const MYSQL_ENGINE = 'InnoDb';
const TABLE = 'jm_pagecache';
const TABLE_CONTEXTS = 'jm_pagecache_contexts';
const TABLE_PERFS = 'jm_pagecache_perfs';
const TABLE_DETAILS = 'jm_pagecache_details';
const TABLE_BACKLINK = 'jm_pagecache_bl';
const TABLE_MODULE = 'jm_pagecache_mods';
// This table is created to store state of the cache refreshment concerning a specific price
const TABLE_SPECIFIC_PRICES = 'jm_pagecache_sp';
// Store date for profiling
const TABLE_PROFILING = 'jm_pagecache_prof';
/**
* @throws PrestaShopDatabaseException
*/
public static function createTables()
{
JprestaUtils::dbExecuteSQL('CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . self::TABLE_DETAILS . '`(
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`details` TEXT NOT NULL,
`details_md5` VARCHAR(32) DEFAULT NULL,
PRIMARY KEY (`id`),
INDEX details_md5 (`details_md5`)
) ENGINE=' . PageCacheDAO::MYSQL_ENGINE . ' DEFAULT CHARSET=utf8', true, true);
self::createTableContexts();
self::createTableStatsPerf();
$sqlCreateMainTable = 'CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . self::TABLE . '`(
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`id_shop` TINYINT UNSIGNED NOT NULL DEFAULT 1,
`cache_key` INT UNSIGNED NOT NULL,
`url` VARCHAR(1000) NOT NULL,
`id_context` INT(10) UNSIGNED NOT NULL,
`id_controller` TINYINT(1) UNSIGNED DEFAULT NULL,
`id_object` INT UNSIGNED,
`count_missed` MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,
`count_hit` MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,
`last_gen` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`deleted` TINYINT UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE `url_id_context` (`url`, `id_context`),
KEY `id_controller_object` (`id_controller`, `id_object`),
KEY `id_controller_last_gen` (`id_controller`, `last_gen`),
KEY `id_controller` (`id_controller`),
KEY `id_shop` (`id_shop`),
KEY `id_context` (`id_context`),
KEY `last_gen` (`last_gen`),
KEY `deleted`(`deleted`)
) ENGINE=' . PageCacheDAO::MYSQL_ENGINE . ' DEFAULT CHARSET=utf8';
try {
JprestaUtils::dbExecuteSQL($sqlCreateMainTable, true, true);
} catch (Exception $e) {
// Some databases do not allow index > 767 bytes
JprestaUtils::dbExecuteSQL(str_replace('`url` VARCHAR(1000)', '`url` VARCHAR(255)',
$sqlCreateMainTable), true, true);
}
JprestaUtils::dbExecuteSQL('CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . self::TABLE_BACKLINK . '`(
`id` int(11) UNSIGNED NOT NULL,
`backlink_key` INT UNSIGNED NOT NULL,
KEY (`id`),
KEY (`backlink_key`)
) ENGINE=' . PageCacheDAO::MYSQL_ENGINE . ' DEFAULT CHARSET=utf8', true, true);
JprestaUtils::dbExecuteSQL('CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . self::TABLE_MODULE . '`(
`id` int(11) UNSIGNED NOT NULL,
`id_module` int(11) UNSIGNED NOT NULL,
PRIMARY KEY (`id`,`id_module`),
KEY (`id_module`)
) ENGINE=' . PageCacheDAO::MYSQL_ENGINE . ' DEFAULT CHARSET=utf8', true, true);
JprestaUtils::dbExecuteSQL('CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . self::TABLE_SPECIFIC_PRICES . '`(
`id_specific_price` int(10) unsigned NOT NULL,
`id_product` int(10) unsigned NOT NULL,
`date_from` datetime,
`date_to` datetime,
PRIMARY KEY (`id_specific_price`),
KEY `idxfrom` (`date_from`),
KEY `idxto` (`date_to`)
) ENGINE=' . PageCacheDAO::MYSQL_ENGINE . ' DEFAULT CHARSET=utf8', true, true);
JprestaUtils::dbExecuteSQL('CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . self::TABLE_PROFILING . '`(
`id_profiling` int(10) unsigned NOT NULL AUTO_INCREMENT,
`id_module` int(10) unsigned NOT NULL,
`description` varchar(255) NOT NULL,
`date_exec` timestamp DEFAULT CURRENT_TIMESTAMP,
`duration_ms` mediumint unsigned NOT NULL,
PRIMARY KEY (`id_profiling`)
) ENGINE=' . PageCacheDAO::MYSQL_ENGINE . ' DEFAULT CHARSET=utf8', true, true);
// Feed TABLE_SPECIFIC_PRICES to trigger cache reffreshment when a reduction starts or ends
$now = date('Y-m-d H:i:00');
$sqlInsertQuery = 'INSERT INTO `' . _DB_PREFIX_ . self::TABLE_SPECIFIC_PRICES . '` (`id_specific_price`,`id_product`,`date_from`,`date_to`)
SELECT `id_specific_price`, `id_product`, `from`, `to` FROM `' . _DB_PREFIX_ . 'specific_price`
WHERE `from`>\'' . pSQL($now) . '\' OR `to`>\'' . pSQL($now) . '\'';
JprestaUtils::dbExecuteSQL($sqlInsertQuery, true, true);
}
public static function createTableContexts() {
$ret = JprestaUtils::dbExecuteSQL('CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . self::TABLE_CONTEXTS . '`(
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`context_key` VARCHAR(64) NOT NULL,
`id_shop` INT(10) UNSIGNED DEFAULT NULL,
`id_lang` INT(10) UNSIGNED DEFAULT NULL,
`id_currency` INT(10) UNSIGNED,
`id_fake_customer` INT(10) UNSIGNED DEFAULT NULL,
`id_device` TINYINT(1) UNSIGNED,
`id_country` INT(10) UNSIGNED DEFAULT NULL,
`id_tax_csz` INT(11) UNSIGNED DEFAULT NULL,
`id_specifics` INT(11) UNSIGNED DEFAULT NULL,
`v_css` SMALLINT UNSIGNED DEFAULT NULL,
`v_js` SMALLINT UNSIGNED DEFAULT NULL,
`used_by_cw` TINYINT UNSIGNED NOT NULL DEFAULT 0,
`active` TINYINT UNSIGNED NOT NULL DEFAULT 1,
`count_bot` INT UNSIGNED NOT NULL DEFAULT 0,
`count_hit_server` INT UNSIGNED NOT NULL DEFAULT 0,
`count_hit_static` INT UNSIGNED NOT NULL DEFAULT 0,
`count_hit_browser` INT UNSIGNED NOT NULL DEFAULT 0,
`count_hit_bfcache` INT UNSIGNED NOT NULL DEFAULT 0,
`count_missed` INT UNSIGNED NOT NULL DEFAULT 0,
`date_add` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`uniq_key` INT UNSIGNED NOT NULL,
PRIMARY KEY (`id`),
UNIQUE (`context_key`)
) ENGINE=' . PageCacheDAO::MYSQL_ENGINE . ' DEFAULT CHARSET=utf8', true, true);
$ret = $ret && JprestaUtils::dbExecuteSQL('CREATE UNIQUE INDEX idx_find_context_full ON `' . _DB_PREFIX_ . self::TABLE_CONTEXTS . '` (id_shop, id_lang, id_currency, id_fake_customer, id_device, id_country, id_tax_csz, id_specifics, v_css, v_js)');
$ret = $ret && JprestaUtils::dbExecuteSQL('CREATE UNIQUE INDEX idx_uniq_key ON `' . _DB_PREFIX_ . self::TABLE_CONTEXTS . '` (uniq_key)');
$ret = $ret && JprestaUtils::dbExecuteSQL('CREATE INDEX idx_find_context ON `' . _DB_PREFIX_ . self::TABLE_CONTEXTS . '` (id_shop, id_lang, id_currency, id_fake_customer, id_device, id_country, id_tax_csz, id_specifics)');
$ret = $ret && JprestaUtils::dbExecuteSQL('CREATE INDEX idx_order_context ON `' . _DB_PREFIX_ . self::TABLE_CONTEXTS . '` (used_by_cw, date_add)');
$ret = $ret && JprestaUtils::dbExecuteSQL('CREATE INDEX idx_active_context ON `' . _DB_PREFIX_ . self::TABLE_CONTEXTS . '` (active)');
return $ret;
}
public static function createTableStatsPerf() {
return JprestaUtils::dbExecuteSQL('CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . self::TABLE_PERFS . '`(
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`id_shop` int(11) UNSIGNED NOT NULL,
`id_controller` TINYINT(1) UNSIGNED DEFAULT NULL,
`date_add` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`day_add` DATE DEFAULT NULL,
`ttfb_ms_hit_server` INT UNSIGNED DEFAULT NULL,
`ttfb_ms_hit_static` INT UNSIGNED DEFAULT NULL,
`ttfb_ms_hit_browser` INT UNSIGNED DEFAULT NULL,
`ttfb_ms_hit_bfcache` INT UNSIGNED DEFAULT NULL,
`ttfb_ms_missed` INT UNSIGNED DEFAULT NULL,
`reduced` TINYINT UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
INDEX date_add_idx (`date_add`),
INDEX day_add_idx (`day_add`),
INDEX id_shop_idx (`id_shop`),
INDEX idx_sdc (id_shop, day_add, id_controller),
INDEX idx_sd (id_shop, day_add)
) ENGINE=' . PageCacheDAO::MYSQL_ENGINE . ' DEFAULT CHARSET=utf8', true, true);
}
public static function insertSpecificPrice($id, $id_product, $from, $to)
{
$query = 'INSERT INTO `' . _DB_PREFIX_ . self::TABLE_SPECIFIC_PRICES . '` (id_specific_price,id_product,date_from,date_to) VALUES ';
$query .= '(' . (int)$id . ',' . (int)$id_product . ',\'' . pSQL($from) . '\',\'' . pSQL($to) . '\');';
JprestaUtils::dbExecuteSQL($query);
// Prestashop deletes and creates new specific prices when saving a product
$queryDeleteOld = 'DELETE psp FROM `' . _DB_PREFIX_ . self::TABLE_SPECIFIC_PRICES . '` psp LEFT JOIN `' . _DB_PREFIX_ . 'specific_price` sp ON psp.id_specific_price=sp.id_specific_price WHERE sp.id_specific_price IS NULL;';
JprestaUtils::dbExecuteSQL($queryDeleteOld);
}
public static function updateSpecificPrice($id, $id_product, $from, $to)
{
$query = 'INSERT INTO `' . _DB_PREFIX_ . self::TABLE_SPECIFIC_PRICES . '` (id_specific_price,id_product,date_from,date_to) VALUES ';
$query .= '(' . (int)$id . ',' . (int)$id_product . ',\'' . pSQL($from) . '\',\'' . pSQL($to) . '\')';
JprestaUtils::dbExecuteSQL($query . ' ON DUPLICATE KEY UPDATE date_from=\'' . pSQL($from) . '\', date_to=\'' . pSQL($to) . '\'');
}
public static function deleteSpecificPrice($id)
{
$query = 'DELETE FROM `' . _DB_PREFIX_ . self::TABLE_SPECIFIC_PRICES . '` WHERE id_specific_price=' . (int)$id . ';';
JprestaUtils::dbExecuteSQL($query);
}
/**
* Reffresh cache if any specific sprice started or ended since last check
*/
public static function triggerReffreshment()
{
$now = date('Y-m-d H:i:00');
$query = 'SELECT DISTINCT id_product FROM `' . _DB_PREFIX_ . self::TABLE_SPECIFIC_PRICES . '` WHERE date_from<=\'' . pSQL($now) . '\' OR date_to<\'' . pSQL($now) . '\';';
$rows = JprestaUtils::dbSelectRows($query);
if (JprestaUtils::isIterable($rows)) {
if (count($rows) > 0) {
// To avoid/limit deadlock errors I added "ORDER BY id_specific_price" as explained
// here: https://stackoverflow.com/questions/2332768/how-to-avoid-mysql-deadlock-found-when-trying-to-get-lock-try-restarting-trans
// Change date now to avoid other visitors to trigger refreshment
$queryUpdFrom = 'UPDATE `' . _DB_PREFIX_ . self::TABLE_SPECIFIC_PRICES . '` SET date_from=\'6666-01-01 00:00:00\' WHERE date_from<=\'' . pSQL($now) . '\' ORDER BY id_specific_price;';
$queryUpdTo = 'UPDATE `' . _DB_PREFIX_ . self::TABLE_SPECIFIC_PRICES . '` SET date_to=\'6666-01-01 00:00:00\' WHERE date_to<\'' . pSQL($now) . '\' ORDER BY id_specific_price;';
// Clean useless rows
$queryDel = 'DELETE FROM `' . _DB_PREFIX_ . self::TABLE_SPECIFIC_PRICES . '` WHERE date_from=\'6666-01-01 00:00:00\' AND date_to=\'6666-01-01 00:00:00\' ORDER BY id_specific_price;';
// Executes all in one within a transaction to avoid Dead locks
if (!JprestaUtils::dbExecuteSQL("START TRANSACTION;\n$queryUpdFrom\n$queryUpdTo\n$queryDel\nCOMMIT;", false, false)) {
// Some configurations do not accept multiple SQL queries in the same statement
JprestaUtils::dbExecuteSQL($queryUpdFrom);
JprestaUtils::dbExecuteSQL($queryUpdTo);
JprestaUtils::dbExecuteSQL($queryDel);
}
foreach ($rows as $row) {
$id_product = (int)$row['id_product'];
// Clear product cache and linking pages because price has changed
if ($id_product !== 0) {
self::clearCacheOfObject('product', $id_product, true, 'specific price');
} else {
// Specific case where the specific rule is global (all products are concerned)
PageCacheDAO::clearCacheOfObject('index', null, false,
'start/end of a global price rule', Configuration::get('pagecache_logs'));
PageCacheDAO::clearCacheOfObject('category', false, false,
'start/end of a global price rule', Configuration::get('pagecache_logs'));
PageCacheDAO::clearCacheOfObject('product', false, false,
'start/end of a global price rule', Configuration::get('pagecache_logs'));
}
}
}
}
}
/**
* @param int $nbHourExpired Number of hour since the cache is expired, can be negative to pages that are about to expire
* @param bool $maxRows Max number of returned rows
* @param bool $deleted false if you want pages that have available cache, true for pages where the cache has been deleted, null if it does not matter
* @param array $obsoleteContextsIds List of context IDs that are obsolete (and can be purged)
* @return array cached pages
*/
public static function getCachedPages($nbHourExpired, $maxRows = false, $deleted = false, $obsoleteContextsIds = [])
{
$rowsToReturn = array();
$query = 'SELECT * FROM `' . _DB_PREFIX_ . self::TABLE . '`';
$whereClauses = array();
foreach (PageCache::getManagedControllers() as $controller => $controllerInfos) {
$configuredMaxAge = 60 * ((int)Configuration::get('pagecache_' . $controller . '_timeout'));
if ($configuredMaxAge < 0) {
// Never expire
continue;
}
elseif ($configuredMaxAge === 0) {
// Cache is disabled
$whereClauses[] = 'id_controller=' . JprestaUtils::dbToInt($controllerInfos['id']);
} else {
$minAgeToReturn = max(0, $configuredMaxAge + ((int)$nbHourExpired * 60 * 60));
$whereClauses[] = '(id_controller=' . JprestaUtils::dbToInt($controllerInfos['id']) . ' AND last_gen < (NOW() - INTERVAL ' . (int)$minAgeToReturn . ' SECOND))';
}
}
if (count($whereClauses) > 0) {
$query .= ' WHERE (' . implode(' OR ', $whereClauses) . ')';
if ($deleted !== null) {
$query .= ' AND `deleted`=' . ($deleted ? 1 : 0);
}
if (count($obsoleteContextsIds) > 0) {
// If the context is obsolete it does not matter if the cache is available or not, it will not be used
// anymore
$query .= ' OR id_context IN (' . implode(',', $obsoleteContextsIds) . ')';
}
}
else {
if (count($obsoleteContextsIds) > 0) {
// If the context is obsolete it does not matter if the cache is available or not, it will not be used
// anymore
$query .= ' WHERE id_context IN (' . implode(',', $obsoleteContextsIds) . ')';
}
else {
// Nothing to return
return [];
}
}
$query .= ' ORDER BY last_gen ASC';
if ($maxRows !== false) {
$query .= ' LIMIT ' . ((int)$maxRows - count($rowsToReturn));
}
return JprestaUtils::dbSelectRows($query);
}
/**
* @param $rows array Rows returned by self::getCachedPages()
* @param bool $deleteStats
*/
public static function deleteCachedPages($rows, $deleteStats = false)
{
if (JprestaUtils::isIterable($rows) && count($rows) > 0) {
$cacheIdsToDelete = array();
foreach ($rows as $row) {
PageCache::getCache()->delete($row['url'], self::getContextKeyById($row['id_context']));
$cacheIdsToDelete[] = (int) $row['id'];
}
if ($deleteStats) {
// Delete all rows
$whereClause = 'id IN (' . pSQL(implode(',', array_map('intval', $cacheIdsToDelete))) . ')';
JprestaUtils::dbExecuteSQL('DELETE FROM `' . _DB_PREFIX_ . self::TABLE . '` WHERE ' . $whereClause);
JprestaUtils::dbExecuteSQL('DELETE FROM `' . _DB_PREFIX_ . self::TABLE_BACKLINK . '` WHERE ' . $whereClause);
JprestaUtils::dbExecuteSQL('DELETE FROM `' . _DB_PREFIX_ . self::TABLE_MODULE . '` WHERE ' . $whereClause);
} else {
// Mark deleted cache contents as deleted in DB
$query = 'UPDATE `' . _DB_PREFIX_ . self::TABLE . '` SET `deleted`=1 WHERE `id` IN (' . pSQL(implode(',', array_map('intval', $cacheIdsToDelete))) . ')';
JprestaUtils::dbExecuteSQL($query);
}
}
}
public static function hasTriggerIn2H()
{
$now = date('Y-m-d H:i:00');
$now_plus_2h = new DateTime();
$now_plus_2h->modify('+2 hour');
$now_plus_2h = $now_plus_2h->format('Y-m-d H:i:00');
$hasTriggerIn2H = (int) JprestaUtils::dbGetValue('SELECT * FROM `' . _DB_PREFIX_ . self::TABLE_SPECIFIC_PRICES . '` WHERE (date_from >= \'' . pSQL($now) . '\' AND date_from <= \'' . pSQL($now_plus_2h) . '\') OR (date_to >= \'' . pSQL($now) . '\' AND date_to <= \'' . pSQL($now_plus_2h) . '\');') > 0;
if (!$hasTriggerIn2H && JprestaUtils::isModuleEnabled('groupinc')) {
// Scheduled configs are not taken into account here
$hasTriggerIn2H = (int) JprestaUtils::dbGetValue('SELECT * FROM `' . _DB_PREFIX_ . 'groupinc_configuration` WHERE (date_from >= \'' . pSQL($now) . '\' AND date_from <= \'' . pSQL($now_plus_2h) . '\') OR (date_to >= \'' . pSQL($now) . '\' AND date_to <= \'' . pSQL($now_plus_2h) . '\');', false, false) > 0;
}
return $hasTriggerIn2H;
}
public static function dropTables()
{
JprestaUtils::dbExecuteSQL('DROP TABLE IF EXISTS `' . _DB_PREFIX_ . self::TABLE_MODULE . '`;');
JprestaUtils::dbExecuteSQL('DROP TABLE IF EXISTS `' . _DB_PREFIX_ . self::TABLE_BACKLINK . '`;');
JprestaUtils::dbExecuteSQL('DROP TABLE IF EXISTS `' . _DB_PREFIX_ . self::TABLE . '`;');
JprestaUtils::dbExecuteSQL('DROP TABLE IF EXISTS `' . _DB_PREFIX_ . self::TABLE_DETAILS . '`;');
JprestaUtils::dbExecuteSQL('DROP TABLE IF EXISTS `' . _DB_PREFIX_ . self::TABLE_CONTEXTS . '`;');
JprestaUtils::dbExecuteSQL('DROP TABLE IF EXISTS `' . _DB_PREFIX_ . self::TABLE_PERFS . '`;');
JprestaUtils::dbExecuteSQL('DROP TABLE IF EXISTS `' . _DB_PREFIX_ . self::TABLE_SPECIFIC_PRICES . '`;');
JprestaUtils::dbExecuteSQL('DROP TABLE IF EXISTS `' . _DB_PREFIX_ . self::TABLE_PROFILING . '`;');
}
/**
* @param $jprestaCacheKey JprestaCacheKey
* @param $cacheSource int 0=no cache, 1=server, 2=browser, 3=static, 4=back/forward cache
* @throws PrestaShopDatabaseException
*/
public static function incrementCountHitMissed($jprestaCacheKey, $cacheSource)
{
// Stats for URL
if ($cacheSource > 0) {
// The missed are incremented in the insert() function
$url = $jprestaCacheKey->get('url');
$url = str_replace([
'?ajax=1&page_cache_dynamics_mods=1&stats=', '&ajax=1&page_cache_dynamics_mods=1&stats=',
'?ajax=1&page_cache_dynamics_mods=1', '&ajax=1&page_cache_dynamics_mods=1',
'?ajax=true&page_cache_dynamics_mods=1', '&ajax=true&page_cache_dynamics_mods=1'
], '', $url);
JprestaUtils::dbExecuteSQL('UPDATE `' . _DB_PREFIX_ . self::TABLE . '` SET count_hit=count_hit+1
WHERE `url`=' . JprestaUtils::dbToString(DB::getInstance(), $url) . '
AND id_context=' . (int)self::getOrCreateContextId($jprestaCacheKey) . ';');
}
// Stats for context
switch ($cacheSource) {
case 0:
JprestaUtils::dbExecuteSQL('UPDATE `' . _DB_PREFIX_ . self::TABLE_CONTEXTS . '` SET count_missed=count_missed+1
WHERE id=' . (int)self::getOrCreateContextId($jprestaCacheKey) . ';');
break;
case 1:
JprestaUtils::dbExecuteSQL('UPDATE `' . _DB_PREFIX_ . self::TABLE_CONTEXTS . '` SET count_hit_server=count_hit_server+1
WHERE id=' . (int)self::getOrCreateContextId($jprestaCacheKey) . ';');
break;
case 3:
JprestaUtils::dbExecuteSQL('UPDATE `' . _DB_PREFIX_ . self::TABLE_CONTEXTS . '` SET count_hit_static=count_hit_static+1
WHERE id=' . (int)self::getOrCreateContextId($jprestaCacheKey) . ';');
break;
case 2:
JprestaUtils::dbExecuteSQL('UPDATE `' . _DB_PREFIX_ . self::TABLE_CONTEXTS . '` SET count_hit_browser=count_hit_browser+1
WHERE id=' . (int)self::getOrCreateContextId($jprestaCacheKey) . ';');
break;
case 4:
JprestaUtils::dbExecuteSQL('UPDATE `' . _DB_PREFIX_ . self::TABLE_CONTEXTS . '` SET count_hit_bfcache=count_hit_bfcache+1
WHERE id=' . (int)self::getOrCreateContextId($jprestaCacheKey) . ';');
break;
}
}
/**
* @param $jprestaCacheKey JprestaCacheKey
*/
public static function incrementCountBot($jprestaCacheKey)
{
JprestaUtils::dbExecuteSQL('UPDATE `' . _DB_PREFIX_ . self::TABLE_CONTEXTS . '` SET count_bot=count_bot+1
WHERE id=' . (int)self::getOrCreateContextId($jprestaCacheKey) . ';');
}
/**
* @param $id_shop int
* @param $controller string Controller name
* @param $ttfb int TTFB in ms
* @param $cacheSource int 0=no cache, 1=server, 2=browser, 3=static, 4=back/forward cache
*/
public static function addStatsPerf($id_shop, $controller, $ttfb, $cacheSource) {
$colName = null;
switch ($cacheSource) {
case 0:
$colName = 'ttfb_ms_missed';
break;
case 1:
$colName = 'ttfb_ms_hit_server';
break;
case 3:
$colName = 'ttfb_ms_hit_static';
break;
case 2:
$colName = 'ttfb_ms_hit_browser';
break;
case 4:
$colName = 'ttfb_ms_hit_bfcache';
break;
}
if ($id_shop && $colName && $ttfb !== null) {
$id_controller = PageCache::getManagedControllerId($controller);
if ($id_controller) {
$query = 'INSERT INTO `' . _DB_PREFIX_ . self::TABLE_PERFS . '` (`' . $colName . '`,`id_controller`, `id_shop`, day_add)
VALUES (' . (int)$ttfb . ',' . JprestaUtils::dbToInt($id_controller) . ',' . JprestaUtils::dbToInt($id_shop) . ',CURRENT_DATE());';
JprestaUtils::dbExecuteSQL($query);
JprestaUtils::dbExecuteSQL('DELETE FROM `' . _DB_PREFIX_ . self::TABLE_PERFS . '` WHERE date_add < CURRENT_DATE() - interval 40 DAY;');
}
// else ignore
}
}
public static function getMostUsedSpecifics($id_shop, $limit = 4)
{
$query = 'SELECT cd.`id` as id_specifics, cd.`details` as `specifics`, sum(1) AS `count`
FROM `' . _DB_PREFIX_ . self::TABLE . '` AS cc
INNER JOIN `' . _DB_PREFIX_ . self::TABLE_CONTEXTS . '` AS ctx
ON cc.id_context = ctx.id
LEFT JOIN `' . _DB_PREFIX_ . self::TABLE_DETAILS . '` AS cd
ON ctx.id_specifics = cd.id
WHERE ctx.id_shop=' . (int) $id_shop . ' AND ctx.active=1
GROUP BY cd.`id`
ORDER BY 3 DESC
LIMIT ' . (int)$limit;
return JprestaUtils::dbSelectRows($query);
}
/**
* @param $id_details
* @return string
*/
public static function getDetailsById($id_details)
{
$query = 'SELECT `details` FROM `' . _DB_PREFIX_ . self::TABLE_DETAILS . '` AS cd WHERE cd.id = ' . (int)$id_details;
return JprestaUtils::dbGetValue($query);
}
/**
* @param $id_context int
* @return array
*/
public static function getContextById($id_context)
{
static $contexts = [];
if (!isset($contexts[$id_context])) {
$query = 'SELECT * FROM `' . _DB_PREFIX_ . self::TABLE_CONTEXTS . '` WHERE id = ' . (int)$id_context;
$rows = JprestaUtils::dbSelectRows($query);
if (count($rows) >= 1) {
$contexts[$id_context] = $rows[0];
}
else {
$contexts[$id_context] = null;
}
}
return $contexts[$id_context];
}
/**
* @param $id_shop int
* @return array
*/
public static function getAllContexts($id_shop)
{
$query = 'SELECT * FROM `' . _DB_PREFIX_ . self::TABLE_CONTEXTS . '` WHERE id_shop=' . (int) $id_shop;
return JprestaUtils::dbSelectRows($query);
}
/**
* @param $id_shop int
* @return array
*/
public static function getAllContextsAndDetails($id_shop)
{
$idCtxDetails = [];
$query = 'SELECT ctx.id as id_ctx, det.details as details FROM `' . _DB_PREFIX_ . self::TABLE_CONTEXTS . '` ctx LEFT JOIN `' . _DB_PREFIX_ . self::TABLE_DETAILS . '` det ON ctx.id_specifics=det.id WHERE ctx.active=1 AND id_shop=' . (int) $id_shop;
$rows = JprestaUtils::dbSelectRows($query);
if ($rows) {
foreach ($rows as $row) {
$idCtxDetails[(int) $row['id_ctx']] = $row['details'];
}
}
return $idCtxDetails;
}
/**
* @param $id_ctx int
*/
public static function disableContext($id_ctx)
{
$query = 'UPDATE `' . _DB_PREFIX_ . self::TABLE_CONTEXTS . '` SET active=0 WHERE id=' . (int)$id_ctx;
JprestaUtils::dbExecuteSQL($query);
}
public static function getObsoleteContextsIds($id_shop) {
$ids = [];
$whereClauses = [];
// If CSS or JS version has been incremented or option has been modified
if (Configuration::get('pagecache_depend_on_css_js')) {
$whereClauses[] = 'v_css IS NULL OR v_css<>' . (int)Configuration::get('PS_CCCCSS_VERSION');
$whereClauses[] = 'v_js IS NULL OR v_js<>' . (int)Configuration::get('PS_CCCJS_VERSION');
}
else {
$whereClauses[] = 'v_css IS NOT NULL';
$whereClauses[] = 'v_js IS NOT NULL';
}
// If a currency is not cached anymore
$pagecache_currencies_to_cache = explode(',', Configuration::get('pagecache_currencies_to_cache'));
if (count($pagecache_currencies_to_cache) > 0) {
$curIds = [];
foreach ($pagecache_currencies_to_cache as $cur_iso) {
$curIds[] = Currency::getIdByIsoCode($cur_iso);
}
$whereClauses[] = 'id_currency NOT IN (' . implode(',', $curIds) . ')';
}
// If a fake customer does not exist anymore
$obsoleteCustomerId = [];
$query = 'SELECT DISTINCT id_fake_customer FROM `' . _DB_PREFIX_ . self::TABLE_CONTEXTS . '` WHERE id_fake_customer IS NOT NULL;';
$rows = JprestaUtils::dbSelectRows($query);
foreach ($rows as $row) {
if (!JprestaUtils::dbGetValue('SELECT EXISTS (SELECT 1 FROM `' . _DB_PREFIX_ . 'customer` WHERE id_customer=' . (int)$row['id_fake_customer'] . ' LIMIT 1)')) {
$obsoleteCustomerId[] = $row['id_fake_customer'];
}
}
if (count($obsoleteCustomerId) > 0) {
$whereClauses[] = 'id_fake_customer IN ('.implode(',', $obsoleteCustomerId).')';
}
// If it is marked as inactive (obsolete)
$whereClauses[] = 'active=0';
if (count($whereClauses) > 0) {
$query = 'SELECT id FROM `' . _DB_PREFIX_ . self::TABLE_CONTEXTS . '` WHERE id_shop=' . (int)$id_shop . ' AND (' . implode(' OR ', $whereClauses) . ')';
$rows = JprestaUtils::dbSelectRows($query);
foreach ($rows as $row) {
$ids[] = $row['id'];
}
}
return $ids;
}
public static function deleteUnusedContexts() {
$query = 'DELETE ctx FROM `' . _DB_PREFIX_ . self::TABLE_CONTEXTS . '` ctx LEFT JOIN `' . _DB_PREFIX_ . self::TABLE . '` c ON ctx.id=c.id_context WHERE c.id_context IS NULL;';
JprestaUtils::dbExecuteSQL($query);
}
public static function deleteUnusedFakeUsers() {
$query = 'DELETE c FROM `' . _DB_PREFIX_ . 'customer` c WHERE email like \'%@fakeemail.com\' AND active=0 AND id_customer NOT IN (SELECT id_fake_customer FROM `' . _DB_PREFIX_ . self::TABLE_CONTEXTS . '` WHERE id_fake_customer IS NOT NULL)';
JprestaUtils::dbExecuteSQL($query);
}
/**
* @param $id_shop int
* @param $id_lang int
* @param $id_currency int
* @param $id_device int
* @param $id_country int
* @param $id_fake_customer int
* @param $id_tax_csz int
* @param $id_specifics int
* @return int|null
*/
public static function getContextIdByInfos(
$id_shop,
$id_lang,
$id_currency,
$id_device,
$id_country,
$id_fake_customer,
$id_tax_csz,
$id_specifics
) {
$whereClauses = [];
$orderBy = '';
$whereClauses[] = 'active=1';
$whereClauses[] = JprestaUtils::dbWhereIntEqual('id_shop', $id_shop);
$whereClauses[] = JprestaUtils::dbWhereIntEqual('id_lang', $id_lang);
$whereClauses[] = JprestaUtils::dbWhereIntEqual('id_currency', $id_currency);
$whereClauses[] = JprestaUtils::dbWhereIntEqual('id_fake_customer', $id_fake_customer);
$whereClauses[] = JprestaUtils::dbWhereIntEqual('id_device', $id_device);
$whereClauses[] = JprestaUtils::dbWhereIntEqual('id_country', $id_country);
if ($id_tax_csz) {
$whereClauses[] = JprestaUtils::dbWhereIntEqual('id_tax_csz', $id_tax_csz);
}
else {
$orderBy = ' ORDER BY used_by_cw DESC, date_add DESC';
}
$whereClauses[] = JprestaUtils::dbWhereIntEqual('id_specifics', $id_specifics);
if (Configuration::get('pagecache_depend_on_css_js')) {
$whereClauses[] = JprestaUtils::dbWhereIntEqual('v_css', Configuration::get('PS_CCCCSS_VERSION'));
$whereClauses[] = JprestaUtils::dbWhereIntEqual('v_js', Configuration::get('PS_CCCJS_VERSION'));
}
return JprestaUtils::dbGetValue('SELECT id FROM `' . _DB_PREFIX_ . self::TABLE_CONTEXTS . '` WHERE ' . implode(' AND ', $whereClauses) . $orderBy . ' LIMIT 1');
}
/**
* @param $jprestaCacheKey JprestaCacheKey
* @return int
*/
public static function getOrCreateContextId($jprestaCacheKey)
{
static $contexts = array();
if (!array_key_exists($jprestaCacheKey->toInt(), $contexts)) {
$id = self::getContextIdByInfos(
$jprestaCacheKey->get('id_shop'),
$jprestaCacheKey->get('id_lang'),
$jprestaCacheKey->get('id_currency'),
$jprestaCacheKey->get('id_device'),
$jprestaCacheKey->get('id_country'),
$jprestaCacheKey->get('id_fake_customer'),
$jprestaCacheKey->get('id_tax_manager'),
self::getOrCreateDetailsId($jprestaCacheKey->get('specifics'))
);
if (!$id) {
// Create the new context
$id_specifics = self::getOrCreateDetailsId($jprestaCacheKey->get('specifics'));
// uniq_key is used to have a uniq index. We cannot do it on multiple columns because they are nullable
$uniq_key = crc32(JprestaUtils::dbToInt($jprestaCacheKey->get('id_shop'))
. '|' . JprestaUtils::dbToInt($jprestaCacheKey->get('id_lang'))
. '|' . JprestaUtils::dbToInt($jprestaCacheKey->get('id_currency'))
. '|' . JprestaUtils::dbToInt($jprestaCacheKey->get('id_fake_customer'))
. '|' . JprestaUtils::dbToInt($jprestaCacheKey->get('id_device'))
. '|' . JprestaUtils::dbToInt($jprestaCacheKey->get('id_country'))
. '|' . JprestaUtils::dbToInt($jprestaCacheKey->get('id_tax_manager'))
. '|' . JprestaUtils::dbToInt($id_specifics)
. '|' . JprestaUtils::dbToInt($jprestaCacheKey->get('css_version'))
. '|' . JprestaUtils::dbToInt($jprestaCacheKey->get('js_version')));
$query = 'INSERT INTO `' . _DB_PREFIX_ . self::TABLE_CONTEXTS . '` (`context_key`,`id_shop`,`id_lang`,`id_currency`,`id_fake_customer`,`id_device`,`id_country`,`id_tax_csz`,`id_specifics`,`v_css`,`v_js`, `uniq_key`) VALUES (
UUID(),
' . JprestaUtils::dbToInt($jprestaCacheKey->get('id_shop')) . ',
' . JprestaUtils::dbToInt($jprestaCacheKey->get('id_lang')) . ',
' . JprestaUtils::dbToInt($jprestaCacheKey->get('id_currency')) . ',
' . JprestaUtils::dbToInt($jprestaCacheKey->get('id_fake_customer')) . ',
' . JprestaUtils::dbToInt($jprestaCacheKey->get('id_device')) . ',
' . JprestaUtils::dbToInt($jprestaCacheKey->get('id_country')) . ',
' . JprestaUtils::dbToInt($jprestaCacheKey->get('id_tax_manager')) . ',
' . JprestaUtils::dbToInt($id_specifics) . ',
' . JprestaUtils::dbToInt($jprestaCacheKey->get('css_version')) . ',
' . JprestaUtils::dbToInt($jprestaCacheKey->get('js_version')) . ',
' . sprintf('%u', $uniq_key);
// We use the ON DUPLICATE... to be thread safe
$query .= ') ON DUPLICATE KEY UPDATE id=id;';
JprestaUtils::dbExecuteSQL($query);
// Make sure to get the ID (don't use lastInsertId...)
$contexts[$jprestaCacheKey->toInt()] = JprestaUtils::dbGetValue('SELECT id FROM `' . _DB_PREFIX_ . self::TABLE_CONTEXTS . '` WHERE uniq_key=' . sprintf('%u', $uniq_key));
} else {
$contexts[$jprestaCacheKey->toInt()] = $id;
}
}
return $contexts[$jprestaCacheKey->toInt()];
}
public static function getContextKeyById($id_context)
{
static $contextsKeys = array();
if (!array_key_exists($id_context, $contextsKeys)) {
$contextsKeys[$id_context] = JprestaUtils::dbGetValue('SELECT context_key FROM `' . _DB_PREFIX_ . self::TABLE_CONTEXTS . '` WHERE id = ' . (int) $id_context);
}
return $contextsKeys[$id_context];
}
/**
* @param $jprestaCacheKey JprestaCacheKey
* @return array Array with 3 keys: 'hit', 'missed' and 'age' in seconds
*/
public static function getStats($jprestaCacheKey)
{
$db = Db::getInstance();
$query = 'SELECT count_hit, count_missed, TIMESTAMPDIFF(SECOND, last_gen, NOW()) as age FROM `' . _DB_PREFIX_ . self::TABLE . '`
WHERE `url`=' . JprestaUtils::dbToString($db, $jprestaCacheKey->get('url')) . '
AND `id_context`=' . (int)self::getOrCreateContextId($jprestaCacheKey);
$result = JprestaUtils::dbSelectRows($query);
if (JprestaUtils::isIterable($result) && count($result) == 1) {
return array(
'hit' => (int)$result[0]['count_hit'],
'missed' => (int)$result[0]['count_missed'],
'age' => (int)$result[0]['age']
);
}
return array('hit' => 0, 'missed' => 0, 'age' => 0);
}
/**
* @return int Number of rows of the main table
*/
public static function getMainRowsCount() {
static $rowCount = null;
if ($rowCount === null) {
$rowCount = JprestaUtils::dbGetValue('SELECT count(*) FROM `' . _DB_PREFIX_ . self::TABLE . '`');
}
return $rowCount;
}
/**
* @param $jprestaCacheKey JprestaCacheKey Cache key
* @param $cache_ttl integer configured timeout in minutes
* @return integer Number of minutes the page will leave in cache
*/
public static function getTtl($jprestaCacheKey, $cache_ttl_minutes)
{
$db = Db::getInstance();
$query = 'SELECT `deleted`, TIMESTAMPDIFF(MINUTE, last_gen, NOW()) as age FROM `' . _DB_PREFIX_ . self::TABLE . '`
WHERE `url`=' . JprestaUtils::dbToString($db, $jprestaCacheKey->get('url')) . '
AND `id_context`=' . (int)self::getOrCreateContextId($jprestaCacheKey);
$result = JprestaUtils::dbSelectRows($query);
if (JprestaUtils::isIterable($result) && count($result) == 1) {
if ($result[0]['deleted']) {
return 0;
} else {
return max(0, $cache_ttl_minutes - $result[0]['age']);
}
}
return 0;
}
public static function getStatsByURLAndContext(
$url,
$id_context
) {
/**
* ATTENTION: this code is called a lot of times by cache-warmer, it has been optimized, don't change anything
*/
$query = 'SELECT sum(count_hit) as sum_hit, sum(count_missed) as sum_missed, TIMESTAMPDIFF(MINUTE, min(last_gen), NOW()) as max_age_minutes, max(deleted) as deleted FROM `' . _DB_PREFIX_ . self::TABLE . '`
WHERE url =\'' . pSQL($url) . '\' AND id_context=' . (int)$id_context;
$result = JprestaUtils::dbGetRow($query);
if ($result && $result['sum_hit'] !== null) {
return $result;
}
return array(
'count' => 0,
'sum_hit' => 0,
'sum_missed' => 0,
'max_age_minutes' => PHP_INT_MAX,
'deleted' => 0
);
}
public static function getStatsByContext(
$id_context
) {
$query = 'SELECT sum(1) as count, sum(deleted) as count_deleted FROM `' . _DB_PREFIX_ . self::TABLE . '` WHERE id_context=' . (int)$id_context;
$result = JprestaUtils::dbGetRow($query);
if ($result && $result['count'] !== null) {
return $result;
}
return array(
'count' => 0,
'count_deleted' => 0
);
}
public static function getPerformances($id_shop)
{
$performances['count_total'] = 0;
$performances['count_context'] = 0;
$performances['count_hit'] = 0;
$performances['count_hit_server'] = 0;
$performances['count_hit_static'] = 0;
$performances['count_hit_browser'] = 0;
$performances['count_hit_bfcache'] = 0;
$performances['count_missed'] = 0;
$performances['percent_hit'] = 0.00;
$performances['percent_hit_server'] = 0.00;
$performances['percent_hit_static'] = 0.00;
$performances['percent_hit_browser'] = 0.00;
$performances['percent_hit_bfcache'] = 0.00;
$performances['percent_missed'] = 0.00;
$performances['start_date'] = null;
$query = 'SELECT sum(1) as `count_context`,
sum(count_hit_server)+sum(count_hit_static)+sum(count_hit_browser)+sum(count_hit_bfcache) as count_hit,
sum(count_hit_server) as count_hit_server,
sum(count_hit_static) as count_hit_static,
sum(count_hit_browser) as count_hit_browser,
sum(count_hit_bfcache) as count_hit_bfcache,
sum(count_missed) as count_missed,
UNIX_TIMESTAMP(MIN(date_add)) as start_date
FROM `' . _DB_PREFIX_ . self::TABLE_CONTEXTS . '` WHERE id_shop=' . (int) $id_shop;
$rows = JprestaUtils::dbSelectRows($query);
if (count($rows) > 0) {
$row = $rows[0];
$totalClics = (int) $row['count_missed'] + (int) $row['count_hit'];
if ($totalClics > 0) {
$start_date = new DateTime();
$start_date->setTimestamp($row['start_date']);
$performances['start_date'] = $start_date;
$performances['count_context'] = (int) $row['count_context'];
$performances['count_total'] = (int) $row['count_hit'] + (int) $row['count_missed'];
$performances['count_hit'] = (int) $row['count_hit'];
$performances['count_missed'] = (int) $row['count_missed'];
$performances['count_hit_server'] = (int) $row['count_hit_server'];
$performances['count_hit_static'] = (int) $row['count_hit_static'];
$performances['count_hit_browser'] = (int) $row['count_hit_browser'];
$performances['count_hit_bfcache'] = (int) $row['count_hit_bfcache'];
$performances['percent_hit'] = round($row['count_hit'] * 100 / $totalClics, 2);
$performances['percent_hit_server'] = round($row['count_hit_server'] * 100 / $totalClics, 2);
$performances['percent_hit_static'] = round($row['count_hit_static'] * 100 / $totalClics, 2);
$performances['percent_hit_browser'] = round($row['count_hit_browser'] * 100 / $totalClics, 2);
$performances['percent_hit_bfcache'] = round($row['count_hit_bfcache'] * 100 / $totalClics, 2);
$performances['percent_missed'] = round($row['count_missed'] * 100 / $totalClics, 2);
}
}
return $performances;
}
/**
* @param $details string
* @return int|null
*/
public static function getOrCreateDetailsId($details, $donotcreate = false)
{
$id_details = null;
if ($details) {
$db = Db::getInstance();
$query = 'SELECT id FROM `' . _DB_PREFIX_ . self::TABLE_DETAILS . '` WHERE `details_md5`=MD5(' . JprestaUtils::dbToString($db,
$details) . ');';
$id_details = JprestaUtils::dbGetValue($query);
if (!$donotcreate && !$id_details) {
$query = 'INSERT INTO `' . _DB_PREFIX_ . self::TABLE_DETAILS . '` (`details`,`details_md5`) VALUES (' . JprestaUtils::dbToString($db,
$details) . ',MD5(' . JprestaUtils::dbToString($db, $details) . '));';
JprestaUtils::dbExecuteSQL($query);
$id_details = $db->Insert_ID();
}
}
return (int)$id_details;
}
/**
* @param $jprestaCacheKey JprestaCacheKey Cache key with informations
* @param $controller string Controller that manage the URL
* @param $id_shop integer
* @param $id_object integer ID of the object (product, category, supplier, etc.) if any
* @param $module_ids array IDs of called module on this page
* @param $backlinks_cache_keys int[] List of cache keys present in this page
* @param int $log_level
* @param bool $stats_it False when it is from Cache-Warmer
* @throws PrestaShopDatabaseException
*/
public static function insert(
$jprestaCacheKey,
$controller,
$id_shop,
$id_object,
$module_ids,
$backlinks_cache_keys,
$log_level = 0,
$stats_it = true
) {
$startTime1 = microtime(true);
$db = Db::getInstance();
//
// Insert a new row or update stats if it exists
//
if ($stats_it) {
$onDuplicateQuery = '`count_missed`=`count_missed` + 1, last_gen = CURRENT_TIMESTAMP, `deleted` = 0';
} else {
$onDuplicateQuery = 'last_gen = CURRENT_TIMESTAMP, `deleted` = 0';
}
$id_context = self::getOrCreateContextId($jprestaCacheKey);
$query = 'INSERT INTO `' . _DB_PREFIX_ . self::TABLE . '` (`cache_key`, `url`, `id_context`, `id_controller`, `id_shop`, `id_object`, `count_missed`, `count_hit`)
VALUES (
' . JprestaUtils::dbToInt($jprestaCacheKey->toInt()) . ',
' . JprestaUtils::dbToString($db, $jprestaCacheKey->get('url')) . ',
' . JprestaUtils::dbToInt($id_context) . ',
' . JprestaUtils::dbToInt(PageCache::getManagedControllerId($controller)) . ',
' . JprestaUtils::dbToInt($id_shop) . ',
' . JprestaUtils::dbToInt($id_object) . ',
' . ($stats_it ? '1' : '0') . ',
0) ON DUPLICATE KEY UPDATE ' . $onDuplicateQuery . ';';
JprestaUtils::dbExecuteSQL($query);
$insertedId = DB::getInstance()->Insert_ID();
if ($insertedId) {
$id_pagecache = $insertedId;
}
else {
// Get the ID of the updated row
$query = 'SELECT id FROM `' . _DB_PREFIX_ . self::TABLE . '`
WHERE `url`=' . JprestaUtils::dbToString($db, $jprestaCacheKey->get('url')) . '
AND `id_context`=' . JprestaUtils::dbToInt($id_context);
$id_pagecache = JprestaUtils::dbGetValue($query, false);
if (!$id_pagecache) {
// URL is too long to be found with the index that can be limited to 255 chars
return;
}
}
//
// MODULES
//
$startTime3 = microtime(true);
$startTime4 = microtime(true);
JprestaUtils::dbExecuteSQL('DELETE FROM `' . _DB_PREFIX_ . self::TABLE_MODULE . '` WHERE `id`=' . (int)$id_pagecache . ';');
if (count($module_ids) > 0) {
$module_query = 'INSERT INTO `' . _DB_PREFIX_ . self::TABLE_MODULE . '` (`id`, `id_module`) VALUES ';
$idx = 0;
foreach ($module_ids as $id_module) {
$module_query .= '(' . $id_pagecache . ',' . $id_module . ')';
$idx++;
if ($idx < count($module_ids)) {
$module_query .= ',';
}
}
$startTime4 = microtime(true);
JprestaUtils::dbExecuteSQL($module_query . ' ON DUPLICATE KEY UPDATE id=id');
}
//
// BACKLINKS
//
$startTime5 = microtime(true);
$startTime6 = microtime(true);
JprestaUtils::dbExecuteSQL('DELETE FROM `' . _DB_PREFIX_ . self::TABLE_BACKLINK . '` WHERE `id`=' . (int)$id_pagecache . ';');
if (count($backlinks_cache_keys) > 0) {
$backlink_query = 'INSERT INTO `' . _DB_PREFIX_ . self::TABLE_BACKLINK . '` (`id`, `backlink_key`) VALUES ';
$idx = 0;
foreach ($backlinks_cache_keys as $backlink_cache_key) {
$backlink_query .= '(' . (int)$id_pagecache . ',' . JprestaUtils::dbToInt($backlink_cache_key) . ')';
$idx++;
if ($idx < count($backlinks_cache_keys)) {
$backlink_query .= ',';
}
}
$startTime6 = microtime(true);
JprestaUtils::dbExecuteSQL($backlink_query . ' ON DUPLICATE KEY UPDATE id=id');
}
if (((int)$log_level) > 0) {
JprestaUtils::addLog("PageCache | insert | added cache for $controller#$id_object during "
. number_format($startTime3 - $startTime1, 3) . '+'
. number_format($startTime4 - $startTime3, 3) . '+'
. number_format($startTime5 - $startTime4, 3) . '+'
. number_format($startTime6 - $startTime5, 3) . '+'
. number_format(microtime(true) - $startTime6, 3) . '='
. number_format(microtime(true) - $startTime1, 3)
. " second(s) with " . count($backlinks_cache_keys) . " backlinks", 1, null, null, null, true);
}
}
public static function clearCacheOfObject(
$controller,
$id_object,
$delete_linking_pages,
$action_origin = '',
$log_level = 0
) {
// Some code to avoid calling this method multiple times (can happen when saving a product for exemple)
static $already_done = array();
$key = $controller . '|' . $id_object . '|' . ($delete_linking_pages ? '1' : '0');
if (array_key_exists($key, $already_done)) {
return;
}
$already_done[$key] = true;
if ($delete_linking_pages) {
// When called with option $delete_linking_pages we can skip call without the option...
$already_done[$controller . '|' . $id_object . '|0'] = true;
}
$startTime1 = microtime(true);
if ($id_object !== null && $id_object !== false) {
$query = 'SELECT id, id_shop, url, id_context, cache_key FROM `' . _DB_PREFIX_ . self::TABLE . '`
WHERE id_controller=' . JprestaUtils::dbToInt(PageCache::getManagedControllerId($controller)) . ' AND id_object=' . ((int)$id_object) . ';';
} else if ($id_object === false) {
$query = 'SELECT id, id_shop, url, id_context, cache_key FROM `' . _DB_PREFIX_ . self::TABLE . '`
WHERE id_controller=' . JprestaUtils::dbToInt(PageCache::getManagedControllerId($controller));
} else {
$query = 'SELECT id, id_shop, url, id_context, cache_key FROM `' . _DB_PREFIX_ . self::TABLE . '`
WHERE id_controller=' . JprestaUtils::dbToInt(PageCache::getManagedControllerId($controller)) . ' AND id_object IS NULL;';
}
$results = JprestaUtils::dbSelectRows($query);
$startTime2 = microtime(true);
$keys = [];
$cacheIdsToDelete = [];
$deletedCount = 0;
if (JprestaUtils::isIterable($results)) {
foreach ($results as $result) {
if (PageCache::getCache($result['id_shop'])->delete($result['url'], self::getContextKeyById($result['id_context']))) {
$deletedCount++;
}
$keys[] = JprestaUtils::dbToInt(PageCache::getCacheKeyForBacklink($result['url']));
$cacheIdsToDelete[] = $result['id'];
}
}
if (((int)$log_level) > 0) {
JprestaUtils::addLog("PageCache | $action_origin | reffreshed $deletedCount pages from $controller#$id_object during "
. number_format($startTime2 - $startTime1, 3) . '+'
. number_format(microtime(true) - $startTime2, 3) . '='
. number_format(microtime(true) - $startTime1, 3)
. " second(s)", 1, null, null, null, true);
$startTime1 = microtime(true);
}
if ($delete_linking_pages) {
// Also add the default link of the object in case that the page has never been cached
$default_links = self::_getDefaultLinks($controller, $id_object, $log_level);
if (count($default_links) > 0) {
$remainingLinks = count($default_links);
foreach ($default_links as $default_link) {
$keys[] = JprestaUtils::dbToInt(PageCache::getCacheKeyForBacklink($default_link));
$remainingLinks--;
}
}
$startTime2 = microtime(true);
$startTime3 = microtime(true);
// Delete pages that link to these pages
$deletedCount = 0;
if (count($keys) > 0) {
$query = 'SELECT DISTINCT pc.id, pc.url, pc.id_context FROM `' . _DB_PREFIX_ . self::TABLE . '` AS pc
LEFT JOIN `' . _DB_PREFIX_ . self::TABLE_BACKLINK . '` AS bl ON (bl.id = pc.id)
WHERE pc.deleted=0 AND `backlink_key` IN (' . implode(',', array_map('intval', $keys)) . ')';
$results = JprestaUtils::dbSelectRows($query);
$startTime3 = microtime(true);
if (JprestaUtils::isIterable($results)) {
$cache = PageCache::getCache();
foreach ($results as $result) {
if ($cache->delete($result['url'], self::getContextKeyById($result['id_context']))) {
$deletedCount++;
}
$cacheIdsToDelete[] = $result['id'];
}
}
}
if (((int)$log_level) > 0) {
JprestaUtils::addLog("PageCache | $action_origin | reffreshed $deletedCount pages that were linking to $controller#$id_object during "
. number_format($startTime2 - $startTime1, 3) . '+'
. number_format($startTime3 - $startTime2, 3) . '+'
. number_format(microtime(true) - $startTime3, 3) . '='
. number_format(microtime(true) - $startTime1, 3)
. " second(s)", 1, null, null, null, true);
}
}
if (count($cacheIdsToDelete) > 0) {
// Mark deleted cache contents as deleted in DB
$query_deleted = 'UPDATE `' . _DB_PREFIX_ . self::TABLE . '` SET `deleted`=1 WHERE `id` IN (' . implode(',', $cacheIdsToDelete) . ')';
JprestaUtils::dbExecuteSQL($query_deleted);
}
}
private static function _getDefaultLinks($controller, $id_object, $log_level = 0)
{
$links = array();
try {
if ($id_object != null) {
$context = Context::getContext();
if (!isset($context->link)) {
/* Link should be initialized in the context but sometimes it is not */
$https_link = (Tools::usingSecureMode() && Configuration::get('PS_SSL_ENABLED')) ? 'https://' : 'http://';
$context->link = new Link($https_link, $https_link);
}
foreach (Language::getLanguages(true) as $language) {
$id_lang = $language['id_lang'];
// TODO Get default links of other modules's contollers
switch ($controller) {
case 'cms':
$links[] = $context->link->getCMSLink((int)($id_object), null, null, $id_lang, null, true);
break;
case 'product':
$idShop = Shop::getContextShopID();
if (!is_object($context->cart)) {
$context->cart = new Cart();
}
$product = new Product((int)$id_object, false, $id_lang, $idShop);
$ipass = Product::getProductAttributesIds((int)$id_object);
if (is_array($ipass)) {
foreach ($ipass as $ipas) {
foreach ($ipas as $ipa) {
$links[] = $context->link->getProductLink($product, null, null, null, $id_lang, $idShop, $ipa, false, true);
}
}
}
$links[] = $context->link->getProductLink((int)($id_object), null, null, null, $id_lang, null, 0, false, true);
break;
case 'category':
$links[] = $context->link->getCategoryLink((int)($id_object), null, $id_lang, null, null, true);
break;
case 'manufacturer':
$links[] = $context->link->getManufacturerLink((int)($id_object), null, $id_lang, null, true);
break;
case 'supplier':
$links[] = $context->link->getSupplierLink((int)($id_object), null, $id_lang, null, true);
break;
}
}
}
}
catch (Exception $e) {
if (((int)$log_level) > 0) {
JprestaUtils::addLog('PageCache | Cannot get the default links: ' . $e->getMessage());
}
}
return $links;
}
public static function clearCacheOfModule($module_name, $action_origin = '', $log_level = 0)
{
$startTime1 = microtime(true);
$module = Module::getInstanceByName($module_name);
if ($module instanceof Module) {
$id_module = $module->id;
if (!empty($id_module)) {
$query = 'SELECT pc.id, pc.url, pc.id_context FROM `' . _DB_PREFIX_ . self::TABLE . '` AS pc
LEFT JOIN `' . _DB_PREFIX_ . self::TABLE_MODULE . '` AS mods ON (mods.id = pc.id)
WHERE pc.deleted=0 AND `id_module`=' . ((int)$id_module);
$results = JprestaUtils::dbSelectRows($query);
$startTime2 = microtime(true);
$deletedCount = 0;
$cacheIdsToDelete = array();
if (JprestaUtils::isIterable($results)) {
foreach ($results as $result) {
if (PageCache::getCache()->delete($result['url'], self::getContextKeyById($result['id_context']))) {
$deletedCount++;
}
$cacheIdsToDelete[] = $result['id'];
}
}
if (((int)$log_level) > 0) {
JprestaUtils::addLog("PageCache | $action_origin | reffreshed $deletedCount pages containing module $module_name during "
. number_format($startTime2 - $startTime1, 3) . '+'
. number_format(microtime(true) - $startTime2, 3) . '='
. number_format(microtime(true) - $startTime1, 3)
. " second(s)", 1, null, null, null, true);
}
if (count($cacheIdsToDelete) > 0) {
// Mark deleted cache contents as deleted in DB
$query_deleted = 'UPDATE `' . _DB_PREFIX_ . self::TABLE . '` SET `deleted`=1 WHERE `id` IN (' . pSQL(implode(',', $cacheIdsToDelete)) . ')';
JprestaUtils::dbExecuteSQL($query_deleted);
}
}
}
}
public static function resetCache($ids_shop = null)
{
if (empty($ids_shop)) {
JprestaUtils::dbExecuteSQL('TRUNCATE TABLE `' . _DB_PREFIX_ . self::TABLE . '`');
JprestaUtils::dbExecuteSQL('TRUNCATE TABLE `' . _DB_PREFIX_ . self::TABLE_DETAILS . '`');
JprestaUtils::dbExecuteSQL('TRUNCATE TABLE `' . _DB_PREFIX_ . self::TABLE_CONTEXTS . '`');
JprestaUtils::dbExecuteSQL('TRUNCATE TABLE `' . _DB_PREFIX_ . self::TABLE_BACKLINK . '`');
JprestaUtils::dbExecuteSQL('TRUNCATE TABLE `' . _DB_PREFIX_ . self::TABLE_MODULE . '`');
} else {
JprestaUtils::dbExecuteSQL('DELETE bl, mods, pc, ctx FROM `' . _DB_PREFIX_ . self::TABLE . '` AS pc
LEFT JOIN `' . _DB_PREFIX_ . self::TABLE_CONTEXTS . '` AS ctx ON pc.id_context=ctx.id
LEFT JOIN `' . _DB_PREFIX_ . self::TABLE_BACKLINK . '` AS bl ON pc.id=bl.id
LEFT JOIN `' . _DB_PREFIX_ . self::TABLE_MODULE . '` AS mods ON pc.id=mods.id
WHERE pc.id_shop IS NULL OR pc.id_shop IN (' . pSQL(implode(',', array_map('intval', $ids_shop))) . ');');
}
self::deleteUnusedContexts();
}
public static function resetStatPerfs($ids_shop = null) {
if (empty($ids_shop)) {
JprestaUtils::dbExecuteSQL('TRUNCATE TABLE `' . _DB_PREFIX_ . self::TABLE_PERFS . '`');
} else {
JprestaUtils::dbExecuteSQL('DELETE FROM `' . _DB_PREFIX_ . self::TABLE_PERFS . '`
WHERE id_shop IS NULL OR id_shop IN (' . pSQL(implode(',', array_map('intval', $ids_shop))) . ');');
}
}
/**
* Request to MySQL to refresh the number of rows
*/
public static function analyzeTables()
{
JprestaUtils::dbExecuteSQL('ANALYZE TABLE `' . _DB_PREFIX_ . PageCacheDAO::TABLE . '`;', false, false);
JprestaUtils::dbExecuteSQL('ANALYZE TABLE `' . _DB_PREFIX_ . PageCacheDAO::TABLE_BACKLINK . '`;', false,
false);
JprestaUtils::dbExecuteSQL('ANALYZE TABLE `' . _DB_PREFIX_ . PageCacheDAO::TABLE_MODULE . '`;', false,
false);
JprestaUtils::dbExecuteSQL('ANALYZE TABLE `' . _DB_PREFIX_ . PageCacheDAO::TABLE_DETAILS . '`;', false,
false);
JprestaUtils::dbExecuteSQL('ANALYZE TABLE `' . _DB_PREFIX_ . PageCacheDAO::TABLE_CONTEXTS . '`;', false,
false);
JprestaUtils::dbExecuteSQL('ANALYZE TABLE `' . _DB_PREFIX_ . PageCacheDAO::TABLE_PROFILING . '`;', false,
false);
JprestaUtils::dbExecuteSQL('ANALYZE TABLE `' . _DB_PREFIX_ . PageCacheDAO::TABLE_SPECIFIC_PRICES . '`;',
false, false);
}
public static function clearAllCache()
{
JprestaUtils::dbExecuteSQL('UPDATE `' . _DB_PREFIX_ . self::TABLE . '` SET `deleted`=1;');
JprestaUtils::dbExecuteSQL('TRUNCATE TABLE `' . _DB_PREFIX_ . self::TABLE_BACKLINK . '`;');
JprestaUtils::dbExecuteSQL('TRUNCATE TABLE `' . _DB_PREFIX_ . self::TABLE_MODULE . '`;');
}
/**
* @param $id_module
* @param $description
* @param $duration
* @param integer $max_records Maximum number of records
* @return bool true if the number of records is less than $max_records
*/
public static function addProfiling($id_module, $description, $duration, $max_records = 1000)
{
try {
$db = Db::getInstance();
$query = 'INSERT INTO `' . _DB_PREFIX_ . self::TABLE_PROFILING . '` (`id_module`, `description`, `duration_ms`) VALUES (' . (int)$id_module . ', \'' . $db->escape($description) . '\', ' . (int)$duration . ');';
JprestaUtils::dbExecuteSQL($query);
return JprestaUtils::dbGetValue('SELECT COUNT(*) FROM ' . _DB_PREFIX_ . self::TABLE_PROFILING) < $max_records;
} catch (Exception $e) {
error_log('Warning, cannot insert profiling datas ' . $e->getMessage());
}
return true;
}
public static function clearProfiling($minMs = 0)
{
try {
if ($minMs === 0) {
JprestaUtils::dbExecuteSQL('TRUNCATE TABLE `' . _DB_PREFIX_ . self::TABLE_PROFILING . '`;');
} else {
JprestaUtils::dbExecuteSQL('DELETE FROM `' . _DB_PREFIX_ . self::TABLE_PROFILING . '` WHERE `duration_ms` < ' . (int)$minMs . ';');
}
} catch (Exception $e) {
error_log('Warning, cannot clear profiling datas ' . $e->getMessage());
}
}
/**
* @param $forMobile bool
* @return string
*/
public static function getMostUsedContextKeyByBots($id_shop, $forMobile = false)
{
if ($forMobile) {
$whereClause = ' WHERE active=1 AND id_shop=' . (int) $id_shop . ' AND id_fake_customer is NULL AND id_device=' . PageCache::DEVICE_MOBILE;
}
else {
$whereClause = ' WHERE active=1 AND id_shop=' . (int) $id_shop . ' AND id_fake_customer is NULL AND id_device=' . PageCache::DEVICE_COMPUTER;
}
return JprestaUtils::dbGetValue('SELECT context_key FROM `'._DB_PREFIX_.self::TABLE_CONTEXTS.'`'.$whereClause.' ORDER BY count_bot DESC LIMIT 1');
}
public static function setContextUsedByCacheWarmer($id_context)
{
JprestaUtils::dbExecuteSQL('UPDATE `' . _DB_PREFIX_ . self::TABLE_CONTEXTS . '` SET used_by_cw=1 WHERE used_by_cw=0 AND id=' . (int)$id_context);
}
}
}