Add .gitignore and .htaccess for security
🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.sublime-*
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Node (if any build tools)
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Composer dev dependencies (if installed locally)
|
||||||
|
vendor/
|
||||||
10
.htaccess
Normal file
10
.htaccess
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Apache 2.2
|
||||||
|
<IfModule !mod_authz_core.c>
|
||||||
|
Order deny,allow
|
||||||
|
Deny from all
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
|
# Apache 2.4
|
||||||
|
<IfModule mod_authz_core.c>
|
||||||
|
Require all denied
|
||||||
|
</IfModule>
|
||||||
27
composer.json
Normal file
27
composer.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "myprestarocks/prestashop-session",
|
||||||
|
"description": "Shared session tracking for PrestaShop modules - bot detection, browser/device/OS detection, session hash generation, shared mpr_sessions table",
|
||||||
|
"keywords": ["prestashop", "session", "tracking", "bot-detection", "analytics"],
|
||||||
|
"type": "library",
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "mypresta.rocks",
|
||||||
|
"email": "info@mypresta.rocks"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.1"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"MyPrestaRocks\\Session\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "1.0.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minimum-stability": "stable"
|
||||||
|
}
|
||||||
262
src/CartActionsTable.php
Normal file
262
src/CartActionsTable.php
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cart Actions Table - Cart action tracking
|
||||||
|
*
|
||||||
|
* Handles installation and management of the mpr_cart_actions table.
|
||||||
|
* Tracks add/remove/update/voucher actions for cart analysis.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* use MyPrestaRocks\Session\CartActionsTable;
|
||||||
|
*
|
||||||
|
* // In module install():
|
||||||
|
* CartActionsTable::install();
|
||||||
|
*
|
||||||
|
* // In module uninstall():
|
||||||
|
* CartActionsTable::uninstall('yourmodulename');
|
||||||
|
*
|
||||||
|
* @author mypresta.rocks <info@mypresta.rocks>
|
||||||
|
* @copyright Copyright (c) mypresta.rocks
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace MyPrestaRocks\Session;
|
||||||
|
|
||||||
|
if (!defined('_PS_VERSION_')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CartActionsTable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Table name without prefix
|
||||||
|
*/
|
||||||
|
private const TABLE_NAME = 'mpr_cart_actions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get full table name with prefix
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function getTableName()
|
||||||
|
{
|
||||||
|
return _DB_PREFIX_ . self::TABLE_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the table exists
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function tableExists()
|
||||||
|
{
|
||||||
|
$sql = "SHOW TABLES LIKE '" . self::getTableName() . "'";
|
||||||
|
return (bool) \Db::getInstance()->getValue($sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install the cart actions table
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function install()
|
||||||
|
{
|
||||||
|
$engine = _MYSQL_ENGINE_;
|
||||||
|
$charset = 'utf8mb4';
|
||||||
|
$table = self::getTableName();
|
||||||
|
|
||||||
|
$sql = "CREATE TABLE IF NOT EXISTS `{$table}` (
|
||||||
|
`id_cart_action` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
`id_session` INT(10) UNSIGNED NOT NULL,
|
||||||
|
`id_cart` INT(10) UNSIGNED NOT NULL,
|
||||||
|
`id_product` INT(10) UNSIGNED NOT NULL,
|
||||||
|
`id_product_attribute` INT(10) UNSIGNED DEFAULT 0,
|
||||||
|
`action_type` ENUM('add', 'remove', 'update', 'voucher_add', 'voucher_remove') NOT NULL,
|
||||||
|
`quantity` INT(10) UNSIGNED NOT NULL DEFAULT 1,
|
||||||
|
`quantity_before` INT(10) UNSIGNED DEFAULT NULL,
|
||||||
|
`product_name` VARCHAR(255) DEFAULT NULL,
|
||||||
|
`product_price` DECIMAL(20,6) DEFAULT NULL,
|
||||||
|
`date_add` DATETIME NOT NULL,
|
||||||
|
PRIMARY KEY (`id_cart_action`),
|
||||||
|
KEY `idx_session` (`id_session`),
|
||||||
|
KEY `idx_cart` (`id_cart`),
|
||||||
|
KEY `idx_product` (`id_product`),
|
||||||
|
KEY `idx_action_type` (`action_type`),
|
||||||
|
KEY `idx_date_add` (`date_add`),
|
||||||
|
KEY `idx_session_date` (`id_session`, `date_add`)
|
||||||
|
) ENGINE={$engine} DEFAULT CHARSET={$charset};";
|
||||||
|
|
||||||
|
return \Db::getInstance()->execute($sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uninstall the cart actions table
|
||||||
|
*
|
||||||
|
* @param string $currentModule The module being uninstalled
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function uninstall($currentModule)
|
||||||
|
{
|
||||||
|
foreach (SessionTable::getModulesUsingSession() as $moduleName) {
|
||||||
|
if ($moduleName === $currentModule) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (\Module::isInstalled($moduleName)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return \Db::getInstance()->execute("DROP TABLE IF EXISTS `" . self::getTableName() . "`");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track a cart action
|
||||||
|
*
|
||||||
|
* @param int $idSession Session ID
|
||||||
|
* @param int $idCart Cart ID
|
||||||
|
* @param int $idProduct Product ID
|
||||||
|
* @param int $idProductAttribute Product attribute ID (0 if none)
|
||||||
|
* @param string $actionType 'add', 'remove', 'update', 'voucher_add', or 'voucher_remove'
|
||||||
|
* @param int $quantity Current/new quantity
|
||||||
|
* @param int|null $quantityBefore Previous quantity (for update actions)
|
||||||
|
* @param string|null $productName Product name or voucher name
|
||||||
|
* @param float|null $productPrice Product price or voucher value
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function trackCartAction(
|
||||||
|
$idSession,
|
||||||
|
$idCart,
|
||||||
|
$idProduct,
|
||||||
|
$idProductAttribute,
|
||||||
|
$actionType,
|
||||||
|
$quantity,
|
||||||
|
$quantityBefore = null,
|
||||||
|
$productName = null,
|
||||||
|
$productPrice = null
|
||||||
|
) {
|
||||||
|
if (!$idSession || !$idCart) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$validActions = ['add', 'remove', 'update', 'voucher_add', 'voucher_remove'];
|
||||||
|
if (!in_array($actionType, $validActions)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$isVoucherAction = strpos($actionType, 'voucher_') === 0;
|
||||||
|
if (!$isVoucherAction && !$idProduct) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look up product name if not provided (only for product actions)
|
||||||
|
if ($productName === null && !$isVoucherAction && $idProduct) {
|
||||||
|
$idLang = (int) \Context::getContext()->language->id;
|
||||||
|
$productName = \Db::getInstance()->getValue(
|
||||||
|
'SELECT name FROM ' . _DB_PREFIX_ . 'product_lang
|
||||||
|
WHERE id_product = ' . (int) $idProduct . '
|
||||||
|
AND id_lang = ' . $idLang
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look up price if not provided
|
||||||
|
if ($productPrice === null && $idProduct) {
|
||||||
|
$productPrice = \Product::getPriceStatic(
|
||||||
|
(int) $idProduct,
|
||||||
|
true,
|
||||||
|
(int) $idProductAttribute ?: null,
|
||||||
|
6
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = self::getTableName();
|
||||||
|
|
||||||
|
$sql = "INSERT INTO `{$table}`
|
||||||
|
(id_session, id_cart, id_product, id_product_attribute, action_type,
|
||||||
|
quantity, quantity_before, product_name, product_price, date_add)
|
||||||
|
VALUES (
|
||||||
|
" . (int) $idSession . ",
|
||||||
|
" . (int) $idCart . ",
|
||||||
|
" . (int) $idProduct . ",
|
||||||
|
" . (int) $idProductAttribute . ",
|
||||||
|
'" . pSQL($actionType) . "',
|
||||||
|
" . (int) $quantity . ",
|
||||||
|
" . ($quantityBefore !== null ? (int) $quantityBefore : 'NULL') . ",
|
||||||
|
" . ($productName ? "'" . pSQL(substr($productName, 0, 255)) . "'" : 'NULL') . ",
|
||||||
|
" . ($productPrice !== null ? (float) $productPrice : 'NULL') . ",
|
||||||
|
NOW()
|
||||||
|
)";
|
||||||
|
|
||||||
|
return \Db::getInstance()->execute($sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cart actions for a session
|
||||||
|
*
|
||||||
|
* @param int $idSession Session ID
|
||||||
|
* @param int $limit Max records
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function getCartActionsForSession($idSession, $limit = 100)
|
||||||
|
{
|
||||||
|
$table = self::getTableName();
|
||||||
|
|
||||||
|
return \Db::getInstance()->executeS(
|
||||||
|
"SELECT * FROM `{$table}`
|
||||||
|
WHERE id_session = " . (int) $idSession . "
|
||||||
|
ORDER BY date_add ASC
|
||||||
|
LIMIT " . (int) $limit
|
||||||
|
) ?: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cart actions for multiple sessions (batch lookup)
|
||||||
|
*
|
||||||
|
* @param array $sessionIds Array of session IDs
|
||||||
|
* @param int $limit Max records per session
|
||||||
|
* @return array Grouped by session ID
|
||||||
|
*/
|
||||||
|
public static function getCartActionsForSessions(array $sessionIds, $limit = 100)
|
||||||
|
{
|
||||||
|
if (empty($sessionIds)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = self::getTableName();
|
||||||
|
$ids = array_map('intval', $sessionIds);
|
||||||
|
|
||||||
|
$actions = \Db::getInstance()->executeS(
|
||||||
|
"SELECT * FROM `{$table}`
|
||||||
|
WHERE id_session IN (" . implode(',', $ids) . ")
|
||||||
|
ORDER BY id_session, date_add ASC"
|
||||||
|
) ?: [];
|
||||||
|
|
||||||
|
$grouped = [];
|
||||||
|
foreach ($actions as $action) {
|
||||||
|
$sessId = $action['id_session'];
|
||||||
|
if (!isset($grouped[$sessId])) {
|
||||||
|
$grouped[$sessId] = [];
|
||||||
|
}
|
||||||
|
if (count($grouped[$sessId]) < $limit) {
|
||||||
|
$grouped[$sessId][] = $action;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $grouped;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean old cart action records
|
||||||
|
*
|
||||||
|
* @param int $hours Hours to keep
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function cleanOldCartActions($hours = 168)
|
||||||
|
{
|
||||||
|
$table = self::getTableName();
|
||||||
|
|
||||||
|
return \Db::getInstance()->execute(
|
||||||
|
"DELETE FROM `{$table}`
|
||||||
|
WHERE date_add < DATE_SUB(NOW(), INTERVAL " . (int) $hours . " HOUR)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
189
src/PageViewsTable.php
Normal file
189
src/PageViewsTable.php
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page Views Table - Detailed page view tracking
|
||||||
|
*
|
||||||
|
* Handles installation and management of the mpr_page_views table.
|
||||||
|
* This is an optional table for detailed funnel analysis.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* use MyPrestaRocks\Session\PageViewsTable;
|
||||||
|
*
|
||||||
|
* // In module install():
|
||||||
|
* PageViewsTable::install();
|
||||||
|
*
|
||||||
|
* // In module uninstall():
|
||||||
|
* PageViewsTable::uninstall('yourmodulename');
|
||||||
|
*
|
||||||
|
* @author mypresta.rocks <info@mypresta.rocks>
|
||||||
|
* @copyright Copyright (c) mypresta.rocks
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace MyPrestaRocks\Session;
|
||||||
|
|
||||||
|
if (!defined('_PS_VERSION_')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PageViewsTable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Table name without prefix
|
||||||
|
*/
|
||||||
|
private const TABLE_NAME = 'mpr_page_views';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get full table name with prefix
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function getTableName()
|
||||||
|
{
|
||||||
|
return _DB_PREFIX_ . self::TABLE_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the table exists
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function tableExists()
|
||||||
|
{
|
||||||
|
$sql = "SHOW TABLES LIKE '" . self::getTableName() . "'";
|
||||||
|
return (bool) \Db::getInstance()->getValue($sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install the page views table
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function install()
|
||||||
|
{
|
||||||
|
$engine = _MYSQL_ENGINE_;
|
||||||
|
$charset = 'utf8mb4';
|
||||||
|
$table = self::getTableName();
|
||||||
|
|
||||||
|
$sql = "CREATE TABLE IF NOT EXISTS `{$table}` (
|
||||||
|
`id_page_view` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
`id_session` INT(10) UNSIGNED NOT NULL,
|
||||||
|
`page_type` TINYINT(2) UNSIGNED NOT NULL,
|
||||||
|
`page_id` INT(10) UNSIGNED DEFAULT NULL,
|
||||||
|
`controller` VARCHAR(64) DEFAULT NULL,
|
||||||
|
`date_add` DATETIME NOT NULL,
|
||||||
|
PRIMARY KEY (`id_page_view`),
|
||||||
|
KEY `id_session` (`id_session`),
|
||||||
|
KEY `page_type` (`page_type`),
|
||||||
|
KEY `date_add` (`date_add`),
|
||||||
|
KEY `session_date` (`id_session`, `date_add`)
|
||||||
|
) ENGINE={$engine} DEFAULT CHARSET={$charset};";
|
||||||
|
|
||||||
|
return \Db::getInstance()->execute($sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uninstall the page views table
|
||||||
|
*
|
||||||
|
* @param string $currentModule The module being uninstalled
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function uninstall($currentModule)
|
||||||
|
{
|
||||||
|
foreach (SessionTable::getModulesUsingSession() as $moduleName) {
|
||||||
|
if ($moduleName === $currentModule) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (\Module::isInstalled($moduleName)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return \Db::getInstance()->execute("DROP TABLE IF EXISTS `" . self::getTableName() . "`");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track detailed page view
|
||||||
|
*
|
||||||
|
* @param int $idSession Session ID
|
||||||
|
* @param int $pageType Page type constant
|
||||||
|
* @param int|null $pageId Page ID
|
||||||
|
* @param string|null $controller Controller name
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function trackPageView($idSession, $pageType, $pageId = null, $controller = null)
|
||||||
|
{
|
||||||
|
if (!$idSession) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = self::getTableName();
|
||||||
|
|
||||||
|
$sql = "INSERT INTO `{$table}` (id_session, page_type, page_id, controller, date_add)
|
||||||
|
VALUES (" . (int) $idSession . ", " . (int) $pageType . ", " .
|
||||||
|
($pageId !== null ? (int) $pageId : 'NULL') . ", " .
|
||||||
|
($controller ? "'" . pSQL(substr($controller, 0, 64)) . "'" : 'NULL') . ", NOW())";
|
||||||
|
|
||||||
|
return \Db::getInstance()->execute($sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get page views for a session
|
||||||
|
*
|
||||||
|
* @param int $idSession Session ID
|
||||||
|
* @param int $limit Max records to return
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function getPageViewsForSession($idSession, $limit = 100)
|
||||||
|
{
|
||||||
|
$table = self::getTableName();
|
||||||
|
|
||||||
|
$sql = "SELECT * FROM `{$table}`
|
||||||
|
WHERE id_session = " . (int) $idSession . "
|
||||||
|
ORDER BY date_add ASC
|
||||||
|
LIMIT " . (int) $limit;
|
||||||
|
|
||||||
|
return \Db::getInstance()->executeS($sql) ?: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get page view statistics for a session
|
||||||
|
*
|
||||||
|
* @param int $idSession Session ID
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function getPageViewStats($idSession)
|
||||||
|
{
|
||||||
|
$table = self::getTableName();
|
||||||
|
|
||||||
|
$sql = "SELECT page_type, COUNT(*) as count
|
||||||
|
FROM `{$table}`
|
||||||
|
WHERE id_session = " . (int) $idSession . "
|
||||||
|
GROUP BY page_type";
|
||||||
|
|
||||||
|
$byType = \Db::getInstance()->executeS($sql) ?: [];
|
||||||
|
|
||||||
|
$stats = ['by_type' => []];
|
||||||
|
foreach ($byType as $row) {
|
||||||
|
$stats['by_type'][$row['page_type']] = (int) $row['count'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean old page view records
|
||||||
|
*
|
||||||
|
* @param int $hours Hours to keep
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function cleanOldPageViews($hours = 24)
|
||||||
|
{
|
||||||
|
$table = self::getTableName();
|
||||||
|
|
||||||
|
return \Db::getInstance()->execute(
|
||||||
|
"DELETE FROM `{$table}`
|
||||||
|
WHERE date_add < DATE_SUB(NOW(), INTERVAL " . (int) $hours . " HOUR)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
77
src/SessionInstaller.php
Normal file
77
src/SessionInstaller.php
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session Installer - Convenience class for installing all session tables
|
||||||
|
*
|
||||||
|
* Provides a single entry point for installing and uninstalling
|
||||||
|
* all shared session tables (mpr_sessions, mpr_page_views, mpr_cart_actions).
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* use MyPrestaRocks\Session\SessionInstaller;
|
||||||
|
*
|
||||||
|
* // In module install():
|
||||||
|
* SessionInstaller::installAll();
|
||||||
|
*
|
||||||
|
* // In module uninstall():
|
||||||
|
* SessionInstaller::uninstallAll('yourmodulename');
|
||||||
|
*
|
||||||
|
* @author mypresta.rocks <info@mypresta.rocks>
|
||||||
|
* @copyright Copyright (c) mypresta.rocks
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace MyPrestaRocks\Session;
|
||||||
|
|
||||||
|
if (!defined('_PS_VERSION_')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SessionInstaller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Install all shared session tables
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function installAll()
|
||||||
|
{
|
||||||
|
$result = true;
|
||||||
|
$result = $result && SessionTable::install();
|
||||||
|
$result = $result && PageViewsTable::install();
|
||||||
|
$result = $result && CartActionsTable::install();
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uninstall all shared session tables
|
||||||
|
* Only drops tables if no other module using them is installed
|
||||||
|
*
|
||||||
|
* @param string $currentModule The module being uninstalled
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function uninstallAll($currentModule)
|
||||||
|
{
|
||||||
|
$result = true;
|
||||||
|
$result = $result && CartActionsTable::uninstall($currentModule);
|
||||||
|
$result = $result && PageViewsTable::uninstall($currentModule);
|
||||||
|
$result = $result && SessionTable::uninstall($currentModule);
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up old data from all session tables
|
||||||
|
*
|
||||||
|
* @param int $sessionHours Hours to keep sessions (default 24)
|
||||||
|
* @param int $pageViewsHours Hours to keep page views (default 24)
|
||||||
|
* @param int $cartActionsHours Hours to keep cart actions (default 168 / 7 days)
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function cleanAll($sessionHours = 24, $pageViewsHours = 24, $cartActionsHours = 168)
|
||||||
|
{
|
||||||
|
$result = true;
|
||||||
|
$result = $result && SessionTable::cleanOldSessions($sessionHours);
|
||||||
|
$result = $result && PageViewsTable::cleanOldPageViews($pageViewsHours);
|
||||||
|
$result = $result && CartActionsTable::cleanOldCartActions($cartActionsHours);
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
395
src/SessionTable.php
Normal file
395
src/SessionTable.php
Normal file
@@ -0,0 +1,395 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session Table - Shared mpr_sessions table management
|
||||||
|
*
|
||||||
|
* Handles installation and uninstallation of the shared mpr_sessions table.
|
||||||
|
* Multiple modules can use this table, and it's only dropped when the last
|
||||||
|
* module using it is uninstalled.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* use MyPrestaRocks\Session\SessionTable;
|
||||||
|
*
|
||||||
|
* // In module install():
|
||||||
|
* SessionTable::install();
|
||||||
|
*
|
||||||
|
* // In module uninstall():
|
||||||
|
* SessionTable::uninstall('yourmodulename');
|
||||||
|
*
|
||||||
|
* @author mypresta.rocks <info@mypresta.rocks>
|
||||||
|
* @copyright Copyright (c) mypresta.rocks
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace MyPrestaRocks\Session;
|
||||||
|
|
||||||
|
if (!defined('_PS_VERSION_')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SessionTable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* List of modules that use the shared mpr_sessions table.
|
||||||
|
* Add your module name here when integrating.
|
||||||
|
*/
|
||||||
|
private const MODULES_USING_SESSION = [
|
||||||
|
'mprexpresscheckout',
|
||||||
|
'mprtotaldefender',
|
||||||
|
'mprtradeaccount',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table name without prefix
|
||||||
|
*/
|
||||||
|
private const TABLE_NAME = 'mpr_sessions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get full table name with prefix
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function getTableName()
|
||||||
|
{
|
||||||
|
return _DB_PREFIX_ . self::TABLE_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the session table exists
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function tableExists()
|
||||||
|
{
|
||||||
|
$sql = "SHOW TABLES LIKE '" . self::getTableName() . "'";
|
||||||
|
return (bool) \Db::getInstance()->getValue($sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install the shared session table
|
||||||
|
* Uses IF NOT EXISTS so any module can safely call this
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function install()
|
||||||
|
{
|
||||||
|
$engine = _MYSQL_ENGINE_;
|
||||||
|
$charset = 'utf8mb4';
|
||||||
|
$table = self::getTableName();
|
||||||
|
|
||||||
|
$sql = "CREATE TABLE IF NOT EXISTS `{$table}` (
|
||||||
|
`id_session` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
`id_customer` INT(10) UNSIGNED DEFAULT NULL,
|
||||||
|
`id_guest` INT(10) UNSIGNED NOT NULL,
|
||||||
|
`session_hash` VARCHAR(32) NOT NULL,
|
||||||
|
|
||||||
|
-- Core tracking (all modules)
|
||||||
|
`ip_address` VARCHAR(45) DEFAULT NULL,
|
||||||
|
`user_agent` VARCHAR(512) DEFAULT NULL,
|
||||||
|
`browser` VARCHAR(32) DEFAULT NULL,
|
||||||
|
`device_type` VARCHAR(16) DEFAULT NULL,
|
||||||
|
`os` VARCHAR(32) DEFAULT NULL,
|
||||||
|
`is_bot` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
-- Attribution tracking (mprexpresscheckout)
|
||||||
|
`source_type` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0,
|
||||||
|
`source_detail` VARCHAR(500) DEFAULT NULL,
|
||||||
|
`utm_source` VARCHAR(128) DEFAULT NULL,
|
||||||
|
`utm_medium` VARCHAR(128) DEFAULT NULL,
|
||||||
|
`utm_campaign` VARCHAR(128) DEFAULT NULL,
|
||||||
|
`utm_term` VARCHAR(255) DEFAULT NULL,
|
||||||
|
`utm_content` VARCHAR(255) DEFAULT NULL,
|
||||||
|
`gclid` VARCHAR(255) DEFAULT NULL,
|
||||||
|
`fbclid` VARCHAR(255) DEFAULT NULL,
|
||||||
|
`msclkid` VARCHAR(255) DEFAULT NULL,
|
||||||
|
`ttclid` VARCHAR(255) DEFAULT NULL,
|
||||||
|
|
||||||
|
-- Landing page tracking (mprexpresscheckout)
|
||||||
|
`landing_page_type` TINYINT(2) UNSIGNED DEFAULT NULL,
|
||||||
|
`landing_page_id` INT(10) UNSIGNED DEFAULT NULL,
|
||||||
|
`landing_url` VARCHAR(500) DEFAULT NULL,
|
||||||
|
|
||||||
|
-- Context
|
||||||
|
`id_lang` INT(10) UNSIGNED DEFAULT NULL,
|
||||||
|
`id_currency` INT(10) UNSIGNED DEFAULT NULL,
|
||||||
|
`is_first_visit` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
-- Page tracking (lightweight counters)
|
||||||
|
`pages_viewed` INT(10) UNSIGNED NOT NULL DEFAULT 0,
|
||||||
|
`last_page_type` TINYINT(2) UNSIGNED DEFAULT NULL,
|
||||||
|
`last_page_id` INT(10) UNSIGNED DEFAULT NULL,
|
||||||
|
|
||||||
|
-- Timestamps
|
||||||
|
`date_add` DATETIME NOT NULL,
|
||||||
|
`date_last_activity` DATETIME NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (`id_session`),
|
||||||
|
UNIQUE KEY `session_hash` (`session_hash`),
|
||||||
|
KEY `id_customer` (`id_customer`),
|
||||||
|
KEY `id_guest` (`id_guest`),
|
||||||
|
KEY `ip_address` (`ip_address`),
|
||||||
|
KEY `source_type` (`source_type`),
|
||||||
|
KEY `is_bot` (`is_bot`),
|
||||||
|
KEY `date_add` (`date_add`),
|
||||||
|
KEY `date_last_activity` (`date_last_activity`),
|
||||||
|
KEY `customer_sessions` (`id_customer`, `date_add`),
|
||||||
|
KEY `guest_sessions` (`id_guest`, `date_add`)
|
||||||
|
) ENGINE={$engine} DEFAULT CHARSET={$charset};";
|
||||||
|
|
||||||
|
return \Db::getInstance()->execute($sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uninstall the shared session table
|
||||||
|
* Only drops the table if no other module using it is still installed
|
||||||
|
*
|
||||||
|
* @param string $currentModule The module being uninstalled
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function uninstall($currentModule)
|
||||||
|
{
|
||||||
|
// Check if any other module using sessions is still installed
|
||||||
|
foreach (self::MODULES_USING_SESSION as $moduleName) {
|
||||||
|
if ($moduleName === $currentModule) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (\Module::isInstalled($moduleName)) {
|
||||||
|
// Another module still needs the table
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No other module needs it - drop the table
|
||||||
|
return \Db::getInstance()->execute("DROP TABLE IF EXISTS `" . self::getTableName() . "`");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get list of modules that use the session table
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function getModulesUsingSession()
|
||||||
|
{
|
||||||
|
return self::MODULES_USING_SESSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if another module using the session table is installed
|
||||||
|
*
|
||||||
|
* @param string $excludeModule Module to exclude from check
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function isAnotherModuleInstalled($excludeModule)
|
||||||
|
{
|
||||||
|
foreach (self::MODULES_USING_SESSION as $moduleName) {
|
||||||
|
if ($moduleName === $excludeModule) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (\Module::isInstalled($moduleName)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update session last activity (with 1-minute throttling)
|
||||||
|
*
|
||||||
|
* @param int $idSession Session ID
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function updateSessionActivity($idSession)
|
||||||
|
{
|
||||||
|
$table = self::getTableName();
|
||||||
|
|
||||||
|
// Check if recently updated (within 1 minute)
|
||||||
|
$sql = "SELECT 1 FROM `{$table}`
|
||||||
|
WHERE id_session = " . (int) $idSession . "
|
||||||
|
AND date_last_activity > DATE_SUB(NOW(), INTERVAL 1 MINUTE)";
|
||||||
|
|
||||||
|
if (\Db::getInstance()->getValue($sql)) {
|
||||||
|
return true; // Skip update - already recent
|
||||||
|
}
|
||||||
|
|
||||||
|
return \Db::getInstance()->execute(
|
||||||
|
"UPDATE `{$table}`
|
||||||
|
SET date_last_activity = NOW()
|
||||||
|
WHERE id_session = " . (int) $idSession
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get active session by hash
|
||||||
|
*
|
||||||
|
* @param string $sessionHash
|
||||||
|
* @param int $timeoutMinutes
|
||||||
|
* @return int|null Session ID or null
|
||||||
|
*/
|
||||||
|
public static function getActiveSessionId($sessionHash, $timeoutMinutes = 60)
|
||||||
|
{
|
||||||
|
$table = self::getTableName();
|
||||||
|
|
||||||
|
$sql = "SELECT id_session FROM `{$table}`
|
||||||
|
WHERE session_hash = '" . pSQL($sessionHash) . "'
|
||||||
|
AND date_last_activity > DATE_SUB(NOW(), INTERVAL " . (int) $timeoutMinutes . " MINUTE)";
|
||||||
|
|
||||||
|
$result = \Db::getInstance()->getValue($sql);
|
||||||
|
return $result ? (int) $result : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Link guest sessions to customer (on login/registration)
|
||||||
|
*
|
||||||
|
* @param int $idGuest
|
||||||
|
* @param int $idCustomer
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function linkGuestToCustomer($idGuest, $idCustomer)
|
||||||
|
{
|
||||||
|
if (!$idGuest || !$idCustomer) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return \Db::getInstance()->update(
|
||||||
|
self::TABLE_NAME,
|
||||||
|
['id_customer' => (int) $idCustomer],
|
||||||
|
'id_guest = ' . (int) $idGuest . ' AND id_customer IS NULL'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean old sessions
|
||||||
|
*
|
||||||
|
* @param int $hours Hours to keep (default 24)
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function cleanOldSessions($hours = 24)
|
||||||
|
{
|
||||||
|
$table = self::getTableName();
|
||||||
|
|
||||||
|
return \Db::getInstance()->execute(
|
||||||
|
"DELETE FROM `{$table}`
|
||||||
|
WHERE date_last_activity < DATE_SUB(NOW(), INTERVAL " . (int) $hours . " HOUR)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get active session count
|
||||||
|
*
|
||||||
|
* @param int $timeoutMinutes
|
||||||
|
* @param bool $excludeBots
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public static function getActiveSessionCount($timeoutMinutes = 60, $excludeBots = true)
|
||||||
|
{
|
||||||
|
$table = self::getTableName();
|
||||||
|
|
||||||
|
$sql = "SELECT COUNT(*) FROM `{$table}`
|
||||||
|
WHERE date_last_activity > DATE_SUB(NOW(), INTERVAL " . (int) $timeoutMinutes . " MINUTE)";
|
||||||
|
|
||||||
|
if ($excludeBots) {
|
||||||
|
$sql .= " AND is_bot = 0";
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int) \Db::getInstance()->getValue($sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get sessions for an IP address
|
||||||
|
*
|
||||||
|
* @param string $ipAddress
|
||||||
|
* @param int $hours Time window (0 for all)
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function getSessionsForIP($ipAddress, $hours = 24)
|
||||||
|
{
|
||||||
|
$table = self::getTableName();
|
||||||
|
|
||||||
|
$sql = "SELECT * FROM `{$table}` WHERE ip_address = '" . pSQL($ipAddress) . "'";
|
||||||
|
|
||||||
|
if ($hours > 0) {
|
||||||
|
$sql .= " AND date_add >= DATE_SUB(NOW(), INTERVAL " . (int) $hours . " HOUR)";
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= " ORDER BY date_add DESC";
|
||||||
|
|
||||||
|
return \Db::getInstance()->executeS($sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count sessions for an IP address
|
||||||
|
*
|
||||||
|
* @param string $ipAddress
|
||||||
|
* @param int $hours Time window
|
||||||
|
* @param bool $excludeBots Exclude bot sessions
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public static function countSessionsForIP($ipAddress, $hours = 24, $excludeBots = true)
|
||||||
|
{
|
||||||
|
$table = self::getTableName();
|
||||||
|
|
||||||
|
$sql = "SELECT COUNT(*) FROM `{$table}` WHERE ip_address = '" . pSQL($ipAddress) . "'";
|
||||||
|
|
||||||
|
if ($excludeBots) {
|
||||||
|
$sql .= " AND is_bot = 0";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($hours > 0) {
|
||||||
|
$sql .= " AND date_add >= DATE_SUB(NOW(), INTERVAL " . (int) $hours . " HOUR)";
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int) \Db::getInstance()->getValue($sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track page view for a session (lightweight - just increment counter)
|
||||||
|
*
|
||||||
|
* @param int $idSession Session ID
|
||||||
|
* @param int|null $pageType Page type constant
|
||||||
|
* @param int|null $pageId Page ID (product, category, etc.)
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function trackPageView($idSession, $pageType = null, $pageId = null)
|
||||||
|
{
|
||||||
|
if (!$idSession) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = self::getTableName();
|
||||||
|
|
||||||
|
$sql = "UPDATE `{$table}` SET
|
||||||
|
pages_viewed = pages_viewed + 1,
|
||||||
|
last_page_type = " . ($pageType !== null ? (int) $pageType : 'NULL') . ",
|
||||||
|
last_page_id = " . ($pageId !== null ? (int) $pageId : 'NULL') . ",
|
||||||
|
date_last_activity = NOW()
|
||||||
|
WHERE id_session = " . (int) $idSession;
|
||||||
|
|
||||||
|
return \Db::getInstance()->execute($sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track page view by session hash (optimized - single query when session exists)
|
||||||
|
*
|
||||||
|
* @param string $sessionHash Session hash
|
||||||
|
* @param int|null $pageType Page type constant
|
||||||
|
* @param int|null $pageId Page ID
|
||||||
|
* @param int $timeoutMinutes Session timeout
|
||||||
|
* @return bool True if page was tracked, false if session not found
|
||||||
|
*/
|
||||||
|
public static function trackPageViewByHash($sessionHash, $pageType = null, $pageId = null, $timeoutMinutes = 60)
|
||||||
|
{
|
||||||
|
$table = self::getTableName();
|
||||||
|
|
||||||
|
$sql = "UPDATE `{$table}` SET
|
||||||
|
pages_viewed = pages_viewed + 1,
|
||||||
|
last_page_type = " . ($pageType !== null ? (int) $pageType : 'NULL') . ",
|
||||||
|
last_page_id = " . ($pageId !== null ? (int) $pageId : 'NULL') . ",
|
||||||
|
date_last_activity = NOW()
|
||||||
|
WHERE session_hash = '" . pSQL($sessionHash) . "'
|
||||||
|
AND date_last_activity > DATE_SUB(NOW(), INTERVAL " . (int) $timeoutMinutes . " MINUTE)";
|
||||||
|
|
||||||
|
\Db::getInstance()->execute($sql);
|
||||||
|
|
||||||
|
return \Db::getInstance()->Affected_Rows() > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
364
src/SessionTrait.php
Normal file
364
src/SessionTrait.php
Normal file
@@ -0,0 +1,364 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session Trait - Shared session detection and tracking for PrestaShop modules
|
||||||
|
*
|
||||||
|
* Provides common session functionality used by multiple MPR modules:
|
||||||
|
* - Bot detection
|
||||||
|
* - Browser detection
|
||||||
|
* - Device type detection
|
||||||
|
* - OS detection
|
||||||
|
* - Session hash generation
|
||||||
|
* - IP address handling
|
||||||
|
* - User agent handling
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* use MyPrestaRocks\Session\SessionTrait;
|
||||||
|
*
|
||||||
|
* class YourSessionClass extends ObjectModel {
|
||||||
|
* use SessionTrait;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @author mypresta.rocks <info@mypresta.rocks>
|
||||||
|
* @copyright Copyright (c) mypresta.rocks
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace MyPrestaRocks\Session;
|
||||||
|
|
||||||
|
if (!defined('_PS_VERSION_')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
trait SessionTrait
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Page type values for last_page_type column
|
||||||
|
* Note: Using static array instead of constants for PHP 7.4 compatibility
|
||||||
|
* (traits cannot have constants in PHP < 8.2)
|
||||||
|
*/
|
||||||
|
protected static $PAGE_TYPES = [
|
||||||
|
'HOME' => 1,
|
||||||
|
'CATEGORY' => 2,
|
||||||
|
'PRODUCT' => 3,
|
||||||
|
'CMS' => 4,
|
||||||
|
'CART' => 5,
|
||||||
|
'CHECKOUT' => 6,
|
||||||
|
'ORDER_CONFIRMATION' => 7,
|
||||||
|
'MY_ACCOUNT' => 8,
|
||||||
|
'SEARCH' => 9,
|
||||||
|
'MANUFACTURER' => 10,
|
||||||
|
'SUPPLIER' => 11,
|
||||||
|
'CONTACT' => 12,
|
||||||
|
'OTHER' => 99,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect if request is from a bot
|
||||||
|
*
|
||||||
|
* @param string|null $userAgent Optional user agent (uses $_SERVER if not provided)
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected static function mprIsBot($userAgent = null)
|
||||||
|
{
|
||||||
|
if ($userAgent === null) {
|
||||||
|
if (!isset($_SERVER['HTTP_USER_AGENT'])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$userAgent = $_SERVER['HTTP_USER_AGENT'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$userAgent = strtolower($userAgent);
|
||||||
|
|
||||||
|
$botPatterns = [
|
||||||
|
// Search engine crawlers
|
||||||
|
'googlebot', 'bingbot', 'slurp', 'duckduckbot', 'baiduspider',
|
||||||
|
'yandexbot', 'sogou', 'exabot',
|
||||||
|
// Social media bots
|
||||||
|
'facebot', 'facebookexternalhit', 'twitterbot', 'linkedinbot',
|
||||||
|
'whatsapp', 'telegrambot', 'slackbot', 'discordbot', 'pinterestbot',
|
||||||
|
// SEO tools
|
||||||
|
'applebot', 'semrushbot', 'ahrefsbot', 'mj12bot', 'dotbot',
|
||||||
|
'rogerbot', 'petalbot', 'screaming frog',
|
||||||
|
// Archive bots
|
||||||
|
'ia_archiver', 'archive.org_bot',
|
||||||
|
// Generic patterns
|
||||||
|
'crawler', 'spider', 'bot.htm', 'bot.php', 'crawl',
|
||||||
|
// HTTP clients
|
||||||
|
'wget', 'curl', 'python-requests', 'python/', 'scrapy',
|
||||||
|
'php/', 'java/', 'go-http-client', 'httpclient', 'apache-httpclient',
|
||||||
|
'libwww', 'lwp-', 'mechanize',
|
||||||
|
// Headless browsers
|
||||||
|
'headlesschrome', 'phantomjs', 'selenium', 'webdriver', 'puppeteer',
|
||||||
|
// API testing tools
|
||||||
|
'postman', 'insomnia',
|
||||||
|
// Monitoring
|
||||||
|
'pingdom', 'uptimerobot', 'monitoring', 'check_http',
|
||||||
|
// Generic http pattern (often bots)
|
||||||
|
'http',
|
||||||
|
'httrack',
|
||||||
|
'mediapartners',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($botPatterns as $pattern) {
|
||||||
|
if (strpos($userAgent, $pattern) !== false) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect browser from user agent
|
||||||
|
*
|
||||||
|
* @param string|null $userAgent Optional user agent
|
||||||
|
* @return string Browser name (chrome, firefox, safari, edge, opera, ie, other)
|
||||||
|
*/
|
||||||
|
protected static function mprDetectBrowser($userAgent = null)
|
||||||
|
{
|
||||||
|
if ($userAgent === null) {
|
||||||
|
if (!isset($_SERVER['HTTP_USER_AGENT'])) {
|
||||||
|
return 'other';
|
||||||
|
}
|
||||||
|
$userAgent = $_SERVER['HTTP_USER_AGENT'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edge must be checked before Chrome (Edge contains "Chrome" in UA)
|
||||||
|
if (strpos($userAgent, 'Edg') !== false || strpos($userAgent, 'Edge') !== false) {
|
||||||
|
return 'edge';
|
||||||
|
}
|
||||||
|
// Chrome must be checked before Safari (Chrome contains "Safari" in UA)
|
||||||
|
if (strpos($userAgent, 'Chrome') !== false && strpos($userAgent, 'Chromium') === false) {
|
||||||
|
return 'chrome';
|
||||||
|
}
|
||||||
|
if (strpos($userAgent, 'Firefox') !== false) {
|
||||||
|
return 'firefox';
|
||||||
|
}
|
||||||
|
if (strpos($userAgent, 'Safari') !== false && strpos($userAgent, 'Chrome') === false) {
|
||||||
|
return 'safari';
|
||||||
|
}
|
||||||
|
if (strpos($userAgent, 'OPR') !== false || strpos($userAgent, 'Opera') !== false) {
|
||||||
|
return 'opera';
|
||||||
|
}
|
||||||
|
if (strpos($userAgent, 'MSIE') !== false || strpos($userAgent, 'Trident') !== false) {
|
||||||
|
return 'ie';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'other';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect device type from user agent
|
||||||
|
*
|
||||||
|
* @param string|null $userAgent Optional user agent
|
||||||
|
* @return string Device type (mobile, tablet, desktop)
|
||||||
|
*/
|
||||||
|
protected static function mprDetectDeviceType($userAgent = null)
|
||||||
|
{
|
||||||
|
if ($userAgent === null) {
|
||||||
|
if (!isset($_SERVER['HTTP_USER_AGENT'])) {
|
||||||
|
return 'desktop';
|
||||||
|
}
|
||||||
|
$userAgent = $_SERVER['HTTP_USER_AGENT'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$userAgentLower = strtolower($userAgent);
|
||||||
|
|
||||||
|
// Check mobile first (phones)
|
||||||
|
if (preg_match('/mobile|android.*mobile|iphone|ipod|phone|blackberry|iemobile|opera mini|opera mobi/i', $userAgentLower)) {
|
||||||
|
return 'mobile';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check tablet
|
||||||
|
if (preg_match('/tablet|ipad|android(?!.*mobile)|kindle|silk/i', $userAgentLower)) {
|
||||||
|
return 'tablet';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'desktop';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect operating system from user agent
|
||||||
|
*
|
||||||
|
* @param string|null $userAgent Optional user agent
|
||||||
|
* @return string OS name (windows, macos, android, ios, linux, other)
|
||||||
|
*/
|
||||||
|
protected static function mprDetectOS($userAgent = null)
|
||||||
|
{
|
||||||
|
if ($userAgent === null) {
|
||||||
|
if (!isset($_SERVER['HTTP_USER_AGENT'])) {
|
||||||
|
return 'other';
|
||||||
|
}
|
||||||
|
$userAgent = $_SERVER['HTTP_USER_AGENT'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strpos($userAgent, 'Windows') !== false) {
|
||||||
|
return 'windows';
|
||||||
|
}
|
||||||
|
if (strpos($userAgent, 'Mac OS X') !== false) {
|
||||||
|
return 'macos';
|
||||||
|
}
|
||||||
|
if (strpos($userAgent, 'Android') !== false) {
|
||||||
|
return 'android';
|
||||||
|
}
|
||||||
|
if (strpos($userAgent, 'iPhone') !== false || strpos($userAgent, 'iPad') !== false) {
|
||||||
|
return 'ios';
|
||||||
|
}
|
||||||
|
if (strpos($userAgent, 'Linux') !== false) {
|
||||||
|
return 'linux';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'other';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate session hash
|
||||||
|
*
|
||||||
|
* @param int $idGuest Guest ID
|
||||||
|
* @param string|null $extra Additional data to include in hash (e.g., campaign params)
|
||||||
|
* @param int $timeBucket Time bucket in seconds (default 30 minutes)
|
||||||
|
* @return string MD5 hash
|
||||||
|
*/
|
||||||
|
protected static function mprGenerateSessionHash($idGuest, $extra = null, $timeBucket = 1800)
|
||||||
|
{
|
||||||
|
$timestamp = floor(time() / $timeBucket);
|
||||||
|
$hashData = $idGuest . '_' . $timestamp;
|
||||||
|
|
||||||
|
if ($extra) {
|
||||||
|
$hashData .= '_' . $extra;
|
||||||
|
}
|
||||||
|
|
||||||
|
return md5($hashData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current IP address
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected static function mprGetIPAddress()
|
||||||
|
{
|
||||||
|
return \Tools::getRemoteAddr();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current user agent
|
||||||
|
*
|
||||||
|
* @param int $maxLength Maximum length to return
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
protected static function mprGetUserAgent($maxLength = 512)
|
||||||
|
{
|
||||||
|
if (!isset($_SERVER['HTTP_USER_AGENT'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return substr($_SERVER['HTTP_USER_AGENT'], 0, $maxLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect current page type from controller
|
||||||
|
*
|
||||||
|
* @return array [page_type, page_id]
|
||||||
|
*/
|
||||||
|
protected static function mprDetectPageType()
|
||||||
|
{
|
||||||
|
$controller = \Tools::getValue('controller', '');
|
||||||
|
|
||||||
|
switch ($controller) {
|
||||||
|
case 'index':
|
||||||
|
return [self::$PAGE_TYPES['HOME'], null];
|
||||||
|
|
||||||
|
case 'category':
|
||||||
|
$pageId = (int) \Tools::getValue('id_category');
|
||||||
|
return [self::$PAGE_TYPES['CATEGORY'], $pageId ?: null];
|
||||||
|
|
||||||
|
case 'product':
|
||||||
|
$pageId = (int) \Tools::getValue('id_product');
|
||||||
|
return [self::$PAGE_TYPES['PRODUCT'], $pageId ?: null];
|
||||||
|
|
||||||
|
case 'cms':
|
||||||
|
$pageId = (int) \Tools::getValue('id_cms');
|
||||||
|
return [self::$PAGE_TYPES['CMS'], $pageId ?: null];
|
||||||
|
|
||||||
|
case 'cart':
|
||||||
|
return [self::$PAGE_TYPES['CART'], null];
|
||||||
|
|
||||||
|
case 'order':
|
||||||
|
case 'orderopc':
|
||||||
|
case 'checkout':
|
||||||
|
case 'mprexpresscheckoutcheckout':
|
||||||
|
return [self::$PAGE_TYPES['CHECKOUT'], null];
|
||||||
|
|
||||||
|
case 'orderconfirmation':
|
||||||
|
case 'order-confirmation':
|
||||||
|
$pageId = (int) \Tools::getValue('id_order');
|
||||||
|
return [self::$PAGE_TYPES['ORDER_CONFIRMATION'], $pageId ?: null];
|
||||||
|
|
||||||
|
case 'my-account':
|
||||||
|
case 'identity':
|
||||||
|
case 'addresses':
|
||||||
|
case 'address':
|
||||||
|
case 'history':
|
||||||
|
case 'order-detail':
|
||||||
|
case 'order-follow':
|
||||||
|
case 'order-slip':
|
||||||
|
return [self::$PAGE_TYPES['MY_ACCOUNT'], null];
|
||||||
|
|
||||||
|
case 'search':
|
||||||
|
return [self::$PAGE_TYPES['SEARCH'], null];
|
||||||
|
|
||||||
|
case 'manufacturer':
|
||||||
|
$pageId = (int) \Tools::getValue('id_manufacturer');
|
||||||
|
return [self::$PAGE_TYPES['MANUFACTURER'], $pageId ?: null];
|
||||||
|
|
||||||
|
case 'supplier':
|
||||||
|
$pageId = (int) \Tools::getValue('id_supplier');
|
||||||
|
return [self::$PAGE_TYPES['SUPPLIER'], $pageId ?: null];
|
||||||
|
|
||||||
|
case 'contact':
|
||||||
|
case 'contact-us':
|
||||||
|
return [self::$PAGE_TYPES['CONTACT'], null];
|
||||||
|
|
||||||
|
default:
|
||||||
|
return [self::$PAGE_TYPES['OTHER'], null];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get page type name for display
|
||||||
|
*
|
||||||
|
* @param int $pageType Page type constant
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function getPageTypeName($pageType)
|
||||||
|
{
|
||||||
|
$names = [
|
||||||
|
self::$PAGE_TYPES['HOME'] => 'Home',
|
||||||
|
self::$PAGE_TYPES['CATEGORY'] => 'Category',
|
||||||
|
self::$PAGE_TYPES['PRODUCT'] => 'Product',
|
||||||
|
self::$PAGE_TYPES['CMS'] => 'CMS',
|
||||||
|
self::$PAGE_TYPES['CART'] => 'Cart',
|
||||||
|
self::$PAGE_TYPES['CHECKOUT'] => 'Checkout',
|
||||||
|
self::$PAGE_TYPES['ORDER_CONFIRMATION'] => 'Order Confirmation',
|
||||||
|
self::$PAGE_TYPES['MY_ACCOUNT'] => 'My Account',
|
||||||
|
self::$PAGE_TYPES['SEARCH'] => 'Search',
|
||||||
|
self::$PAGE_TYPES['MANUFACTURER'] => 'Manufacturer',
|
||||||
|
self::$PAGE_TYPES['SUPPLIER'] => 'Supplier',
|
||||||
|
self::$PAGE_TYPES['CONTACT'] => 'Contact',
|
||||||
|
self::$PAGE_TYPES['OTHER'] => 'Other',
|
||||||
|
];
|
||||||
|
|
||||||
|
return $names[$pageType] ?? 'Unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get page types array
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function getPageTypes()
|
||||||
|
{
|
||||||
|
return self::$PAGE_TYPES;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user