Image_Optimizer_NAT-2.0-2.2/upload/admin/model/module/img_opti.php

5035 lines
261 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
if (!defined('DIR_APPLICATION')) { exit; }
class ModelModuleImgOpti extends Model {
private $translations = array();
private function paginateScanResult($rows, $data, $headers, $extra = array()) {
$start = isset($data['start']) ? max(0, (int)$data['start']) : 0;
$limit = isset($data['limit']) ? max(0, (int)$data['limit']) : 0;
$total = count($rows);
if ($limit > 0) {
$sliced_rows = array_slice($rows, $start, $limit);
} else {
$sliced_rows = $rows;
}
$result = array(
'success' => true,
'total' => $total,
'start' => $start,
'limit' => $limit,
'headers' => $headers,
'rows' => $sliced_rows
);
return array_merge($result, $extra);
}
private function formatFixResult($processed = 0, $total = 0) {
return array(
'success' => true,
'processed' => (int)$processed,
'total' => (int)$total
);
}
public function get_cached_grid($data) {
$this->loadLanguageSafe();
$tool = isset($data['tool']) ? preg_replace('/[^a-zA-Z0-9_]/', '', (string)$data['tool']) : '';
if ($tool === 'broken') $tool = 'broken_db';
$cache_file = DIR_CACHE . 'img_opti_' . $tool . '_grid.json';
if (!file_exists($cache_file)) {
return array('success' => false, 'rows' => array(), 'total' => 0);
}
$rows = json_decode(file_get_contents($cache_file), true);
if (!is_array($rows)) $rows = array();
return $this->paginateScanResult($rows, $data, array(
$this->language->get('text_th_preview'),
$this->language->get('text_th_entity'),
$this->language->get('text_th_before'),
$this->language->get('text_th_after'),
$this->language->get('text_th_action')
));
}
public function backupCatalogTables($tool_name) {
$tables = array(
DB_PREFIX . 'product',
DB_PREFIX . 'product_image',
DB_PREFIX . 'category',
DB_PREFIX . 'manufacturer'
);
$backup_dir = DIR_IMAGE . 'catalog_trash/backup/';
if (!is_dir($backup_dir)) {
@mkdir($backup_dir, 0755, true);
}
$file_name = 'backup_db_' . $tool_name . '_' . date('Ymd_His') . '.sql';
$file_path = $backup_dir . $file_name;
$fp = @fopen($file_path, 'w');
if (!$fp) {
$this->registerModuleFolder('catalog_trash/backup');
return $file_name;
}
fwrite($fp, "-- Database Backup for Tool: " . $tool_name . "\n");
fwrite($fp, "-- Date: " . date('Y-m-d H:i:s') . "\n\n");
foreach ($tables as $table) {
fwrite($fp, "DROP TABLE IF EXISTS `" . $table . "`;\n");
$query = $this->db->query("SHOW CREATE TABLE `" . $table . "`");
if ($query->num_rows) {
fwrite($fp, $query->row['Create Table'] . ";\n\n");
}
$chunk_size = 200;
$offset = 0;
do {
$query_rows = $this->db->query("SELECT * FROM `" . $table . "` LIMIT " . (int)$chunk_size . " OFFSET " . (int)$offset);
foreach ($query_rows->rows as $row) {
$fields = array();
$values = array();
foreach ($row as $key => $value) {
$fields[] = "`" . $key . "`";
if ($value === null) {
$values[] = "NULL";
} else {
$values[] = "'" . $this->db->escape($value) . "'";
}
}
fwrite($fp, "INSERT INTO `" . $table . "` (" . implode(', ', $fields) . ") VALUES (" . implode(', ', $values) . ");\n");
}
$offset += $chunk_size;
} while ($query_rows->num_rows === $chunk_size);
fwrite($fp, "\n");
}
fclose($fp);
$this->registerModuleFolder('catalog_trash/backup');
return $file_name;
}
public function quarantineFile($relative_path, $tool_name) {
$clean_rel = str_replace(array('../', '..\\', "\0"), '', (string)$relative_path);
$src = DIR_IMAGE . $clean_rel;
if (!is_file($src)) return false;
$dest = DIR_IMAGE . 'catalog_trash/' . $tool_name . '/' . ltrim($clean_rel, '/');
$dest_dir = $this->mb_dirname($dest);
if (!is_dir($dest_dir)) {
@mkdir($dest_dir, 0755, true);
}
$this->registerModuleFolder('catalog_trash/' . $tool_name);
if (file_exists($dest)) {
@unlink($dest);
}
if (!@rename($src, $dest)) {
if (@copy($src, $dest)) {
@unlink($src);
return true;
}
return false;
}
return true;
}
public function registerModuleFolder($folder_name) {
$folder_name = preg_replace('/[^a-zA-Z0-9_\-\/]/', '', (string)$folder_name);
if (empty($folder_name)) return;
$query = $this->db->query("SELECT `value` FROM `" . DB_PREFIX . "setting` WHERE `code` = 'module_img_opti' AND `key` = 'module_img_opti_folders'");
$folders = ($query->num_rows && $query->row['value']) ? json_decode($query->row['value'], true) : array();
if (!in_array($folder_name, $folders)) {
$folders[] = $folder_name;
$this->db->query("DELETE FROM `" . DB_PREFIX . "setting` WHERE `code` = 'module_img_opti' AND `key` = 'module_img_opti_folders'");
$this->db->query("INSERT INTO `" . DB_PREFIX . "setting` SET `store_id` = '0', `code` = 'module_img_opti', `key` = 'module_img_opti_folders', `value` = '" . $this->db->escape(json_encode($folders)) . "'");
}
}
private function getSearchLinks($name, $model = '') {
$query_parts = array();
if ($name) $query_parts[] = trim($name);
if ($model) $query_parts[] = trim($model);
$search_query = implode(' ', $query_parts);
if (!$search_query) return '';
$google_url = 'https://www.google.com/search?tbm=isch&q=' . urlencode($search_query);
$yandex_url = 'https://yandex.ru/images/search?text=' . urlencode($search_query);
$html = ' <a href="' . $google_url . '" target="_blank" title="Search Google Images" style="margin-left: 5px; color: #4285F4;"><i class="fa fa-google"></i></a>';
$html .= ' <a href="' . $yandex_url . '" target="_blank" title="Search Yandex Images" style="margin-left: 5px; color: #FF0000;"><i class="fa fa-yandex"></i></a>';
return $html;
}
private function getEntitiesByImagePath($image_path) {
$entities = array();
$escaped = $this->db->escape((string)$image_path);
$catalog_url = (defined('HTTPS_CATALOG') && HTTPS_CATALOG) ? HTTPS_CATALOG : (defined('HTTP_CATALOG') ? HTTP_CATALOG : '');
$query = $this->db->query("SELECT p.product_id, p.model, p.status, p.date_available, (SELECT p2s.store_id FROM `" . DB_PREFIX . "product_to_store` p2s WHERE p2s.product_id = p.product_id AND p2s.store_id = '" . (int)$this->config->get('config_store_id') . "' LIMIT 1) AS store_id, (SELECT name FROM `" . DB_PREFIX . "product_description` pd WHERE pd.product_id = p.product_id AND pd.language_id = '" . (int)$this->config->get('config_language_id') . "' LIMIT 1) AS name FROM `" . DB_PREFIX . "product` p WHERE p.image = '" . $escaped . "'");
foreach ($query->rows as $row) {
$admin_link = $this->url->link('catalog/product/edit', 'user_token=' . $this->session->data['user_token'] . '&product_id=' . (int)$row['product_id'], true);
$catalog_link_html = '';
if (isset($row['status']) && (int)$row['status'] === 1 && (empty($row['date_available']) || strtotime($row['date_available']) <= time()) && (isset($row['store_id']) && $row['store_id'] !== null)) {
$catalog_link = $catalog_url . 'index.php?route=product/product&product_id=' . (int)$row['product_id'];
$catalog_link_html = ' <a href="' . $catalog_link . '" target="_blank" title="On Catalog"><i class="fa fa-external-link text-muted"></i></a>';
}
$search_links = $this->getSearchLinks($row['name'], $row['model']);
$entities[] = '<span class="label label-info">Product</span> <a href="' . $admin_link . '" target="_blank">' . htmlspecialchars($row['name'] ? $row['name'] : 'Product ID ' . $row['product_id'], ENT_QUOTES, 'UTF-8') . '</a>' . $catalog_link_html . $search_links;
}
$query = $this->db->query("SELECT pi.product_id, p.model, p.status, p.date_available, (SELECT p2s.store_id FROM `" . DB_PREFIX . "product_to_store` p2s WHERE p2s.product_id = pi.product_id AND p2s.store_id = '" . (int)$this->config->get('config_store_id') . "' LIMIT 1) AS store_id, (SELECT name FROM `" . DB_PREFIX . "product_description` pd WHERE pd.product_id = pi.product_id AND pd.language_id = '" . (int)$this->config->get('config_language_id') . "' LIMIT 1) AS name FROM `" . DB_PREFIX . "product_image` pi LEFT JOIN `" . DB_PREFIX . "product` p ON (pi.product_id = p.product_id) WHERE pi.image = '" . $escaped . "'");
foreach ($query->rows as $row) {
$admin_link = $this->url->link('catalog/product/edit', 'user_token=' . $this->session->data['user_token'] . '&product_id=' . (int)$row['product_id'], true);
$catalog_link_html = '';
if (isset($row['status']) && (int)$row['status'] === 1 && (empty($row['date_available']) || strtotime($row['date_available']) <= time()) && (isset($row['store_id']) && $row['store_id'] !== null)) {
$catalog_link = $catalog_url . 'index.php?route=product/product&product_id=' . (int)$row['product_id'];
$catalog_link_html = ' <a href="' . $catalog_link . '" target="_blank" title="On Catalog"><i class="fa fa-external-link text-muted"></i></a>';
}
$search_links = $this->getSearchLinks($row['name'], $row['model']);
$entities[] = '<span class="label label-info">Product (Add.)</span> <a href="' . $admin_link . '" target="_blank">' . htmlspecialchars($row['name'] ? $row['name'] : 'Product ID ' . $row['product_id'], ENT_QUOTES, 'UTF-8') . '</a>' . $catalog_link_html . $search_links;
}
$query = $this->db->query("SELECT c.category_id, c.status, (SELECT c2s.store_id FROM `" . DB_PREFIX . "category_to_store` c2s WHERE c2s.category_id = c.category_id AND c2s.store_id = '" . (int)$this->config->get('config_store_id') . "' LIMIT 1) AS store_id, (SELECT name FROM `" . DB_PREFIX . "category_description` cd WHERE cd.category_id = c.category_id AND cd.language_id = '" . (int)$this->config->get('config_language_id') . "' LIMIT 1) AS name FROM `" . DB_PREFIX . "category` c WHERE image = '" . $escaped . "'");
foreach ($query->rows as $row) {
$admin_link = $this->url->link('catalog/category/edit', 'user_token=' . $this->session->data['user_token'] . '&category_id=' . (int)$row['category_id'], true);
$catalog_link_html = '';
if (isset($row['status']) && (int)$row['status'] === 1 && (isset($row['store_id']) && $row['store_id'] !== null)) {
$catalog_link = $catalog_url . 'index.php?route=product/category&path=' . (int)$row['category_id'];
$catalog_link_html = ' <a href="' . $catalog_link . '" target="_blank" title="On Catalog"><i class="fa fa-external-link text-muted"></i></a>';
}
$search_links = $this->getSearchLinks($row['name']);
$entities[] = '<span class="label label-success">Category</span> <a href="' . $admin_link . '" target="_blank">' . htmlspecialchars($row['name'] ? $row['name'] : 'Category ID ' . $row['category_id'], ENT_QUOTES, 'UTF-8') . '</a>' . $catalog_link_html . $search_links;
}
$query = $this->db->query("SELECT m.manufacturer_id, m.name, (SELECT m2s.store_id FROM `" . DB_PREFIX . "manufacturer_to_store` m2s WHERE m2s.manufacturer_id = m.manufacturer_id AND m2s.store_id = '" . (int)$this->config->get('config_store_id') . "' LIMIT 1) AS store_id FROM `" . DB_PREFIX . "manufacturer` m WHERE image = '" . $escaped . "'");
foreach ($query->rows as $row) {
$admin_link = $this->url->link('catalog/manufacturer/edit', 'user_token=' . $this->session->data['user_token'] . '&manufacturer_id=' . (int)$row['manufacturer_id'], true);
$catalog_link_html = '';
if ((isset($row['store_id']) && $row['store_id'] !== null)) {
$catalog_link = $catalog_url . 'index.php?route=product/manufacturer/info&manufacturer_id=' . (int)$row['manufacturer_id'];
$catalog_link_html = ' <a href="' . $catalog_link . '" target="_blank" title="On Catalog"><i class="fa fa-external-link text-muted"></i></a>';
}
$search_links = $this->getSearchLinks($row['name']);
$entities[] = '<span class="label label-warning">Manufacturer</span> <a href="' . $admin_link . '" target="_blank">' . htmlspecialchars($row['name'], ENT_QUOTES, 'UTF-8') . '</a>' . $catalog_link_html . $search_links;
}
$table_query = $this->db->query("SHOW TABLES LIKE '" . DB_PREFIX . "banner_image'");
if ($table_query->num_rows) {
$query_banner = $this->db->query("SELECT bi.banner_id, b.name FROM `" . DB_PREFIX . "banner_image` bi LEFT JOIN `" . DB_PREFIX . "banner` b ON (bi.banner_id = b.banner_id) WHERE bi.image = '" . $escaped . "'");
foreach ($query_banner->rows as $row) {
$admin_link = $this->url->link('design/banner/edit', 'user_token=' . $this->session->data['user_token'] . '&banner_id=' . (int)$row['banner_id'], true);
$search_links = $this->getSearchLinks($row['name']);
$entities[] = '<span class="label label-default">Banner</span> <a href="' . $admin_link . '" target="_blank">' . htmlspecialchars($row['name'] ? $row['name'] : 'Banner ID ' . $row['banner_id'], ENT_QUOTES, 'UTF-8') . '</a>' . $search_links;
}
}
$blog_tables = array(
'oct_blog_article' => array('id' => 'blog_article_id', 'route' => 'extension/news', 'name' => 'title', 'type' => 'OCT Blog'),
'simple_blog_article' => array('id' => 'simple_blog_article_id', 'route' => 'simple_blog/article/edit', 'name' => 'article_title', 'type' => 'Simple Blog'),
'newsblog_article' => array('id' => 'article_id', 'route' => 'extension/newsblog/article/edit', 'name' => 'name', 'type' => 'NewsBlog'),
'information' => array('id' => 'information_id', 'route' => 'catalog/information/edit', 'name' => 'title', 'type' => 'Information')
);
foreach ($blog_tables as $table => $cfg) {
$table_query = $this->db->query("SHOW TABLES LIKE '" . DB_PREFIX . $table . "'");
if ($table_query->num_rows) {
$column_query = $this->db->query("SHOW COLUMNS FROM `" . DB_PREFIX . $table . "` LIKE 'image'");
if ($column_query->num_rows) {
$desc_table = $table . '_description';
$desc_query = $this->db->query("SHOW TABLES LIKE '" . DB_PREFIX . $desc_table . "'");
if ($desc_query->num_rows) {
$sql = "SELECT t.`" . $cfg['id'] . "`, td.`" . $cfg['name'] . "` as name FROM `" . DB_PREFIX . $table . "` t LEFT JOIN `" . DB_PREFIX . $desc_table . "` td ON (t.`" . $cfg['id'] . "` = td.`" . $cfg['id'] . "`) WHERE t.image = '" . $escaped . "' AND td.language_id = '" . (int)$this->config->get('config_language_id') . "' LIMIT 5";
} else {
$sql = "SELECT `" . $cfg['id'] . "`, `" . $cfg['name'] . "` as name FROM `" . DB_PREFIX . $table . "` WHERE image = '" . $escaped . "' LIMIT 5";
}
try {
$query_blog = $this->db->query($sql);
foreach ($query_blog->rows as $row) {
$admin_link = $this->url->link($cfg['route'], 'user_token=' . $this->session->data['user_token'] . '&' . $cfg['id'] . '=' . (int)$row[$cfg['id']], true);
$search_links = $this->getSearchLinks($row['name']);
$entities[] = '<span class="label label-primary">' . $cfg['type'] . '</span> <a href="' . $admin_link . '" target="_blank">' . htmlspecialchars($row['name'] ? $row['name'] : $cfg['type'] . ' ID ' . $row[$cfg['id']], ENT_QUOTES, 'UTF-8') . '</a>' . $search_links;
}
} catch (Exception $e) {}
}
}
}
return $entities;
}
public function checkAndFixFakeJpgOnTheFly($filename) {
if (!$this->config->get('module_img_opti_status') || !$this->config->get('module_img_opti_auto_fake_jpg_guard')) {
return $filename;
}
$cleanFile = str_replace(array('../', '..\\', "\0"), '', (string)$filename);
$ext = strtolower(pathinfo($cleanFile, PATHINFO_EXTENSION));
if (!in_array($ext, array('jpg', 'jpeg'))) {
return $filename;
}
$fullPath = DIR_IMAGE . $cleanFile;
if (!is_file($fullPath)) {
return $filename;
}
$header = @file_get_contents($fullPath, false, null, 0, 8);
if ($header !== "\x89PNG\r\n\x1a\n") {
return $filename;
}
$mode = $this->config->get('module_img_opti_fake_jpg_mode');
if ($mode === 'png') {
$newRelPath = preg_replace('/\.(jpg|jpeg)$/i', '.png', $cleanFile);
$newFullPath = DIR_IMAGE . $newRelPath;
if (@copy($fullPath, $newFullPath)) {
$this->updateImagePathInDb($cleanFile, $newRelPath);
$this->deleteImageCache($cleanFile);
@unlink($fullPath);
return $newRelPath;
}
} else {
if ($this->convertPngToJpgClean($fullPath, 100)) {
$this->deleteImageCache($cleanFile);
}
}
return $filename;
}
public function getGlobalStats() {
$stats = $this->config->get('module_img_opti_global_stats');
if (is_string($stats)) {
$stats = json_decode($stats, true);
}
if (!is_array($stats)) {
$stats = array(
'total_files' => 0,
'total_bytes_saved' => 0,
'total_original_bytes' => 0,
'last_updated' => 0
);
}
$total_files = isset($stats['total_files']) ? (int)$stats['total_files'] : 0;
$total_saved = isset($stats['total_bytes_saved']) ? (float)$stats['total_bytes_saved'] : 0.0;
$total_orig = isset($stats['total_original_bytes']) ? (float)$stats['total_original_bytes'] : 0.0;
$mb_saved = round($total_saved / (1024 * 1024), 2);
$avg_pct = ($total_orig > 0) ? round(($total_saved / $total_orig) * 100, 1) : 0.0;
return array(
'total_files' => $total_files,
'total_bytes_saved' => $total_saved,
'total_original_bytes' => $total_orig,
'saved_mb' => $mb_saved,
'avg_pct' => $avg_pct
);
}
public function recordGlobalStats($filesCount, $bytesSaved, $originalBytes = 0, $source = 'opti') {
if ($filesCount <= 0 && $bytesSaved <= 0) return;
$stats = $this->config->get('module_img_opti_global_stats');
if (is_string($stats)) {
$stats = json_decode($stats, true);
}
if (!is_array($stats)) {
$stats = array(
'total_files' => 0,
'total_bytes_saved' => 0,
'total_original_bytes' => 0,
'last_updated' => 0
);
}
$stats['total_files'] += (int)$filesCount;
$stats['total_bytes_saved'] += (float)$bytesSaved;
if ($originalBytes > 0) {
$stats['total_original_bytes'] += (float)$originalBytes;
} else {
$stats['total_original_bytes'] += (float)$bytesSaved;
}
$stats['last_updated'] = time();
$this->load->model('setting/setting');
$this->model_setting_setting->editSettingValue('module_img_opti', 'module_img_opti_global_stats', json_encode($stats));
}
public function resetGlobalStats() {
$stats = array(
'total_files' => 0,
'total_bytes_saved' => 0,
'total_original_bytes' => 0,
'last_updated' => time()
);
$this->load->model('setting/setting');
$this->model_setting_setting->editSettingValue('module_img_opti', 'module_img_opti_global_stats', json_encode($stats));
return $this->getGlobalStats();
}
private function updateImagePathInDb($oldPath, $newPath) {
$escapedOld = $this->db->escape((string)$oldPath);
$escapedNew = $this->db->escape((string)$newPath);
$updated = 0;
$this->db->query("UPDATE `" . DB_PREFIX . "product` SET image = '" . $escapedNew . "' WHERE image = '" . $escapedOld . "'");
$updated += $this->db->countAffected();
$this->db->query("UPDATE `" . DB_PREFIX . "product_image` SET image = '" . $escapedNew . "' WHERE image = '" . $escapedOld . "'");
$updated += $this->db->countAffected();
$this->db->query("UPDATE `" . DB_PREFIX . "category` SET image = '" . $escapedNew . "' WHERE image = '" . $escapedOld . "'");
$updated += $this->db->countAffected();
$this->db->query("UPDATE `" . DB_PREFIX . "manufacturer` SET image = '" . $escapedNew . "' WHERE image = '" . $escapedOld . "'");
$updated += $this->db->countAffected();
$table_query = $this->db->query("SHOW TABLES LIKE '" . DB_PREFIX . "banner_image'");
if ($table_query->num_rows) {
$this->db->query("UPDATE `" . DB_PREFIX . "banner_image` SET image = '" . $escapedNew . "' WHERE image = '" . $escapedOld . "'");
$updated += $this->db->countAffected();
}
$blog_tables = array('oct_blog_article', 'simple_blog_article', 'newsblog_article', 'information');
foreach ($blog_tables as $table) {
$table_query = $this->db->query("SHOW TABLES LIKE '" . DB_PREFIX . $table . "'");
if ($table_query->num_rows) {
$column_query = $this->db->query("SHOW COLUMNS FROM `" . DB_PREFIX . $table . "` LIKE 'image'");
if ($column_query->num_rows) {
try {
$this->db->query("UPDATE `" . DB_PREFIX . $table . "` SET image = '" . $escapedNew . "' WHERE image = '" . $escapedOld . "'");
$updated += $this->db->countAffected();
} catch (Exception $e) {}
}
}
}
return $updated;
}
public function scan_broken_db($data = array()) {
$this->loadLanguageSafe();
$rows = array();
$realImageDir = rtrim(str_replace('\\', '/', realpath(DIR_IMAGE)), '/');
$catalog_url = (defined('HTTPS_CATALOG') && HTTPS_CATALOG) ? HTTPS_CATALOG : (defined('HTTP_CATALOG') ? HTTP_CATALOG : '');
$query = $this->db->query("SELECT p.product_id as id, p.model, p.image, p.status, p.date_available, (SELECT p2s.store_id FROM `" . DB_PREFIX . "product_to_store` p2s WHERE p2s.product_id = p.product_id AND p2s.store_id = '" . (int)$this->config->get('config_store_id') . "' LIMIT 1) AS store_id, (SELECT name FROM `" . DB_PREFIX . "product_description` pd WHERE pd.product_id = p.product_id AND pd.language_id = '" . (int)$this->config->get('config_language_id') . "' LIMIT 1) AS name FROM `" . DB_PREFIX . "product` p WHERE image != '' AND image IS NOT NULL");
foreach ($query->rows as $row) {
$image_path = str_replace(array('../', '..\\', "\0"), '', (string)$row['image']);
$fullPath = DIR_IMAGE . $image_path;
$realPath = realpath($fullPath);
if (!$realPath || strpos(str_replace('\\', '/', $realPath), $realImageDir) !== 0 || !is_file($realPath)) {
$admin_link = $this->url->link('catalog/product/edit', 'user_token=' . $this->session->data['user_token'] . '&product_id=' . (int)$row['id'], true);
$catalog_link_html = '';
if (isset($row['status']) && (int)$row['status'] === 1 && (empty($row['date_available']) || strtotime($row['date_available']) <= time()) && (isset($row['store_id']) && $row['store_id'] !== null)) {
$catalog_link = $catalog_url . 'index.php?route=product/product&product_id=' . (int)$row['id'];
$catalog_link_html = ' <a href="' . $catalog_link . '" target="_blank"><i class="fa fa-external-link text-muted"></i></a>';
}
$search_links = $this->getSearchLinks($row['name'], $row['model']);
$rows[] = array(
'id' => 'product_main-' . (int)$row['id'],
'preview' => '<i class="fa fa-exclamation-triangle text-danger" style="font-size:20px;"></i>',
'name' => '<span class="label label-info">Product</span> <a href="' . $admin_link . '" target="_blank">' . htmlspecialchars($row['name'] ? $row['name'] : 'Product ID ' . (int)$row['id'], ENT_QUOTES, 'UTF-8') . '</a>' . $catalog_link_html . $search_links,
'before' => '<b>' . $this->language->get('text_grid_missing_file') . '</b>: ' . htmlspecialchars($image_path, ENT_QUOTES, 'UTF-8'),
'after' => $this->language->get('text_grid_broken_db_after'),
'actions' => array(
'clear' => $this->language->get('text_action_clear'),
'disable' => $this->language->get('text_action_disable'),
'stock' => $this->language->get('text_action_stock'),
'placeholder' => $this->language->get('text_action_placeholder'),
'ignore' => $this->language->get('text_action_ignore')
),
'selected_action' => 'clear'
);
}
}
$query_add = $this->db->query("SELECT pi.product_image_id as id, pi.product_id, pi.image, p.model, p.status, p.date_available, (SELECT p2s.store_id FROM `" . DB_PREFIX . "product_to_store` p2s WHERE p2s.product_id = pi.product_id AND p2s.store_id = '" . (int)$this->config->get('config_store_id') . "' LIMIT 1) AS store_id, (SELECT name FROM `" . DB_PREFIX . "product_description` pd WHERE pd.product_id = pi.product_id AND pd.language_id = '" . (int)$this->config->get('config_language_id') . "' LIMIT 1) AS name FROM `" . DB_PREFIX . "product_image` pi LEFT JOIN `" . DB_PREFIX . "product` p ON (pi.product_id = p.product_id) WHERE pi.image != '' AND pi.image IS NOT NULL");
foreach ($query_add->rows as $row) {
$image_path = str_replace(array('../', '..\\', "\0"), '', (string)$row['image']);
$fullPath = DIR_IMAGE . $image_path;
$realPath = realpath($fullPath);
if (!$realPath || strpos(str_replace('\\', '/', $realPath), $realImageDir) !== 0 || !is_file($realPath)) {
$admin_link = $this->url->link('catalog/product/edit', 'user_token=' . $this->session->data['user_token'] . '&product_id=' . (int)$row['product_id'], true);
$catalog_link_html = '';
if (isset($row['status']) && (int)$row['status'] === 1 && (empty($row['date_available']) || strtotime($row['date_available']) <= time()) && (isset($row['store_id']) && $row['store_id'] !== null)) {
$catalog_link = $catalog_url . 'index.php?route=product/product&product_id=' . (int)$row['product_id'];
$catalog_link_html = ' <a href="' . $catalog_link . '" target="_blank"><i class="fa fa-external-link text-muted"></i></a>';
}
$search_links = $this->getSearchLinks($row['name'], $row['model']);
$rows[] = array(
'id' => 'product_additional-' . (int)$row['id'],
'preview' => '<i class="fa fa-exclamation-triangle text-danger" style="font-size:20px;"></i>',
'name' => '<span class="label label-info">Product (Add.)</span> <a href="' . $admin_link . '" target="_blank">' . htmlspecialchars($row['name'] ? $row['name'] : 'Product ID ' . (int)$row['product_id'], ENT_QUOTES, 'UTF-8') . '</a>' . $catalog_link_html . $search_links,
'before' => '<b>' . $this->language->get('text_grid_missing_file') . '</b>: ' . htmlspecialchars($image_path, ENT_QUOTES, 'UTF-8'),
'after' => $this->language->get('text_grid_broken_db_after'),
'actions' => array(
'clear' => $this->language->get('text_action_clear'),
'placeholder' => $this->language->get('text_action_placeholder'),
'ignore' => $this->language->get('text_action_ignore')
),
'selected_action' => 'clear'
);
}
}
$query_cat = $this->db->query("SELECT c.category_id as id, c.image, c.status, (SELECT c2s.store_id FROM `" . DB_PREFIX . "category_to_store` c2s WHERE c2s.category_id = c.category_id AND c2s.store_id = '" . (int)$this->config->get('config_store_id') . "' LIMIT 1) AS store_id, (SELECT name FROM `" . DB_PREFIX . "category_description` cd WHERE cd.category_id = c.category_id AND cd.language_id = '" . (int)$this->config->get('config_language_id') . "' LIMIT 1) AS name FROM `" . DB_PREFIX . "category` c WHERE image != '' AND image IS NOT NULL");
foreach ($query_cat->rows as $row) {
$image_path = str_replace(array('../', '..\\', "\0"), '', (string)$row['image']);
$fullPath = DIR_IMAGE . $image_path;
$realPath = realpath($fullPath);
if (!$realPath || strpos(str_replace('\\', '/', $realPath), $realImageDir) !== 0 || !is_file($realPath)) {
$admin_link = $this->url->link('catalog/category/edit', 'user_token=' . $this->session->data['user_token'] . '&category_id=' . (int)$row['id'], true);
$catalog_link_html = '';
if (isset($row['status']) && (int)$row['status'] === 1 && (isset($row['store_id']) && $row['store_id'] !== null)) {
$catalog_link = $catalog_url . 'index.php?route=product/category&path=' . (int)$row['id'];
$catalog_link_html = ' <a href="' . $catalog_link . '" target="_blank"><i class="fa fa-external-link text-muted"></i></a>';
}
$search_links = $this->getSearchLinks($row['name']);
$rows[] = array(
'id' => 'category-' . (int)$row['id'],
'preview' => '<i class="fa fa-exclamation-triangle text-danger" style="font-size:20px;"></i>',
'name' => '<span class="label label-success">Category</span> <a href="' . $admin_link . '" target="_blank">' . htmlspecialchars($row['name'] ? $row['name'] : 'Category ID ' . (int)$row['id'], ENT_QUOTES, 'UTF-8') . '</a>' . $catalog_link_html . $search_links,
'before' => '<b>' . $this->language->get('text_grid_missing_file') . '</b>: ' . htmlspecialchars($image_path, ENT_QUOTES, 'UTF-8'),
'after' => $this->language->get('text_grid_broken_db_after'),
'actions' => array(
'clear' => $this->language->get('text_action_clear'),
'disable' => $this->language->get('text_action_disable'),
'placeholder' => $this->language->get('text_action_placeholder'),
'ignore' => $this->language->get('text_action_ignore')
),
'selected_action' => 'clear'
);
}
}
$query_man = $this->db->query("SELECT m.manufacturer_id as id, m.name, m.image, (SELECT m2s.store_id FROM `" . DB_PREFIX . "manufacturer_to_store` m2s WHERE m2s.manufacturer_id = m.manufacturer_id AND m2s.store_id = '" . (int)$this->config->get('config_store_id') . "' LIMIT 1) AS store_id FROM `" . DB_PREFIX . "manufacturer` m WHERE image != '' AND image IS NOT NULL");
foreach ($query_man->rows as $row) {
$image_path = str_replace(array('../', '..\\', "\0"), '', (string)$row['image']);
$fullPath = DIR_IMAGE . $image_path;
$realPath = realpath($fullPath);
if (!$realPath || strpos(str_replace('\\', '/', $realPath), $realImageDir) !== 0 || !is_file($realPath)) {
$admin_link = $this->url->link('catalog/manufacturer/edit', 'user_token=' . $this->session->data['user_token'] . '&manufacturer_id=' . (int)$row['id'], true);
$catalog_link_html = '';
if ((isset($row['store_id']) && $row['store_id'] !== null)) {
$catalog_link = $catalog_url . 'index.php?route=product/manufacturer/info&manufacturer_id=' . (int)$row['id'];
$catalog_link_html = ' <a href="' . $catalog_link . '" target="_blank"><i class="fa fa-external-link text-muted"></i></a>';
}
$search_links = $this->getSearchLinks($row['name']);
$rows[] = array(
'id' => 'manufacturer-' . (int)$row['id'],
'preview' => '<i class="fa fa-exclamation-triangle text-danger" style="font-size:20px;"></i>',
'name' => '<span class="label label-warning">Manufacturer</span> <a href="' . $admin_link . '" target="_blank">' . htmlspecialchars($row['name'], ENT_QUOTES, 'UTF-8') . '</a>' . $catalog_link_html . $search_links,
'before' => '<b>' . $this->language->get('text_grid_missing_file') . '</b>: ' . htmlspecialchars($image_path, ENT_QUOTES, 'UTF-8'),
'after' => $this->language->get('text_grid_broken_db_after'),
'actions' => array(
'clear' => $this->language->get('text_action_clear'),
'placeholder' => $this->language->get('text_action_placeholder'),
'ignore' => $this->language->get('text_action_ignore')
),
'selected_action' => 'clear'
);
}
}
file_put_contents(DIR_CACHE . 'img_opti_broken_db_grid.json', json_encode($rows));
return $this->paginateScanResult($rows, $data, array(
$this->language->get('text_th_preview'),
$this->language->get('text_th_entity'),
$this->language->get('text_th_before'),
$this->language->get('text_th_after'),
$this->language->get('text_th_action')
), array(
'backup_available' => true,
'quarantine_available' => false,
'description' => $this->language->get('text_description_broken')
));
}
public function fix_broken_db($data) {
$this->backupCatalogTables('broken');
$selected = isset($data['selected']) ? (array)$data['selected'] : array();
$fix_all = isset($data['fix_all']) ? (int)$data['fix_all'] : 0;
if ($fix_all === 1 || empty($selected)) {
$grid = json_decode(file_get_contents(DIR_CACHE . 'img_opti_broken_db_grid.json'), true);
foreach ($grid as $row) {
$selected[$row['id']] = $row['selected_action'];
}
}
$ph_product = (string)$this->config->get('module_img_opti_ph_product');
$ph_category = (string)$this->config->get('module_img_opti_ph_category');
$ph_brand = (string)$this->config->get('module_img_opti_ph_brand');
$processed = 0;
foreach ($selected as $row_id => $act) {
if ($act === 'ignore') continue;
$parts = explode('-', (string)$row_id, 2);
if (count($parts) !== 2) continue;
$type = preg_replace('/[^a-zA-Z_]/', '', $parts[0]);
$id = (int)$parts[1];
if ($act === 'clear') {
if ($type === 'product_main') {
$this->db->query("UPDATE `" . DB_PREFIX . "product` SET image = '' WHERE product_id = '" . $id . "'");
} elseif ($type === 'product_additional') {
$this->db->query("DELETE FROM `" . DB_PREFIX . "product_image` WHERE product_image_id = '" . $id . "'");
} elseif ($type === 'category') {
$this->db->query("UPDATE `" . DB_PREFIX . "category` SET image = '' WHERE category_id = '" . $id . "'");
} elseif ($type === 'manufacturer') {
$this->db->query("UPDATE `" . DB_PREFIX . "manufacturer` SET image = '' WHERE manufacturer_id = '" . $id . "'");
}
} elseif ($act === 'disable') {
if ($type === 'product_main') {
$this->db->query("UPDATE `" . DB_PREFIX . "product` SET status = '0' WHERE product_id = '" . $id . "'");
} elseif ($type === 'category') {
$this->db->query("UPDATE `" . DB_PREFIX . "category` SET status = '0' WHERE category_id = '" . $id . "'");
}
} elseif ($act === 'stock') {
if ($type === 'product_main') {
$this->db->query("UPDATE `" . DB_PREFIX . "product` SET quantity = '0' WHERE product_id = '" . $id . "'");
}
} elseif ($act === 'placeholder') {
if ($type === 'product_main') {
$this->db->query("UPDATE `" . DB_PREFIX . "product` SET image = '" . $this->db->escape($ph_product) . "' WHERE product_id = '" . $id . "'");
} elseif ($type === 'product_additional') {
$this->db->query("UPDATE `" . DB_PREFIX . "product_image` SET image = '" . $this->db->escape($ph_product) . "' WHERE product_image_id = '" . $id . "'");
} elseif ($type === 'category') {
$this->db->query("UPDATE `" . DB_PREFIX . "category` SET image = '" . $this->db->escape($ph_category) . "' WHERE category_id = '" . $id . "'");
} elseif ($type === 'manufacturer') {
$this->db->query("UPDATE `" . DB_PREFIX . "manufacturer` SET image = '" . $this->db->escape($ph_brand) . "' WHERE manufacturer_id = '" . $id . "'");
}
}
$processed++;
}
@unlink(DIR_CACHE . 'img_opti_broken_db_grid.json');
return $this->formatFixResult($processed, count($selected));
}
public function scan_broken_files($data = array()) {
$this->loadLanguageSafe();
$rows = array();
$dir = realpath(DIR_IMAGE . 'catalog');
$realImageDir = realpath(DIR_IMAGE);
if (!$dir || strpos(str_replace('\\', '/', $dir), str_replace('\\', '/', $realImageDir)) !== 0 || !is_dir($dir)) {
return array('error' => $this->language->get('text_err_dir'));
}
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS));
$this->load->model('tool/image');
foreach ($iterator as $file) {
if ($file->isFile()) {
$ext = strtolower($file->getExtension());
if (in_array($ext, array('jpg', 'jpeg', 'png', 'webp', 'gif'))) {
$realPath = realpath($file->getPathname());
if (!$realPath || strpos(str_replace('\\', '/', $realPath), str_replace('\\', '/', $dir)) !== 0) continue;
$size = filesize($realPath);
$is_broken = false;
if ($size === 0) {
$is_broken = true;
} elseif ($size > 52428800) {
$is_broken = false;
} else {
if (extension_loaded('imagick')) {
try {
$img = new Imagick($realPath);
$img->clear(); $img->destroy();
} catch (Exception $e) {
$is_broken = true;
}
} else {
$img = @imagecreatefromstring(@file_get_contents($realPath, false, null, 0, 52428800));
if (!$img) {
$is_broken = true;
} else {
imagedestroy($img);
}
}
}
if ($is_broken) {
$relPath = $this->getRelativeImagePath($realPath);
$entities = $this->getEntitiesByImagePath($relPath);
$entities_html = !empty($entities) ? implode('<br>', $entities) : '<i>' . $this->language->get('text_no_relations') . '</i>';
$rows[] = array(
'id' => 'broken_file-' . md5($relPath),
'preview' => '<i class="fa fa-file-image-o text-danger" style="font-size:24px;"></i>',
'name' => '<b>' . htmlspecialchars($relPath, ENT_QUOTES, 'UTF-8') . '</b><br>' . $entities_html,
'before' => $this->language->get('text_grid_broken_file') . ' (' . $this->formatBytes($size) . ')',
'after' => $this->language->get('text_grid_broken_file_after'),
'actions' => array(
'delete' => $this->language->get('text_action_delete'),
'placeholder' => $this->language->get('text_action_placeholder'),
'ignore' => $this->language->get('text_action_ignore')
),
'selected_action' => 'delete',
'meta' => array(
'path' => $relPath
)
);
}
}
}
}
file_put_contents(DIR_CACHE . 'img_opti_broken_files_grid.json', json_encode($rows));
return $this->paginateScanResult($rows, $data, array(
$this->language->get('text_th_preview'),
$this->language->get('text_th_entity'),
$this->language->get('text_th_before'),
$this->language->get('text_th_after'),
$this->language->get('text_th_action')
), array(
'backup_available' => false,
'quarantine_available' => true,
'description' => $this->language->get('text_description_broken_files')
));
}
public function fix_broken_files($data) {
$selected = isset($data['selected']) ? (array)$data['selected'] : array();
$fix_all = isset($data['fix_all']) ? (int)$data['fix_all'] : 0;
$grid = json_decode(file_get_contents(DIR_CACHE . 'img_opti_broken_files_grid.json'), true);
$rows_map = array();
foreach ($grid as $row) {
$rows_map[$row['id']] = $row;
if ($fix_all === 1 || empty($selected)) {
$selected[$row['id']] = $row['selected_action'];
}
}
$ph_product_path = DIR_IMAGE . (string)$this->config->get('module_img_opti_ph_product');
if (!is_file($ph_product_path)) {
$ph_product_path = DIR_IMAGE . 'no_image.png';
}
foreach ($selected as $row_id => $act) {
if ($act === 'ignore' || !isset($rows_map[$row_id])) continue;
$row = $rows_map[$row_id];
$relPath = $row['meta']['path'];
$fullPath = DIR_IMAGE . $relPath;
if ($act === 'delete') {
$this->quarantineFile($relPath, 'broken_files');
} elseif ($act === 'placeholder') {
$this->quarantineFile($relPath, 'broken_files');
if (is_file($ph_product_path)) {
@copy($ph_product_path, $fullPath);
}
}
}
@unlink(DIR_CACHE . 'img_opti_broken_files_grid.json');
return array('success' => true);
}
public function scan_duplicates($data) {
$this->loadLanguageSafe();
$rows = array();
$dir = realpath(DIR_IMAGE . 'catalog');
$realImageDir = realpath(DIR_IMAGE);
if (!$dir || strpos(str_replace('\\', '/', $dir), str_replace('\\', '/', $realImageDir)) !== 0 || !is_dir($dir)) {
return array('error' => $this->language->get('text_err_dir'));
}
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS));
$hashes = array();
foreach ($iterator as $file) {
if ($file->isFile()) {
$ext = strtolower($file->getExtension());
if (in_array($ext, array('jpg', 'jpeg', 'png', 'webp', 'gif', 'svg'))) {
$realPath = realpath($file->getPathname());
if ($realPath && strpos(str_replace('\\', '/', $realPath), str_replace('\\', '/', $dir)) === 0) {
$hash = md5_file($realPath);
$relPath = $this->getRelativeImagePath($realPath);
if (!isset($hashes[$hash])) {
$hashes[$hash] = array();
}
$hashes[$hash][] = $relPath;
}
}
}
}
$this->load->model('tool/image');
foreach ($hashes as $hash => $files) {
if (count($files) > 1) {
$original = array_shift($files);
$original_size = filesize(DIR_IMAGE . $original);
$orig_ext = strtolower($this->mb_pathinfo($original, PATHINFO_EXTENSION));
if ($orig_ext === 'svg') {
$thumb_orig = '../image/' . $original;
} else {
$thumb_orig = $this->model_tool_image->resize($original, 100, 100);
}
foreach ($files as $duplicate) {
$dupe_ext = strtolower($this->mb_pathinfo($duplicate, PATHINFO_EXTENSION));
if ($dupe_ext === 'svg') {
$thumb_dupe = '../image/' . $duplicate;
} else {
$thumb_dupe = $this->model_tool_image->resize($duplicate, 100, 100);
}
$entities = $this->getEntitiesByImagePath($duplicate);
$entities_html = !empty($entities) ? implode('<br>', $entities) : '<i>' . $this->language->get('text_no_relations') . '</i>';
$preview_html = '<div style="display: flex; gap: 8px; justify-content: center; align-items: flex-start;">';
$preview_html .= ' <div style="text-align: center;">';
$preview_html .= ' <img src="' . $thumb_orig . '" class="img-thumbnail" style="max-width:80px; max-height:80px;" onerror="this.style.display=\'none\'; $(this).after(\'<span class=\&quot;fa fa-file-image-o\&quot; style=\&quot;font-size:32px;color:#ccc;\&quot;></span>\'); this.onerror=null;"><br>';
$preview_html .= ' <span class="label label-success" style="font-size: 9px; display: inline-block; margin-top: 4px;">' . $this->language->get('text_dupe_original_label') . '</span>';
$preview_html .= ' </div>';
$preview_html .= ' <div style="text-align: center; border-left: 1px solid #ddd; padding-left: 8px;">';
$preview_html .= ' <img src="' . $thumb_dupe . '" class="img-thumbnail" style="max-width:80px; max-height:80px;" onerror="this.style.display=\'none\'; $(this).after(\'<span class=\&quot;fa fa-file-image-o\&quot; style=\&quot;font-size:32px;color:#ccc;\&quot;></span>\'); this.onerror=null;"><br>';
$preview_html .= ' <span class="label label-danger" style="font-size: 9px; display: inline-block; margin-top: 4px;">' . $this->language->get('text_dupe_duplicate_label') . '</span>';
$preview_html .= ' </div>';
$preview_html .= '</div>';
$name_html = '<strong>Duplicate path:</strong><br><span class="text-muted" style="word-break:break-all;">' . htmlspecialchars($duplicate, ENT_QUOTES, 'UTF-8') . '</span><br>';
$name_html .= '<strong>Relations:</strong><br>' . $entities_html;
$rows[] = array(
'id' => 'duplicate-' . md5($duplicate),
'preview' => $preview_html,
'name' => $name_html,
'before' => '<span class="label label-danger">' . $this->language->get('text_grid_dupe_before') . '</span><br><small style="word-break: break-all; display: inline-block; margin-top: 4px;">' . htmlspecialchars($duplicate, ENT_QUOTES, 'UTF-8') . '</small><br><small>' . $this->formatBytes($original_size) . '</small>',
'after' => '<span class="label label-success"><i class="fa fa-check-circle"></i> ' . $this->language->get('text_grid_dupe_after') . '</span><br><small style="word-break: break-all; display: inline-block; margin-top: 4px;">' . htmlspecialchars($original, ENT_QUOTES, 'UTF-8') . '</small><br><small>' . $this->formatBytes($original_size) . '</small>',
'actions' => array(
'merge' => $this->language->get('text_action_merge'),
'ignore' => $this->language->get('text_action_ignore')
),
'selected_action' => 'merge',
'meta' => array(
'original' => $original,
'duplicate' => $duplicate
)
);
}
}
}
file_put_contents(DIR_CACHE . 'img_opti_duplicates_grid.json', json_encode($rows));
return array(
'success' => true,
'headers' => array(
$this->language->get('text_th_preview'),
$this->language->get('text_th_entity'),
$this->language->get('text_th_dupe_delete'),
$this->language->get('text_th_dupe_keep'),
$this->language->get('text_th_action')
),
'rows' => $rows,
'backup_available' => true,
'quarantine_available' => true,
'description' => $this->language->get('text_description_duplicates')
);
}
public function fix_duplicates($data) {
$this->backupCatalogTables('duplicates');
$selected = isset($data['selected']) ? (array)$data['selected'] : array();
$fix_all = isset($data['fix_all']) ? (int)$data['fix_all'] : 0;
$grid = json_decode(file_get_contents(DIR_CACHE . 'img_opti_duplicates_grid.json'), true);
$rows_map = array();
foreach ($grid as $row) {
$rows_map[$row['id']] = $row;
if ($fix_all === 1 || empty($selected)) {
$selected[$row['id']] = $row['selected_action'];
}
}
foreach ($selected as $row_id => $act) {
if ($act !== 'merge' || !isset($rows_map[$row_id])) continue;
$row = $rows_map[$row_id];
$orig = $this->db->escape($row['meta']['original']);
$fake = $this->db->escape($row['meta']['duplicate']);
$this->db->query("UPDATE `" . DB_PREFIX . "product` SET image = '" . $orig . "' WHERE image = '" . $fake . "'");
$this->db->query("UPDATE `" . DB_PREFIX . "product_image` SET image = '" . $orig . "' WHERE image = '" . $fake . "'");
$this->db->query("UPDATE `" . DB_PREFIX . "category` SET image = '" . $orig . "' WHERE image = '" . $fake . "'");
$this->db->query("UPDATE `" . DB_PREFIX . "manufacturer` SET image = '" . $orig . "' WHERE image = '" . $fake . "'");
$table_query = $this->db->query("SHOW TABLES LIKE '" . DB_PREFIX . "banner_image'");
if ($table_query->num_rows) {
$this->db->query("UPDATE `" . DB_PREFIX . "banner_image` SET image = '" . $orig . "' WHERE image = '" . $fake . "'");
}
$blog_tables = array(
'oct_blog_article' => 'image',
'simple_blog_article' => 'image',
'newsblog_article' => 'image',
'information' => 'image'
);
foreach ($blog_tables as $table => $column) {
$table_query = $this->db->query("SHOW TABLES LIKE '" . DB_PREFIX . $table . "'");
if ($table_query->num_rows) {
$column_query = $this->db->query("SHOW COLUMNS FROM `" . DB_PREFIX . $table . "` LIKE '" . $column . "'");
if ($column_query->num_rows) {
try {
$this->db->query("UPDATE `" . DB_PREFIX . $table . "` SET `" . $column . "` = '" . $orig . "' WHERE `" . $column . "` = '" . $fake . "'");
} catch (Exception $e) {}
}
}
}
$this->quarantineFile($row['meta']['duplicate'], 'duplicates');
}
@unlink(DIR_CACHE . 'img_opti_duplicates_grid.json');
return array('success' => true);
}
public function scan_png_jpg($data) {
$this->loadLanguageSafe();
$rows = array();
$dir = realpath(DIR_IMAGE . 'catalog');
$realImageDir = realpath(DIR_IMAGE);
if (!$dir || strpos(str_replace('\\', '/', $dir), str_replace('\\', '/', $realImageDir)) !== 0 || !is_dir($dir)) {
return array('error' => $this->language->get('text_err_dir'));
}
$limit = isset($data['module_img_opti_png_jpg_limit']) ? (int)$data['module_img_opti_png_jpg_limit'] : 100;
if ($limit <= 0) $limit = 100;
$min_size = isset($data['module_img_opti_png_jpg_min_size']) ? (int)$data['module_img_opti_png_jpg_min_size'] : 500;
if ($min_size < 0) $min_size = 500;
$min_size_bytes = $min_size * 1024;
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS));
$this->load->model('tool/image');
foreach ($iterator as $file) {
if ($file->isFile() && strtolower($file->getExtension()) === 'png') {
$realPath = realpath($file->getPathname());
if ($realPath && strpos(str_replace('\\', '/', $realPath), str_replace('\\', '/', $dir)) === 0) {
$size = filesize($realPath);
if ($size < $min_size_bytes) continue;
$w = 0;
$h = 0;
$imgSize = @getimagesize($realPath);
if ($imgSize) {
$w = $imgSize[0];
$h = $imgSize[1];
}
$is_alpha = false;
if (extension_loaded('imagick')) {
try {
$img = new Imagick($realPath);
if ($img->getImageAlphaChannel()) {
$is_alpha = true;
}
$img->clear(); $img->destroy();
} catch (Exception $e) {}
} else {
$img = @imagecreatefrompng($realPath);
if ($img) {
$w_gd = imagesx($img);
$h_gd = imagesy($img);
for($x = 0; $x < $w_gd; $x++) {
for($y = 0; $y < $h_gd; $y++) {
$rgba = imagecolorat($img, $x, $y);
$alpha = ($rgba & 0x7F000000) >> 24;
if($alpha > 0) {
$is_alpha = true;
break 2;
}
}
}
imagedestroy($img);
}
}
if (!$is_alpha) {
$relPath = $this->getRelativeImagePath($realPath);
$thumb = $this->model_tool_image->resize($relPath, 100, 100);
$entities = $this->getEntitiesByImagePath($relPath);
$entities_html = !empty($entities) ? implode('<br>', $entities) : '<i>' . $this->language->get('text_no_relations') . '</i>';
$rows[] = array(
'id' => 'png_jpg-' . md5($relPath),
'preview' => '<img src="' . $thumb . '" class="img-thumbnail" style="max-width:100px; max-height:100px;">',
'name' => '<b>' . htmlspecialchars($relPath, ENT_QUOTES, 'UTF-8') . '</b><br>' . $entities_html,
'before' => 'PNG: ' . $this->formatBytes($size),
'after' => 'JPG: ~' . $this->formatBytes(round($size * 0.3)),
'actions' => array(
'convert' => $this->language->get('text_action_convert'),
'ignore' => $this->language->get('text_action_ignore')
),
'selected_action' => 'convert',
'meta' => array(
'path' => $relPath,
'width' => $w,
'height' => $h
)
);
if (count($rows) >= $limit) {
break;
}
}
}
}
}
file_put_contents(DIR_CACHE . 'img_opti_png_jpg_grid.json', json_encode($rows));
return array(
'success' => true,
'headers' => array(
$this->language->get('text_th_preview'),
$this->language->get('text_th_entity'),
$this->language->get('text_th_before'),
$this->language->get('text_th_after'),
$this->language->get('text_th_action')
),
'rows' => $rows,
'backup_available' => true,
'quarantine_available' => true,
'description' => $this->language->get('text_description_png_jpg')
);
}
public function fix_png_jpg($data) {
$this->backupCatalogTables('png_jpg');
$selected = isset($data['selected']) ? (array)$data['selected'] : array();
$fix_all = isset($data['fix_all']) ? (int)$data['fix_all'] : 0;
$grid = json_decode(file_get_contents(DIR_CACHE . 'img_opti_png_jpg_grid.json'), true);
$rows_map = array();
foreach ($grid as $row) {
$rows_map[$row['id']] = $row;
if ($fix_all === 1 || empty($selected)) {
$selected[$row['id']] = $row['selected_action'];
}
}
$realImageDir = realpath(DIR_IMAGE);
foreach ($selected as $row_id => $act) {
if ($act !== 'convert' || !isset($rows_map[$row_id])) continue;
$row = $rows_map[$row_id];
$pngClean = str_replace(array('../', '..\\', "\0"), '', (string)$row['meta']['path']);
$fullPath = realpath(DIR_IMAGE . $pngClean);
if (!$fullPath || strpos(str_replace('\\', '/', $fullPath), str_replace('\\', '/', $realImageDir)) !== 0 || !is_file($fullPath)) continue;
$jpgPathStr = preg_replace('/\.png$/i', '.jpg', DIR_IMAGE . $pngClean);
$jpgDir = realpath($this->mb_dirname($jpgPathStr));
if (!$jpgDir || strpos(str_replace('\\', '/', $jpgDir), str_replace('\\', '/', $realImageDir)) !== 0) continue;
$relJpg = ltrim(str_replace(str_replace('\\', '/', $realImageDir), '', str_replace('\\', '/', $jpgPathStr)), '/');
if ($this->convertPngToJpgClean($fullPath, $jpgPathStr, 85)) {
$dbOld = $this->db->escape($pngClean);
$dbNew = $this->db->escape($relJpg);
$this->db->query("UPDATE `" . DB_PREFIX . "product` SET image = '" . $dbNew . "' WHERE image = '" . $dbOld . "'");
$this->db->query("UPDATE `" . DB_PREFIX . "product_image` SET image = '" . $dbNew . "' WHERE image = '" . $dbOld . "'");
$this->db->query("UPDATE `" . DB_PREFIX . "category` SET image = '" . $dbNew . "' WHERE image = '" . $dbOld . "'");
$this->db->query("UPDATE `" . DB_PREFIX . "manufacturer` SET image = '" . $dbNew . "' WHERE image = '" . $dbOld . "'");
$this->quarantineFile($pngClean, 'png_jpg');
}
}
@unlink(DIR_CACHE . 'img_opti_png_jpg_grid.json');
return array('success' => true);
}
public function scan_translit($data) {
$this->loadLanguageSafe();
$rows = array();
$dir = realpath(DIR_IMAGE . 'catalog');
$realImageDir = realpath(DIR_IMAGE);
if (!$dir || strpos(str_replace('\\', '/', $dir), str_replace('\\', '/', $realImageDir)) !== 0 || !is_dir($dir)) {
return array('error' => $this->language->get('text_err_dir'));
}
$limit = isset($data['module_img_opti_translit_limit']) ? (int)$data['module_img_opti_translit_limit'] : 100;
if ($limit <= 0) $limit = 100;
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS));
$this->load->model('tool/image');
$assigned_paths = array();
foreach ($iterator as $file) {
if ($file->isFile()) {
$realPath = realpath($file->getPathname());
if ($realPath && strpos(str_replace('\\', '/', $realPath), str_replace('\\', '/', $dir)) === 0) {
$relPath = $this->getRelativeImagePath($realPath);
$filename = $this->mb_basename($relPath);
if (preg_match('/[^a-zA-Z0-9_\\-\\.]/', $filename)) {
$dirPart = $this->mb_dirname($relPath);
$ext = $this->mb_pathinfo($filename, PATHINFO_EXTENSION);
$nameOnly = $this->mb_pathinfo($filename, PATHINFO_FILENAME);
$cleanName = $this->transliterate($nameOnly) . '.' . $ext;
$proposedRel = ($dirPart === '.' ? '' : $dirPart . '/') . $cleanName;
if ($relPath !== $proposedRel) {
$proposedFullPath = DIR_IMAGE . $proposedRel;
if (file_exists($proposedFullPath) || isset($assigned_paths[$proposedRel]) || $this->isPathUsedInDb($proposedRel)) {
$transName = $this->transliterate($nameOnly);
$counter = 1;
while (true) {
$candidate = ($dirPart === '.' ? '' : $dirPart . '/') . $transName . '_' . $counter . ($ext !== '' ? '.' . $ext : '');
if (!file_exists(DIR_IMAGE . $candidate) && !isset($assigned_paths[$candidate]) && !$this->isPathUsedInDb($candidate)) {
$proposedRel = $candidate;
break;
}
$counter++;
}
}
$assigned_paths[$proposedRel] = true;
$thumb = $this->model_tool_image->resize($relPath, 100, 100);
$entities = $this->getEntitiesByImagePath($relPath);
$entities_html = !empty($entities) ? implode('<br>', $entities) : '<i>' . $this->language->get('text_no_relations') . '</i>';
$rows[] = array(
'id' => 'translit-' . md5($relPath),
'preview' => '<img src="' . $thumb . '" class="img-thumbnail" style="max-width:100px; max-height:100px;">',
'name' => '<b>' . htmlspecialchars($relPath, ENT_QUOTES, 'UTF-8') . '</b><br>' . $entities_html,
'before' => $filename,
'after' => $this->mb_basename($proposedRel),
'actions' => array(
'rename' => $this->language->get('text_action_rename'),
'ignore' => $this->language->get('text_action_ignore')
),
'selected_action' => 'rename',
'meta' => array(
'path' => $relPath,
'new_path' => $proposedRel
)
);
if (count($rows) >= $limit) {
break;
}
}
}
}
}
}
file_put_contents(DIR_CACHE . 'img_opti_translit_grid.json', json_encode($rows));
return array(
'success' => true,
'headers' => array(
$this->language->get('text_th_preview'),
$this->language->get('text_th_entity'),
$this->language->get('text_th_before'),
$this->language->get('text_th_after'),
$this->language->get('text_th_action')
),
'rows' => $rows,
'backup_available' => true,
'quarantine_available' => true,
'description' => $this->language->get('text_description_translit')
);
}
public function fix_translit($data) {
$this->backupCatalogTables('translit');
$selected = isset($data['selected']) ? (array)$data['selected'] : array();
$fix_all = isset($data['fix_all']) ? (int)$data['fix_all'] : 0;
$grid = json_decode(file_get_contents(DIR_CACHE . 'img_opti_translit_grid.json'), true);
$rows_map = array();
foreach ($grid as $row) {
$rows_map[$row['id']] = $row;
if ($fix_all === 1 || empty($selected)) {
$selected[$row['id']] = $row['selected_action'];
}
}
$realImageDir = realpath(DIR_IMAGE);
foreach ($selected as $row_id => $act) {
if ($act !== 'rename' || !isset($rows_map[$row_id])) continue;
$row = $rows_map[$row_id];
$cleanBadFile = str_replace(array('../', '..\\', "\0"), '', (string)$row['meta']['path']);
$fullPath = realpath(DIR_IMAGE . $cleanBadFile);
if (!$fullPath || strpos(str_replace('\\', '/', $fullPath), str_replace('\\', '/', $realImageDir)) !== 0 || !is_file($fullPath)) continue;
$newRel = (string)$row['meta']['new_path'];
$newFullPathStr = DIR_IMAGE . $newRel;
$newDirStr = $this->mb_dirname($newFullPathStr);
if (!is_dir($newDirStr)) {
@mkdir($newDirStr, 0755, true);
}
$realNewDir = realpath($newDirStr);
if (!$realNewDir || strpos(str_replace('\\', '/', $realNewDir), str_replace('\\', '/', $realImageDir)) !== 0) continue;
$newFullPath = $realNewDir . DIRECTORY_SEPARATOR . $this->mb_basename($newFullPathStr);
if (@copy($fullPath, $newFullPath)) {
$dbOld = $this->db->escape($cleanBadFile);
$dbNew = $this->db->escape($newRel);
$this->db->query("UPDATE `" . DB_PREFIX . "product` SET image = '" . $dbNew . "' WHERE image = '" . $dbOld . "'");
$this->db->query("UPDATE `" . DB_PREFIX . "product_image` SET image = '" . $dbNew . "' WHERE image = '" . $dbOld . "'");
$this->db->query("UPDATE `" . DB_PREFIX . "category` SET image = '" . $dbNew . "' WHERE image = '" . $dbOld . "'");
$this->db->query("UPDATE `" . DB_PREFIX . "manufacturer` SET image = '" . $dbNew . "' WHERE image = '" . $dbOld . "'");
$table_query = $this->db->query("SHOW TABLES LIKE '" . DB_PREFIX . "banner_image'");
if ($table_query->num_rows) {
$this->db->query("UPDATE `" . DB_PREFIX . "banner_image` SET image = '" . $dbNew . "' WHERE image = '" . $dbOld . "'");
}
$blog_tables = array(
'oct_blog_article' => 'image',
'simple_blog_article' => 'image',
'newsblog_article' => 'image',
'information' => 'image'
);
foreach ($blog_tables as $table => $column) {
$table_query = $this->db->query("SHOW TABLES LIKE '" . DB_PREFIX . $table . "'");
if ($table_query->num_rows) {
$column_query = $this->db->query("SHOW COLUMNS FROM `" . DB_PREFIX . $table . "` LIKE '" . $column . "'");
if ($column_query->num_rows) {
try {
$this->db->query("UPDATE `" . DB_PREFIX . $table . "` SET `" . $column . "` = '" . $dbNew . "' WHERE `" . $column . "` = '" . $dbOld . "'");
} catch (Exception $e) {}
}
}
}
$this->quarantineFile($cleanBadFile, 'translit');
}
}
@unlink(DIR_CACHE . 'img_opti_translit_grid.json');
return array('success' => true);
}
public function scan_empty_folders($data) {
$this->loadLanguageSafe();
$rows = array();
$dir = realpath(DIR_IMAGE . 'catalog');
$realImageDir = realpath(DIR_IMAGE);
if (!$dir || strpos(str_replace('\\', '/', $dir), str_replace('\\', '/', $realImageDir)) !== 0 || !is_dir($dir)) {
return array('error' => $this->language->get('text_err_dir'));
}
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($iterator as $path) {
if ($path->isDir()) {
$realPath = realpath($path->getPathname());
if ($realPath && strpos(str_replace('\\', '/', $realPath), str_replace('\\', '/', $dir)) === 0) {
$files = array_diff(scandir($realPath), array('.', '..'));
if (count($files) === 0) {
$relPath = str_replace(str_replace('\\', '/', DIR_IMAGE), '', str_replace('\\', '/', $realPath));
$rows[] = array(
'id' => 'empty_folder-' . md5($relPath),
'preview' => '<i class="fa fa-folder-open-o text-muted" style="font-size:24px;"></i>',
'name' => '<b>' . htmlspecialchars($relPath, ENT_QUOTES, 'UTF-8') . '</b>',
'before' => $this->language->get('text_grid_empty_folder'),
'after' => $this->language->get('text_grid_empty_folder_after'),
'actions' => array(
'delete' => $this->language->get('text_action_delete'),
'ignore' => $this->language->get('text_action_ignore')
),
'selected_action' => 'delete',
'meta' => array(
'path' => $relPath
)
);
}
}
}
}
file_put_contents(DIR_CACHE . 'img_opti_empty_folders_grid.json', json_encode($rows));
return array(
'success' => true,
'headers' => array(
$this->language->get('text_th_preview'),
$this->language->get('text_th_entity'),
$this->language->get('text_th_before'),
$this->language->get('text_th_after'),
$this->language->get('text_th_action')
),
'rows' => $rows,
'backup_available' => false,
'quarantine_available' => false,
'description' => $this->language->get('text_description_empty_folders')
);
}
public function fix_empty_folders($data) {
$selected = isset($data['selected']) ? (array)$data['selected'] : array();
$fix_all = isset($data['fix_all']) ? (int)$data['fix_all'] : 0;
$grid = json_decode(file_get_contents(DIR_CACHE . 'img_opti_empty_folders_grid.json'), true);
$rows_map = array();
foreach ($grid as $row) {
$rows_map[$row['id']] = $row;
if ($fix_all === 1 || empty($selected)) {
$selected[$row['id']] = $row['selected_action'];
}
}
$realImageDir = realpath(DIR_IMAGE);
foreach ($selected as $row_id => $act) {
if ($act !== 'delete' || !isset($rows_map[$row_id])) continue;
$row = $rows_map[$row_id];
$cleanPath = str_replace(array('../', '..\\', "\0"), '', (string)$row['meta']['path']);
$fullPath = realpath(DIR_IMAGE . $cleanPath);
if ($fullPath && strpos(str_replace('\\', '/', $fullPath), $realImageDir) === 0 && is_dir($fullPath)) {
$files = array_diff(scandir($fullPath), array('.', '..'));
if (count($files) === 0) {
@rmdir($fullPath);
}
}
}
@unlink(DIR_CACHE . 'img_opti_empty_folders_grid.json');
return array('success' => true);
}
public function scan_smart_cache($data) {
$this->loadLanguageSafe();
$rows = array();
$cache_dir = realpath(DIR_IMAGE . 'cache/catalog');
$realImageDir = realpath(DIR_IMAGE);
if (!$cache_dir || strpos(str_replace('\\', '/', $cache_dir), str_replace('\\', '/', $realImageDir)) !== 0 || !is_dir($cache_dir)) {
return array('error' => $this->language->get('text_err_dir'));
}
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($cache_dir, RecursiveDirectoryIterator::SKIP_DOTS));
$this->load->model('tool/image');
foreach ($iterator as $file) {
if ($file->isFile()) {
$realPath = realpath($file->getPathname());
if (!$realPath || strpos(str_replace('\\', '/', $realPath), str_replace('\\', '/', $cache_dir)) !== 0) continue;
$rel_cache = ltrim(str_replace(str_replace('\\', '/', DIR_IMAGE . 'cache/'), '', str_replace('\\', '/', $realPath)), '/');
$orig_rel = preg_replace('/-\d+x\d+\.(jpg|jpeg|png|webp|gif)$/i', '.$1', $rel_cache);
$orig_path = realpath(DIR_IMAGE . $orig_rel);
if (!$orig_path || !is_file($orig_path) || strpos(str_replace('\\', '/', $orig_path), str_replace('\\', '/', $realImageDir)) !== 0) {
$thumb = (defined('HTTPS_CATALOG') && HTTPS_CATALOG ? HTTPS_CATALOG : (defined('HTTP_CATALOG') ? HTTP_CATALOG : '')) . 'image/cache/' . $rel_cache;
$size = filesize($realPath);
$rows[] = array(
'id' => 'smart_cache-' . md5($rel_cache),
'preview' => '<img src="' . $thumb . '" class="img-thumbnail" style="max-width:100px; max-height:100px;">',
'name' => '<b>' . htmlspecialchars($rel_cache, ENT_QUOTES, 'UTF-8') . '</b>',
'before' => $this->language->get('text_grid_orph_cache') . ' (' . $this->formatBytes($size) . ')',
'after' => $this->language->get('text_grid_orph_cache_after'),
'actions' => array(
'delete' => $this->language->get('text_action_delete'),
'ignore' => $this->language->get('text_action_ignore')
),
'selected_action' => 'delete',
'meta' => array(
'path' => $rel_cache
)
);
}
}
}
file_put_contents(DIR_CACHE . 'img_opti_smart_cache_grid.json', json_encode($rows));
return array(
'success' => true,
'headers' => array(
$this->language->get('text_th_preview'),
$this->language->get('text_th_entity'),
$this->language->get('text_th_before'),
$this->language->get('text_th_after'),
$this->language->get('text_th_action')
),
'rows' => $rows,
'backup_available' => false,
'quarantine_available' => true,
'description' => $this->language->get('text_description_smart_cache')
);
}
public function fix_smart_cache($data) {
$selected = isset($data['selected']) ? (array)$data['selected'] : array();
$fix_all = isset($data['fix_all']) ? (int)$data['fix_all'] : 0;
$grid = json_decode(file_get_contents(DIR_CACHE . 'img_opti_smart_cache_grid.json'), true);
$rows_map = array();
foreach ($grid as $row) {
$rows_map[$row['id']] = $row;
if ($fix_all === 1 || empty($selected)) {
$selected[$row['id']] = $row['selected_action'];
}
}
foreach ($selected as $row_id => $act) {
if ($act !== 'delete' || !isset($rows_map[$row_id])) continue;
$row = $rows_map[$row_id];
$this->quarantineFile('cache/' . $row['meta']['path'], 'smart_cache');
}
@unlink(DIR_CACHE . 'img_opti_smart_cache_grid.json');
return array('success' => true);
}
public function scan_cache_restore($data) {
$this->loadLanguageSafe();
$rows = array();
$cache_dir = realpath(DIR_IMAGE . 'cache/catalog');
$realImageDir = realpath(DIR_IMAGE);
if (!$cache_dir || strpos(str_replace('\\', '/', $cache_dir), str_replace('\\', '/', $realImageDir)) !== 0 || !is_dir($cache_dir)) {
return array('error' => $this->language->get('text_err_dir'));
}
$restore_folder = isset($data['module_img_opti_restore_folder']) ? trim((string)$data['module_img_opti_restore_folder']) : '';
$restore_products = isset($data['module_img_opti_restore_target_product']) ? (int)$data['module_img_opti_restore_target_product'] : 1;
$restore_categories = isset($data['module_img_opti_restore_target_category']) ? (int)$data['module_img_opti_restore_target_category'] : 1;
$restore_brands = isset($data['module_img_opti_restore_target_brand']) ? (int)$data['module_img_opti_restore_target_brand'] : 1;
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($cache_dir, RecursiveDirectoryIterator::SKIP_DOTS));
$this->load->model('tool/image');
foreach ($iterator as $file) {
if ($file->isFile()) {
$realPath = realpath($file->getPathname());
if (!$realPath || strpos(str_replace('\\', '/', $realPath), str_replace('\\', '/', $cache_dir)) !== 0) continue;
$rel_cache = ltrim(str_replace(str_replace('\\', '/', DIR_IMAGE . 'cache/'), '', str_replace('\\', '/', $realPath)), '/');
$orig_rel = preg_replace('/-\d+x\d+\.(jpg|jpeg|png|webp|gif)$/i', '.$1', $rel_cache);
$orig_path = realpath(DIR_IMAGE . $orig_rel);
if (!$orig_path || !is_file($orig_path)) {
if (!empty($restore_folder)) {
$clean_restore_folder = str_replace(array('../', '..\\', "\0"), '', $restore_folder);
if (strpos($rel_cache, $clean_restore_folder) === false) continue;
}
$entities = $this->getEntitiesByImagePath($orig_rel);
$has_product = false;
$has_category = false;
$has_brand = false;
foreach ($entities as $entity_html) {
if (strpos($entity_html, 'Product') !== false) $has_product = true;
if (strpos($entity_html, 'Category') !== false) $has_category = true;
if (strpos($entity_html, 'Manufacturer') !== false) $has_brand = true;
}
$is_allowed = false;
if (empty($entities)) {
$is_allowed = true;
} else {
if ($has_product && $restore_products) $is_allowed = true;
if ($has_category && $restore_categories) $is_allowed = true;
if ($has_brand && $restore_brands) $is_allowed = true;
}
if (!$is_allowed) continue;
$thumb = (defined('HTTPS_CATALOG') && HTTPS_CATALOG ? HTTPS_CATALOG : (defined('HTTP_CATALOG') ? HTTP_CATALOG : '')) . 'image/cache/' . $rel_cache;
$entities_html = !empty($entities) ? implode('<br>', $entities) : '<i>' . $this->language->get('text_no_relations') . '</i>';
$rows[] = array(
'id' => 'cache_restore-' . md5($rel_cache),
'preview' => '<img src="' . $thumb . '" class="img-thumbnail" style="max-width:100px; max-height:100px;">',
'name' => '<b>' . htmlspecialchars($orig_rel, ENT_QUOTES, 'UTF-8') . '</b><br>' . $entities_html,
'before' => $this->language->get('text_grid_missing_orig'),
'after' => $this->language->get('text_grid_missing_orig_after'),
'actions' => array(
'restore' => $this->language->get('text_action_restore'),
'ignore' => $this->language->get('text_action_ignore')
),
'selected_action' => 'restore',
'meta' => array(
'cache_path' => $rel_cache,
'orig_path' => $orig_rel
)
);
}
}
}
file_put_contents(DIR_CACHE . 'img_opti_cache_restore_grid.json', json_encode($rows));
return array(
'success' => true,
'headers' => array(
$this->language->get('text_th_preview'),
$this->language->get('text_th_entity'),
$this->language->get('text_th_before'),
$this->language->get('text_th_after'),
$this->language->get('text_th_action')
),
'rows' => $rows,
'backup_available' => false,
'quarantine_available' => false,
'description' => $this->language->get('text_description_cache_restore')
);
}
public function fix_cache_restore($data) {
$selected = isset($data['selected']) ? (array)$data['selected'] : array();
$fix_all = isset($data['fix_all']) ? (int)$data['fix_all'] : 0;
$grid = json_decode(file_get_contents(DIR_CACHE . 'img_opti_cache_restore_grid.json'), true);
$rows_map = array();
foreach ($grid as $row) {
$rows_map[$row['id']] = $row;
if ($fix_all === 1 || empty($selected)) {
$selected[$row['id']] = $row['selected_action'];
}
}
$realImageDir = realpath(DIR_IMAGE);
foreach ($selected as $row_id => $act) {
if ($act !== 'restore' || !isset($rows_map[$row_id])) continue;
$row = $rows_map[$row_id];
$cacheFullPath = realpath(DIR_IMAGE . 'cache/' . $row['meta']['cache_path']);
if (!$cacheFullPath || !is_file($cacheFullPath)) continue;
$origRel = (string)$row['meta']['orig_path'];
$destPath = DIR_IMAGE . $origRel;
if (is_file($destPath)) continue;
$destDir = $this->mb_dirname($destPath);
if (!is_dir($destDir)) {
@mkdir($destDir, 0755, true);
}
$realDestDir = realpath($destDir);
if ($realDestDir && strpos(str_replace('\\', '/', $realDestDir), $realImageDir) === 0) {
@copy($cacheFullPath, $realDestDir . DIRECTORY_SEPARATOR . $this->mb_basename($origRel));
}
}
@unlink(DIR_CACHE . 'img_opti_cache_restore_grid.json');
return array('success' => true);
}
public function scan_exif($data) {
$this->loadLanguageSafe();
$rows = array();
$dir = realpath(DIR_IMAGE . 'catalog');
$realImageDir = realpath(DIR_IMAGE);
if (!$dir || strpos(str_replace('\\', '/', $dir), str_replace('\\', '/', $realImageDir)) !== 0 || !is_dir($dir)) {
return array('error' => $this->language->get('text_err_dir'));
}
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS));
$this->load->model('tool/image');
foreach ($iterator as $file) {
if ($file->isFile() && in_array(strtolower($file->getExtension()), array('jpg', 'jpeg'))) {
$realPath = realpath($file->getPathname());
if (!$realPath || strpos(str_replace('\\', '/', $realPath), str_replace('\\', '/', $dir)) !== 0) continue;
$exif = @exif_read_data($realPath);
if ($exif && (isset($exif['Make']) || isset($exif['Model']) || isset($exif['GPSLatitude']) || isset($exif['GPSLongitude']) || isset($exif['Software']) || isset($exif['DateTime']) || isset($exif['GPSInfo']) || isset($exif['GPSLatitudeRef']) || isset($exif['GPSLongitudeRef']) || isset($exif['DateTimeOriginal']) || isset($exif['Artist']) || isset($exif['Copyright']) || isset($exif['UserComment']))) {
$relPath = $this->getRelativeImagePath($realPath);
$thumb = $this->model_tool_image->resize($relPath, 100, 100);
$entities = $this->getEntitiesByImagePath($relPath);
$entities_html = !empty($entities) ? implode('<br>', $entities) : '<i>' . $this->language->get('text_no_relations') . '</i>';
$size = filesize($realPath);
$exif_details = array();
if (isset($exif['Make'])) $exif_details[] = 'Make: ' . htmlspecialchars($exif['Make'], ENT_QUOTES, 'UTF-8');
if (isset($exif['Model'])) $exif_details[] = 'Model: ' . htmlspecialchars($exif['Model'], ENT_QUOTES, 'UTF-8');
if (isset($exif['Software'])) $exif_details[] = 'Software: ' . htmlspecialchars($exif['Software'], ENT_QUOTES, 'UTF-8');
if (isset($exif['GPSLatitude']) || isset($exif['GPSLongitude']) || isset($exif['GPSInfo']) || isset($exif['GPSLatitudeRef']) || isset($exif['GPSLongitudeRef'])) $exif_details[] = 'GPS: Yes';
if (isset($exif['DateTime'])) $exif_details[] = 'Date: ' . htmlspecialchars($exif['DateTime'], ENT_QUOTES, 'UTF-8');
if (isset($exif['DateTimeOriginal'])) $exif_details[] = 'DateOriginal: ' . htmlspecialchars($exif['DateTimeOriginal'], ENT_QUOTES, 'UTF-8');
if (isset($exif['Artist'])) $exif_details[] = 'Artist: ' . htmlspecialchars($exif['Artist'], ENT_QUOTES, 'UTF-8');
if (isset($exif['Copyright'])) $exif_details[] = 'Copyright: ' . htmlspecialchars($exif['Copyright'], ENT_QUOTES, 'UTF-8');
if (isset($exif['UserComment'])) $exif_details[] = 'Comment: ' . htmlspecialchars($exif['UserComment'], ENT_QUOTES, 'UTF-8');
$exif_info = implode(', ', $exif_details);
if (!$exif_info) $exif_info = 'EXIF data present';
$rows[] = array(
'id' => 'exif-' . md5($relPath),
'preview' => '<img src="' . $thumb . '" class="img-thumbnail" style="max-width:100px; max-height:100px;">',
'name' => '<b>' . htmlspecialchars($relPath, ENT_QUOTES, 'UTF-8') . '</b><br>' . $entities_html,
'before' => $exif_info . ' (' . $this->formatBytes($size) . ')',
'after' => $this->language->get('text_grid_exif_after'),
'actions' => array(
'strip' => $this->language->get('text_action_strip'),
'ignore' => $this->language->get('text_action_ignore')
),
'selected_action' => 'strip',
'meta' => array(
'path' => $relPath
)
);
}
}
}
file_put_contents(DIR_CACHE . 'img_opti_exif_grid.json', json_encode($rows));
return array(
'success' => true,
'headers' => array(
$this->language->get('text_th_preview'),
$this->language->get('text_th_entity'),
$this->language->get('text_th_before'),
$this->language->get('text_th_after'),
$this->language->get('text_th_action')
),
'rows' => $rows,
'backup_available' => false,
'quarantine_available' => true,
'description' => $this->language->get('text_description_exif')
);
}
public function fix_exif($data) {
$selected = isset($data['selected']) ? (array)$data['selected'] : array();
$fix_all = isset($data['fix_all']) ? (int)$data['fix_all'] : 0;
$grid = json_decode(file_get_contents(DIR_CACHE . 'img_opti_exif_grid.json'), true);
$rows_map = array();
foreach ($grid as $row) {
$rows_map[$row['id']] = $row;
if ($fix_all === 1 || empty($selected)) {
$selected[$row['id']] = $row['selected_action'];
}
}
$realImageDir = realpath(DIR_IMAGE);
foreach ($selected as $row_id => $act) {
if ($act !== 'strip' || !isset($rows_map[$row_id])) continue;
$row = $rows_map[$row_id];
$cleanPath = str_replace(array('../', '..\\', "\0"), '', (string)$row['meta']['path']);
$fullPath = realpath(DIR_IMAGE . $cleanPath);
if (!$fullPath || strpos(str_replace('\\', '/', $fullPath), str_replace('\\', '/', $realImageDir)) !== 0 || !is_file($fullPath)) continue;
$this->quarantineFile($cleanPath, 'exif');
$restoredFullPath = DIR_IMAGE . 'catalog_trash/exif/' . ltrim($cleanPath, '/');
if (is_file($restoredFullPath)) {
if (extension_loaded('imagick')) {
try {
$img = new Imagick($restoredFullPath);
$img->stripImage();
$img->writeImage($fullPath);
$img->clear(); $img->destroy();
} catch (Exception $e) {}
} else {
$img = @imagecreatefromjpeg($restoredFullPath);
if ($img) {
imagejpeg($img, $fullPath, 90);
imagedestroy($img);
}
}
}
}
@unlink(DIR_CACHE . 'img_opti_exif_grid.json');
return array('success' => true);
}
public function scan_placeholders($data = array()) {
$this->loadLanguageSafe();
$rows = array();
$catalog_url = (defined('HTTPS_CATALOG') && HTTPS_CATALOG) ? HTTPS_CATALOG : (defined('HTTP_CATALOG') ? HTTP_CATALOG : '');
$scan_products = !empty($data) ? (isset($data['module_img_opti_ph_target_product']) ? (int)$data['module_img_opti_ph_target_product'] : 0) : ($this->config->get('module_img_opti_ph_target_product') !== null ? (int)$this->config->get('module_img_opti_ph_target_product') : 1);
$scan_categories = !empty($data) ? (isset($data['module_img_opti_ph_target_category']) ? (int)$data['module_img_opti_ph_target_category'] : 0) : ($this->config->get('module_img_opti_ph_target_category') !== null ? (int)$this->config->get('module_img_opti_ph_target_category') : 1);
$scan_brands = !empty($data) ? (isset($data['module_img_opti_ph_target_brand']) ? (int)$data['module_img_opti_ph_target_brand'] : 0) : ($this->config->get('module_img_opti_ph_target_brand') !== null ? (int)$this->config->get('module_img_opti_ph_target_brand') : 1);
$this->load->model('tool/image');
$rules_json = $this->config->get('module_img_opti_ph_rules');
$rules = json_decode(html_entity_decode($rules_json, ENT_QUOTES, 'UTF-8'), true) ?: array();
if ($scan_products) {
$query = $this->db->query("SELECT p.product_id as id, p.model, p.status, p.date_available, (SELECT p2s.store_id FROM `" . DB_PREFIX . "product_to_store` p2s WHERE p2s.product_id = p.product_id AND p2s.store_id = '" . (int)$this->config->get('config_store_id') . "' LIMIT 1) AS store_id, (SELECT name FROM `" . DB_PREFIX . "product_description` pd WHERE pd.product_id = p.product_id AND pd.language_id = '" . (int)$this->config->get('config_language_id') . "' LIMIT 1) AS name FROM `" . DB_PREFIX . "product` p WHERE image = '' OR image IS NULL");
foreach ($query->rows as $row) {
$matched_image = '';
foreach ($rules as $rule) {
if (empty($rule['status']) || empty($rule['target_product'])) continue;
$categories = array();
if (!empty($rule['categories'])) {
foreach ($rule['categories'] as $c) {
$categories[] = is_array($c) ? (int)$c['id'] : (int)$c;
}
}
$brands = array();
if (!empty($rule['brands'])) {
foreach ($rule['brands'] as $b) {
$brands[] = is_array($b) ? (int)$b['id'] : (int)$b;
}
}
$product_id = (int)$row['id'];
$query_p_info = $this->db->query("SELECT manufacturer_id FROM `" . DB_PREFIX . "product` WHERE product_id = '" . $product_id . "' LIMIT 1");
$manufacturer_id = $query_p_info->num_rows ? (int)$query_p_info->row['manufacturer_id'] : 0;
$prod_categories = array();
$query_p_cats = $this->db->query("SELECT category_id FROM `" . DB_PREFIX . "product_to_category` WHERE product_id = '" . $product_id . "'");
foreach ($query_p_cats->rows as $cat_row) {
$prod_categories[] = (int)$cat_row['category_id'];
}
$in_selected_cats = false;
if (!empty($categories)) {
foreach ($prod_categories as $c_id) {
if (in_array($c_id, $categories)) {
$in_selected_cats = true;
break;
}
}
}
$in_selected_brands = false;
if (!empty($brands) && in_array($manufacturer_id, $brands)) {
$in_selected_brands = true;
}
$filter_mode = isset($rule['filter_mode']) ? $rule['filter_mode'] : 'exclude';
$match = true;
if ($filter_mode === 'include') {
if (!empty($categories) || !empty($brands)) {
if (!$in_selected_cats && !$in_selected_brands) {
$match = false;
}
}
} else {
if ($in_selected_cats || $in_selected_brands) {
$match = false;
}
}
if ($match && !empty($rule['image'])) {
$matched_image = $rule['image'];
break;
}
}
if (!$matched_image) continue;
$admin_link = $this->url->link('catalog/product/edit', 'user_token=' . $this->session->data['user_token'] . '&product_id=' . (int)$row['id'], true);
$catalog_link_html = '';
if (isset($row['status']) && (int)$row['status'] === 1 && (empty($row['date_available']) || strtotime($row['date_available']) <= time()) && (isset($row['store_id']) && $row['store_id'] !== null)) {
$catalog_link = $catalog_url . 'index.php?route=product/product&product_id=' . (int)$row['id'];
$catalog_link_html = ' <a href="' . $catalog_link . '" target="_blank"><i class="fa fa-external-link text-muted"></i></a>';
}
$search_links = $this->getSearchLinks($row['name'], $row['model']);
$rows[] = array(
'id' => 'placeholder_product-' . (int)$row['id'],
'preview' => is_file(DIR_IMAGE . $matched_image) ? '<img src="' . $this->model_tool_image->resize($matched_image, 100, 100) . '" class="img-thumbnail" style="max-width:100px; max-height:100px;">' : '<i class="fa fa-picture-o text-muted" style="font-size:24px;"></i>',
'name' => '<span class="label label-info">Product</span> <a href="' . $admin_link . '" target="_blank">' . htmlspecialchars($row['name'] ? $row['name'] : 'Product ID ' . (int)$row['id'], ENT_QUOTES, 'UTF-8') . '</a>' . $catalog_link_html . $search_links,
'before' => $this->language->get('text_grid_no_image'),
'after' => $this->mb_basename($matched_image),
'actions' => array(
'set_placeholder' => $this->language->get('text_action_set_placeholder'),
'ignore' => $this->language->get('text_action_ignore')
),
'selected_action' => 'set_placeholder',
'meta' => array(
'image' => $matched_image
)
);
}
}
if ($scan_categories) {
$query_cat = $this->db->query("SELECT c.category_id as id, c.status, (SELECT c2s.store_id FROM `" . DB_PREFIX . "category_to_store` c2s WHERE c2s.category_id = c.category_id AND c2s.store_id = '" . (int)$this->config->get('config_store_id') . "' LIMIT 1) AS store_id, (SELECT name FROM `" . DB_PREFIX . "category_description` cd WHERE cd.category_id = c.category_id AND cd.language_id = '" . (int)$this->config->get('config_language_id') . "' LIMIT 1) AS name FROM `" . DB_PREFIX . "category` c WHERE image = '' OR image IS NULL");
foreach ($query_cat->rows as $row) {
$matched_image = '';
foreach ($rules as $rule) {
if (empty($rule['status']) || empty($rule['target_category'])) continue;
$categories = array();
if (!empty($rule['categories'])) {
foreach ($rule['categories'] as $c) {
$categories[] = is_array($c) ? (int)$c['id'] : (int)$c;
}
}
$category_id = (int)$row['id'];
$match = true;
if (!empty($categories)) {
$in_selected = in_array($category_id, $categories);
$filter_mode = isset($rule['filter_mode']) ? $rule['filter_mode'] : 'include';
if ($filter_mode === 'include' && !$in_selected) {
$match = false;
} elseif ($filter_mode === 'exclude' && $in_selected) {
$match = false;
}
}
if ($match && !empty($rule['image'])) {
$matched_image = $rule['image'];
break;
}
}
if (!$matched_image) continue;
$admin_link = $this->url->link('catalog/category/edit', 'user_token=' . $this->session->data['user_token'] . '&category_id=' . (int)$row['id'], true);
$catalog_link_html = '';
if (isset($row['status']) && (int)$row['status'] === 1 && (isset($row['store_id']) && $row['store_id'] !== null)) {
$catalog_link = $catalog_url . 'index.php?route=product/category&path=' . (int)$row['id'];
$catalog_link_html = ' <a href="' . $catalog_link . '" target="_blank"><i class="fa fa-external-link text-muted"></i></a>';
}
$search_links = $this->getSearchLinks($row['name']);
$rows[] = array(
'id' => 'placeholder_category-' . (int)$row['id'],
'preview' => is_file(DIR_IMAGE . $matched_image) ? '<img src="' . $this->model_tool_image->resize($matched_image, 100, 100) . '" class="img-thumbnail" style="max-width:100px; max-height:100px;">' : '<i class="fa fa-picture-o text-muted" style="font-size:24px;"></i>',
'name' => '<span class="label label-success">Category</span> <a href="' . $admin_link . '" target="_blank">' . htmlspecialchars($row['name'] ? $row['name'] : 'Category ID ' . (int)$row['id'], ENT_QUOTES, 'UTF-8') . '</a>' . $catalog_link_html . $search_links,
'before' => $this->language->get('text_grid_no_image'),
'after' => $this->mb_basename($matched_image),
'actions' => array(
'set_placeholder' => $this->language->get('text_action_set_placeholder'),
'ignore' => $this->language->get('text_action_ignore')
),
'selected_action' => 'set_placeholder',
'meta' => array(
'image' => $matched_image
)
);
}
}
if ($scan_brands) {
$query_man = $this->db->query("SELECT m.manufacturer_id as id, m.name, (SELECT m2s.store_id FROM `" . DB_PREFIX . "manufacturer_to_store` m2s WHERE m2s.manufacturer_id = m.manufacturer_id AND m2s.store_id = '" . (int)$this->config->get('config_store_id') . "' LIMIT 1) AS store_id FROM `" . DB_PREFIX . "manufacturer` m WHERE image = '' OR image IS NULL");
foreach ($query_man->rows as $row) {
$matched_image = '';
foreach ($rules as $rule) {
if (empty($rule['status']) || empty($rule['target_brand'])) continue;
$brands = array();
if (!empty($rule['brands'])) {
foreach ($rule['brands'] as $b) {
$brands[] = is_array($b) ? (int)$b['id'] : (int)$b;
}
}
$manufacturer_id = (int)$row['id'];
$match = true;
if (!empty($brands)) {
$in_selected = in_array($manufacturer_id, $brands);
$filter_mode = isset($rule['filter_mode']) ? $rule['filter_mode'] : 'include';
if ($filter_mode === 'include' && !$in_selected) {
$match = false;
} elseif ($filter_mode === 'exclude' && $in_selected) {
$match = false;
}
}
if ($match && !empty($rule['image'])) {
$matched_image = $rule['image'];
break;
}
}
if (!$matched_image) continue;
$admin_link = $this->url->link('catalog/manufacturer/edit', 'user_token=' . $this->session->data['user_token'] . '&manufacturer_id=' . (int)$row['id'], true);
$catalog_link_html = '';
if ((isset($row['store_id']) && $row['store_id'] !== null)) {
$catalog_link = $catalog_url . 'index.php?route=product/manufacturer/info&manufacturer_id=' . (int)$row['id'];
$catalog_link_html = ' <a href="' . $catalog_link . '" target="_blank"><i class="fa fa-external-link text-muted"></i></a>';
}
$search_links = $this->getSearchLinks($row['name']);
$rows[] = array(
'id' => 'placeholder_manufacturer-' . (int)$row['id'],
'preview' => is_file(DIR_IMAGE . $matched_image) ? '<img src="' . $this->model_tool_image->resize($matched_image, 100, 100) . '" class="img-thumbnail" style="max-width:100px; max-height:100px;">' : '<i class="fa fa-picture-o text-muted" style="font-size:24px;"></i>',
'name' => '<span class="label label-warning">Manufacturer</span> <a href="' . $admin_link . '" target="_blank">' . htmlspecialchars($row['name'], ENT_QUOTES, 'UTF-8') . '</a>' . $catalog_link_html . $search_links,
'before' => $this->language->get('text_grid_no_image'),
'after' => $this->mb_basename($matched_image),
'actions' => array(
'set_placeholder' => $this->language->get('text_action_set_placeholder'),
'ignore' => $this->language->get('text_action_ignore')
),
'selected_action' => 'set_placeholder',
'meta' => array(
'image' => $matched_image
)
);
}
}
file_put_contents(DIR_CACHE . 'img_opti_placeholders_grid.json', json_encode($rows));
return array(
'success' => true,
'headers' => array(
$this->language->get('text_th_preview'),
$this->language->get('text_th_entity'),
$this->language->get('text_th_before'),
$this->language->get('text_th_after'),
$this->language->get('text_th_action')
),
'rows' => $rows,
'backup_available' => true,
'quarantine_available' => false,
'description' => $this->language->get('text_description_placeholders')
);
}
public function fix_placeholders($data) {
$this->backupCatalogTables('placeholders');
$selected = isset($data['selected']) ? (array)$data['selected'] : array();
$fix_all = isset($data['fix_all']) ? (int)$data['fix_all'] : 0;
$grid = json_decode(file_get_contents(DIR_CACHE . 'img_opti_placeholders_grid.json'), true);
foreach ($grid as $row) {
if ($fix_all === 1 || empty($selected)) {
$selected[$row['id']] = $row['selected_action'];
}
}
foreach ($selected as $row_id => $act) {
if ($act !== 'set_placeholder') continue;
$image_path = '';
foreach ($grid as $row) {
if ($row['id'] === $row_id) {
$image_path = isset($row['meta']['image']) ? (string)$row['meta']['image'] : '';
break;
}
}
if (!$image_path) continue;
$parts = explode('-', (string)$row_id, 2);
if (count($parts) !== 2) continue;
$type = preg_replace('/[^a-zA-Z_]/', '', $parts[0]);
$id = (int)$parts[1];
if ($type === 'placeholder_product') {
$this->db->query("UPDATE `" . DB_PREFIX . "product` SET image = '" . $this->db->escape($image_path) . "' WHERE product_id = '" . $id . "'");
} elseif ($type === 'placeholder_category') {
$this->db->query("UPDATE `" . DB_PREFIX . "category` SET image = '" . $this->db->escape($image_path) . "' WHERE category_id = '" . $id . "'");
} elseif ($type === 'placeholder_manufacturer') {
$this->db->query("UPDATE `" . DB_PREFIX . "manufacturer` SET image = '" . $this->db->escape($image_path) . "' WHERE manufacturer_id = '" . $id . "'");
}
}
@unlink(DIR_CACHE . 'img_opti_placeholders_grid.json');
return array('success' => true);
}
public function scan_watermark($data) {
$this->loadLanguageSafe();
$cache_dir = realpath(DIR_IMAGE . 'cache/catalog');
$realImageDir = realpath(DIR_IMAGE);
if ($cache_dir && strpos(str_replace('\\', '/', $cache_dir), str_replace('\\', '/', $realImageDir)) !== 0 && is_dir($cache_dir)) {
$this->cleanDir($cache_dir);
}
return array(
'success' => true,
'message' => $this->language->get('text_log_cache_cleared') . '<br>' . $this->language->get('text_log_wm_smart_mode')
);
}
private function cleanDir($dir) {
$realDir = realpath($dir);
$realImageDir = realpath(DIR_IMAGE);
if (!$realDir || strpos(str_replace('\\', '/', $realDir), str_replace('\\', '/', $realImageDir)) !== 0 || !is_dir($realDir)) return;
$files = array_diff(scandir($realDir), array('.','..'));
foreach ($files as $file) {
$currentPath = $realDir . DIRECTORY_SEPARATOR . $this->mb_basename($file);
(is_dir($currentPath)) ? $this->cleanDir($currentPath) : @unlink($currentPath);
}
@rmdir($realDir);
}
public function applyDynamicWatermark($cached_file, $original_file, $is_preview = false) {
$realCachedFile = realpath($cached_file);
$realImageDir = realpath(DIR_IMAGE);
$cleanCached = mb_strtolower(str_replace('\\', '/', $realCachedFile ?: ''), 'UTF-8');
$cleanImageDir = mb_strtolower(str_replace('\\', '/', $realImageDir ?: ''), 'UTF-8');
if (!$realCachedFile || strpos($cleanCached, $cleanImageDir) !== 0) return false;
$escaped_original = $this->db->escape((string)$original_file);
$matched_rules = array();
if ($is_preview) {
$matched_rules[] = array(
'status' => 1,
'type' => $this->config->get('module_img_opti_wm_type'),
'image' => $this->config->get('module_img_opti_wm_image'),
'text_val' => $this->config->get('module_img_opti_wm_text_val'),
'position' => $this->config->get('module_img_opti_wm_position'),
'opacity' => $this->config->get('module_img_opti_wm_opacity'),
'angle' => $this->config->get('module_img_opti_wm_angle'),
'corner_radius' => $this->config->get('module_img_opti_wm_corner_radius'),
'size_type' => $this->config->get('module_img_opti_wm_size_type'),
'size_percent' => $this->config->get('module_img_opti_wm_size_percent'),
'size_px' => $this->config->get('module_img_opti_wm_size_px'),
'text_color' => $this->config->get('module_img_opti_wm_text_color'),
'text_font' => $this->config->get('module_img_opti_wm_text_font'),
'text_size' => $this->config->get('module_img_opti_wm_text_size')
);
} else {
$rules_json = $this->config->get('module_img_opti_wm_rules');
$rules = json_decode(html_entity_decode($rules_json, ENT_QUOTES, 'UTF-8'), true) ?: array();
foreach ($rules as $rule) {
if (empty($rule['status'])) continue;
$targets = array();
if (!empty($rule['target_product'])) $targets[] = 'product';
if (!empty($rule['target_category'])) $targets[] = 'category';
if (!empty($rule['target_brand'])) $targets[] = 'manufacturer';
if (!empty($rule['target_banner'])) $targets[] = 'banner';
if (!empty($rule['target_blog'])) $targets[] = 'blog';
$is_match = false;
$product_id = 0;
$manufacturer_id = 0;
if (in_array('product', $targets)) {
$query_prod = $this->db->query("SELECT product_id, manufacturer_id FROM `" . DB_PREFIX . "product` WHERE image = '" . $escaped_original . "' LIMIT 1");
if ($query_prod->num_rows) {
$is_match = true;
$product_id = (int)$query_prod->row['product_id'];
$manufacturer_id = (int)$query_prod->row['manufacturer_id'];
} else {
$query_prod_add = $this->db->query("SELECT product_id FROM `" . DB_PREFIX . "product_image` WHERE image = '" . $escaped_original . "' LIMIT 1");
if ($query_prod_add->num_rows) {
$is_match = true;
$product_id = (int)$query_prod_add->row['product_id'];
$query_p_info = $this->db->query("SELECT manufacturer_id FROM `" . DB_PREFIX . "product` WHERE product_id = '" . $product_id . "' LIMIT 1");
$manufacturer_id = $query_p_info->num_rows ? (int)$query_p_info->row['manufacturer_id'] : 0;
}
}
if ($is_match) {
$categories = array();
if (!empty($rule['categories'])) {
foreach ($rule['categories'] as $c) {
$categories[] = is_array($c) ? (int)$c['id'] : (int)$c;
}
}
$brands = array();
if (!empty($rule['brands'])) {
foreach ($rule['brands'] as $b) {
$brands[] = is_array($b) ? (int)$b['id'] : (int)$b;
}
}
$prod_categories = array();
$query_p_cats = $this->db->query("SELECT category_id FROM `" . DB_PREFIX . "product_to_category` WHERE product_id = '" . $product_id . "'");
foreach ($query_p_cats->rows as $row) {
$prod_categories[] = (int)$row['category_id'];
}
$in_selected_cats = false;
if (!empty($categories)) {
foreach ($prod_categories as $c_id) {
if (in_array($c_id, $categories)) {
$in_selected_cats = true;
break;
}
}
}
$in_selected_brands = false;
if (!empty($brands) && in_array($manufacturer_id, $brands)) {
$in_selected_brands = true;
}
$filter_mode = isset($rule['filter_mode']) ? $rule['filter_mode'] : 'include';
if ($filter_mode === 'include') {
if (!empty($categories) || !empty($brands)) {
if (!$in_selected_cats && !$in_selected_brands) {
$is_match = false;
}
}
} else {
if ($in_selected_cats || $in_selected_brands) {
$is_match = false;
}
}
}
}
if (!$is_match && in_array('category', $targets)) {
$query_cat = $this->db->query("SELECT category_id FROM `" . DB_PREFIX . "category` WHERE image = '" . $escaped_original . "' LIMIT 1");
if ($query_cat->num_rows) {
$is_match = true;
$category_id = (int)$query_cat->row['category_id'];
$categories = array();
if (!empty($rule['categories'])) {
foreach ($rule['categories'] as $c) {
$categories[] = is_array($c) ? (int)$c['id'] : (int)$c;
}
}
if (!empty($categories)) {
$in_selected = in_array($category_id, $categories);
$filter_mode = isset($rule['filter_mode']) ? $rule['filter_mode'] : 'include';
if ($filter_mode === 'include' && !$in_selected) {
$is_match = false;
} elseif ($filter_mode === 'exclude' && $in_selected) {
$is_match = false;
}
}
}
}
if (!$is_match && in_array('manufacturer', $targets)) {
$query_brand = $this->db->query("SELECT manufacturer_id FROM `" . DB_PREFIX . "manufacturer` WHERE image = '" . $escaped_original . "' LIMIT 1");
if ($query_brand->num_rows) {
$is_match = true;
$manufacturer_id = (int)$query_brand->row['manufacturer_id'];
$brands = array();
if (!empty($rule['brands'])) {
foreach ($rule['brands'] as $b) {
$brands[] = is_array($b) ? (int)$b['id'] : (int)$b;
}
}
if (!empty($brands)) {
$in_selected = in_array($manufacturer_id, $brands);
$filter_mode = isset($rule['filter_mode']) ? $rule['filter_mode'] : 'include';
if ($filter_mode === 'include' && !$in_selected) {
$is_match = false;
} elseif ($filter_mode === 'exclude' && $in_selected) {
$is_match = false;
}
}
}
}
if (!$is_match && in_array('banner', $targets)) {
$table_query = $this->db->query("SHOW TABLES LIKE '" . DB_PREFIX . "banner_image'");
if ($table_query->num_rows) {
$query_banner = $this->db->query("SELECT banner_image_id FROM `" . DB_PREFIX . "banner_image` WHERE image = '" . $escaped_original . "' LIMIT 1");
if ($query_banner->num_rows) {
$is_match = true;
}
}
}
if (!$is_match && in_array('blog', $targets)) {
$blog_tables = array(
'oct_blog_article',
'simple_blog_article',
'newsblog_article',
'information'
);
foreach ($blog_tables as $b_table) {
$table_query = $this->db->query("SHOW TABLES LIKE '" . DB_PREFIX . $b_table . "'");
if ($table_query->num_rows) {
$column_query = $this->db->query("SHOW COLUMNS FROM `" . DB_PREFIX . $b_table . "` LIKE 'image'");
if ($column_query->num_rows) {
$query_blog = $this->db->query("SELECT * FROM `" . DB_PREFIX . $b_table . "` WHERE image = '" . $escaped_original . "' LIMIT 1");
if ($query_blog->num_rows) {
$is_match = true;
break;
}
}
}
}
}
if ($is_match) {
$matched_rules[] = $rule;
}
}
}
if (empty($matched_rules)) {
return false;
}
$engine = preg_replace('/[^a-z]/', '', strtolower($this->config->get('module_img_opti_engine')));
$hasImagick = extension_loaded('imagick');
if ($engine === 'imagick' && $hasImagick) {
try {
$img = new Imagick($realCachedFile);
foreach ($matched_rules as $rule) {
$type = preg_replace('/[^a-z]/', '', $rule['type']);
$imagePathStr = str_replace(array('../', '..\\', ' '), '', (string)$rule['image']);
$imagePath = realpath(DIR_IMAGE . $imagePathStr);
$cleanImagePath = mb_strtolower(str_replace('\\', '/', $imagePath ?: ''), 'UTF-8');
if ($imagePath && strpos($cleanImagePath, $cleanImageDir) !== 0) {
$imagePath = false;
}
$textVal = (string)$rule['text_val'];
$position = (int)$rule['position'];
$opacity = (int)$rule['opacity'];
$size_type = isset($rule['size_type']) ? $rule['size_type'] : 'original';
$size_percent = isset($rule['size_percent']) ? (int)$rule['size_percent'] : 20;
$size_px = isset($rule['size_px']) ? (int)$rule['size_px'] : 200;
$text_size = isset($rule['text_size']) ? (int)$rule['text_size'] : 24;
$angle = isset($rule['angle']) ? (int)$rule['angle'] : 0;
$corner_radius = isset($rule['corner_radius']) ? (int)$rule['corner_radius'] : 0;
$text_font = isset($rule['text_font']) ? (string)$rule['text_font'] : '';
$text_color = isset($rule['text_color']) ? (string)$rule['text_color'] : '#ffffff';
if ($type === 'image') {
if (!$imagePath || !is_file($imagePath)) continue;
}
if ($type === 'text' && empty($textVal)) continue;
$imgW = $img->getImageWidth();
$imgH = $img->getImageHeight();
if ($type === 'image') {
$wm = new Imagick($imagePath);
$max_wm_w = (int)$this->config->get('module_img_opti_wm_max_width');
if ($max_wm_w <= 0) $max_wm_w = 800;
$max_wm_h = (int)$this->config->get('module_img_opti_wm_max_height');
if ($max_wm_h <= 0) $max_wm_h = 800;
$wmW = $wm->getImageWidth();
$wmH = $wm->getImageHeight();
if ($wmW > $max_wm_w || $wmH > $max_wm_h) {
$ratio = min($max_wm_w / $wmW, $max_wm_h / $wmH);
$new_wm_w = intval($wmW * $ratio);
$new_wm_h = intval($wmH * $ratio);
if ($new_wm_w > 0 && $new_wm_h > 0) {
$wm->resizeImage($new_wm_w, $new_wm_h, Imagick::FILTER_LANCZOS, 1);
}
}
if ($size_type === 'px' && $size_px > 0) {
$targetW = min($imgW, $size_px);
$targetH = intval(($wm->getImageHeight() / $wm->getImageWidth()) * $targetW);
if ($targetW > 0 && $targetH > 0) {
$wm->resizeImage($targetW, $targetH, Imagick::FILTER_LANCZOS, 1);
}
} elseif ($size_type === 'percent' && $size_percent > 0) {
$targetW = intval(($imgW * $size_percent) / 100);
$targetH = intval(($wm->getImageHeight() / $wm->getImageWidth()) * $targetW);
if ($targetW > 0 && $targetH > 0) {
$wm->resizeImage($targetW, $targetH, Imagick::FILTER_LANCZOS, 1);
}
}
if ($corner_radius > 0) {
$wm->roundCorners($corner_radius, $corner_radius);
}
if ($angle != 0) {
$wm->rotateImage(new ImagickPixel('none'), $angle);
}
$wm->evaluateImage(Imagick::EVALUATE_MULTIPLY, $opacity / 100, Imagick::CHANNEL_ALPHA);
$wmW = $wm->getImageWidth();
$wmH = $wm->getImageHeight();
} else {
$wm = new Imagick();
$draw = new ImagickDraw();
$fontFile = $text_font;
if (!empty($fontFile) && !is_file($fontFile)) {
$base_font = basename($fontFile);
if (is_file(DIR_SYSTEM . 'library/font/' . $base_font)) {
$fontFile = DIR_SYSTEM . 'library/font/' . $base_font;
} elseif (is_file(DIR_SYSTEM . 'fonts/' . $base_font)) {
$fontFile = DIR_SYSTEM . 'fonts/' . $base_font;
} elseif (is_file(DIR_SYSTEM . 'font/' . $base_font)) {
$fontFile = DIR_SYSTEM . 'font/' . $base_font;
}
}
if (empty($fontFile) || !is_file($fontFile)) {
$fontFile = DIR_SYSTEM . 'library/font/Roboto-Regular.ttf';
}
if (is_file($fontFile)) {
$draw->setFont($fontFile);
}
$fontSize = ($text_size > 0) ? $text_size : 24;
$draw->setFontSize($fontSize);
$colorHex = $text_color;
list($r, $g, $b) = $this->hex2rgb($colorHex);
$draw->setFillColor(new ImagickPixel('rgba(' . $r . ',' . $g . ',' . $b . ',' . ($opacity / 100) . ')'));
$draw->setGravity(Imagick::GRAVITY_CENTER);
$wm->newImage($imgW, $imgH, new ImagickPixel('transparent'));
$wm->annotateImage($draw, 0, 0, 0, $textVal);
if ($angle != 0) {
$wm->rotateImage(new ImagickPixel('none'), $angle);
}
$wmW = $wm->getImageWidth();
$wmH = $wm->getImageHeight();
}
$posX = 0; $posY = 0; $pad = 10;
if (in_array($position, array(1, 4, 7))) $posX = $pad;
elseif (in_array($position, array(2, 5, 8))) $posX = ($imgW - $wmW) / 2;
elseif (in_array($position, array(3, 6, 9))) $posX = $imgW - $wmW - $pad;
if (in_array($position, array(1, 2, 3))) $posY = $pad;
elseif (in_array($position, array(4, 5, 6))) $posY = ($imgH - $wmH) / 2;
elseif (in_array($position, array(7, 8, 9))) $posY = $imgH - $wmH - $pad;
$img->compositeImage($wm, Imagick::COMPOSITE_OVER, $posX, $posY);
$wm->clear(); $wm->destroy();
}
$img->writeImage($realCachedFile);
$img->clear(); $img->destroy();
} catch (Exception $e) {}
} else {
$ext = strtolower($this->mb_pathinfo($realCachedFile, PATHINFO_EXTENSION));
if ($ext === 'jpg' || $ext === 'jpeg') {
$img = @imagecreatefromjpeg($realCachedFile);
} elseif ($ext === 'png') {
$img = @imagecreatefrompng($realCachedFile);
} elseif ($ext === 'webp' && function_exists('imagecreatefromwebp')) {
$img = @imagecreatefromwebp($realCachedFile);
} else {
$img = false;
}
if ($img) {
if (function_exists('imagepalettetotruecolor') && !imageistruecolor($img)) {
imagepalettetotruecolor($img);
}
imagealphablending($img, true);
$imgW = imagesx($img);
$imgH = imagesy($img);
foreach ($matched_rules as $rule) {
$type = preg_replace('/[^a-z]/', '', $rule['type']);
$imagePathStr = str_replace(array('../', '..\\', ' '), '', (string)$rule['image']);
$imagePath = realpath(DIR_IMAGE . $imagePathStr);
$cleanImagePath = mb_strtolower(str_replace('\\', '/', $imagePath ?: ''), 'UTF-8');
if ($imagePath && strpos($cleanImagePath, $cleanImageDir) !== 0) {
$imagePath = false;
}
$textVal = (string)$rule['text_val'];
$position = (int)$rule['position'];
$opacity = (int)$rule['opacity'];
$size_type = isset($rule['size_type']) ? $rule['size_type'] : 'original';
$size_percent = isset($rule['size_percent']) ? (int)$rule['size_percent'] : 20;
$size_px = isset($rule['size_px']) ? (int)$rule['size_px'] : 200;
$text_size = isset($rule['text_size']) ? (int)$rule['text_size'] : 24;
$angle = isset($rule['angle']) ? (int)$rule['angle'] : 0;
$corner_radius = isset($rule['corner_radius']) ? (int)$rule['corner_radius'] : 0;
$text_font = isset($rule['text_font']) ? (string)$rule['text_font'] : '';
$text_color = isset($rule['text_color']) ? (string)$rule['text_color'] : '#ffffff';
if ($type === 'image') {
if (!$imagePath || !is_file($imagePath)) continue;
}
if ($type === 'text' && empty($textVal)) continue;
if ($type === 'image') {
$wm = @imagecreatefrompng($imagePath);
if (!$wm) {
$wm = @imagecreatefromjpeg($imagePath);
}
if (!$wm && function_exists('imagecreatefromwebp')) {
$wm = @imagecreatefromwebp($imagePath);
}
if ($wm) {
if (function_exists('imagepalettetotruecolor') && !imageistruecolor($wm)) {
imagepalettetotruecolor($wm);
}
imagealphablending($wm, false);
imagesavealpha($wm, true);
$wmW = imagesx($wm);
$wmH = imagesy($wm);
$max_wm_w = (int)$this->config->get('module_img_opti_wm_max_width');
if ($max_wm_w <= 0) $max_wm_w = 800;
$max_wm_h = (int)$this->config->get('module_img_opti_wm_max_height');
if ($max_wm_h <= 0) $max_wm_h = 800;
if ($wmW > $max_wm_w || $wmH > $max_wm_h) {
$ratio = min($max_wm_w / $wmW, $max_wm_h / $wmH);
$new_wm_w = intval($wmW * $ratio);
$new_wm_h = intval($wmH * $ratio);
if ($new_wm_w > 0 && $new_wm_h > 0) {
$resized_wm = imagecreatetruecolor($new_wm_w, $new_wm_h);
imagealphablending($resized_wm, false);
imagesavealpha($resized_wm, true);
$transparent = imagecolorallocatealpha($resized_wm, 255, 255, 255, 127);
imagefill($resized_wm, 0, 0, $transparent);
imagecopyresampled($resized_wm, $wm, 0, 0, 0, 0, $new_wm_w, $new_wm_h, $wmW, $wmH);
imagedestroy($wm);
$wm = $resized_wm;
$wmW = $new_wm_w;
$wmH = $new_wm_h;
}
}
if ($size_type === 'px' && $size_px > 0) {
$targetW = min($imgW, $size_px);
$targetH = intval(($wmH / $wmW) * $targetW);
if ($targetW > 0 && $targetH > 0) {
$resized_wm = imagecreatetruecolor($targetW, $targetH);
imagealphablending($resized_wm, false);
imagesavealpha($resized_wm, true);
$transparent = imagecolorallocatealpha($resized_wm, 255, 255, 255, 127);
imagefill($resized_wm, 0, 0, $transparent);
imagecopyresampled($resized_wm, $wm, 0, 0, 0, 0, $targetW, $targetH, $wmW, $wmH);
imagedestroy($wm);
$wm = $resized_wm;
$wmW = $targetW;
$wmH = $targetH;
}
} elseif ($size_type === 'percent' && $size_percent > 0) {
$targetW = intval(($imgW * $size_percent) / 100);
$targetH = intval(($wmH / $wmW) * $targetW);
if ($targetW > 0 && $targetH > 0) {
$resized_wm = imagecreatetruecolor($targetW, $targetH);
imagealphablending($resized_wm, false);
imagesavealpha($resized_wm, true);
$transparent = imagecolorallocatealpha($resized_wm, 255, 255, 255, 127);
imagefill($resized_wm, 0, 0, $transparent);
imagecopyresampled($resized_wm, $wm, 0, 0, 0, 0, $targetW, $targetH, $wmW, $wmH);
imagedestroy($wm);
$wm = $resized_wm;
$wmW = $targetW;
$wmH = $targetH;
}
}
if ($corner_radius > 0) {
$wm = $this->roundCornersGD($wm, $corner_radius);
}
if ($angle != 0) {
$gd_angle = -1 * $angle;
$transparent = imagecolorallocatealpha($wm, 255, 255, 255, 127);
$rotated = imagerotate($wm, $gd_angle, $transparent);
if ($rotated) {
imagealphablending($rotated, false);
imagesavealpha($rotated, true);
imagedestroy($wm);
$wm = $rotated;
$wmW = imagesx($wm);
$wmH = imagesy($wm);
}
}
$posX = 0; $posY = 0; $pad = 10;
if (in_array($position, array(1, 4, 7))) $posX = $pad;
elseif (in_array($position, array(2, 5, 8))) $posX = ($imgW - $wmW) / 2;
elseif (in_array($position, array(3, 6, 9))) $posX = $imgW - $wmW - $pad;
if (in_array($position, array(1, 2, 3))) $posY = $pad;
elseif (in_array($position, array(4, 5, 6))) $posY = ($imgH - $wmH) / 2;
elseif (in_array($position, array(7, 8, 9))) $posY = $imgH - $wmH - $pad;
$this->imagecopymerge_alpha($img, $wm, $posX, $posY, 0, 0, $wmW, $wmH, $opacity);
imagedestroy($wm);
}
} else {
$fontFile = $text_font;
if (!empty($fontFile) && !is_file($fontFile)) {
$base_font = basename($fontFile);
if (is_file(DIR_SYSTEM . 'library/font/' . $base_font)) {
$fontFile = DIR_SYSTEM . 'library/font/' . $base_font;
} elseif (is_file(DIR_SYSTEM . 'fonts/' . $base_font)) {
$fontFile = DIR_SYSTEM . 'fonts/' . $base_font;
} elseif (is_file(DIR_SYSTEM . 'font/' . $base_font)) {
$fontFile = DIR_SYSTEM . 'font/' . $base_font;
}
}
if (empty($fontFile) || !is_file($fontFile)) {
$fontFile = DIR_SYSTEM . 'library/font/Roboto-Regular.ttf';
}
if (!is_file($fontFile)) {
$open_basedir = ini_get('open_basedir');
$fallback_dirs = array(
DIR_SYSTEM . 'library/font/',
DIR_SYSTEM . 'fonts/',
DIR_SYSTEM . 'font/',
DIR_CATALOG . 'view/theme/default/fonts/'
);
if (DIRECTORY_SEPARATOR === '/') {
if (!$open_basedir) {
$fallback_dirs[] = '/usr/share/fonts/truetype/dejavu/';
$fallback_dirs[] = '/usr/share/fonts/truetype/liberation/';
$fallback_dirs[] = '/usr/share/fonts/truetype/freefont/';
}
} else {
if (!$open_basedir) {
$fallback_dirs[] = 'C:/Windows/Fonts/';
}
}
foreach ($fallback_dirs as $f_dir) {
if (@is_dir($f_dir)) {
$glob = @glob($f_dir . '*.ttf');
if ($glob) {
$fontFile = $glob[0];
break;
}
}
}
}
$userFontSize = ($text_size > 0) ? $text_size : 24;
if (!is_file($fontFile)) {
$gdFont = 3;
if ($userFontSize <= 14) $gdFont = 1;
elseif ($userFontSize <= 20) $gdFont = 2;
elseif ($userFontSize <= 28) $gdFont = 3;
elseif ($userFontSize <= 38) $gdFont = 4;
else $gdFont = 5;
$fontWidth = imagefontwidth($gdFont) * strlen($textVal);
$fontHeight = imagefontheight($gdFont);
$posX = 0; $posY = 0; $pad = 10;
if (in_array($position, array(1, 4, 7))) $posX = $pad;
elseif (in_array($position, array(2, 5, 8))) $posX = ($imgW - $fontWidth) / 2;
elseif (in_array($position, array(3, 6, 9))) $posX = $imgW - $fontWidth - $pad;
if (in_array($position, array(1, 2, 3))) $posY = $pad;
elseif (in_array($position, array(4, 5, 6))) $posY = ($imgH - $fontHeight) / 2;
elseif (in_array($position, array(7, 8, 9))) $posY = $imgH - $fontHeight - $pad;
$alpha = intval(127 - (127 * ($opacity / 100)));
$color = imagecolorallocatealpha($img, 255, 255, 255, $alpha);
imagestring($img, $gdFont, $posX, $posY, $textVal, $color);
} else {
$fontSize = $userFontSize;
$colorHex = $text_color;
list($r, $g, $b) = $this->hex2rgb($colorHex);
$alpha = intval(127 - (127 * ($opacity / 100)));
$color = imagecolorallocatealpha($img, $r, $g, $b, $alpha);
$bbox = imagettfbbox($fontSize, $angle, $fontFile, $textVal);
$textW = abs($bbox[4] - $bbox[0]);
$textH = abs($bbox[5] - $bbox[1]);
$posX = 0; $posY = 0; $pad = 10;
if (in_array($position, array(1, 4, 7))) $posX = $pad;
elseif (in_array($position, array(2, 5, 8))) $posX = ($imgW - $textW) / 2;
elseif (in_array($position, array(3, 6, 9))) $posX = $imgW - $textW - $pad;
if (in_array($position, array(1, 2, 3))) $posY = $pad + $textH;
elseif (in_array($position, array(4, 5, 6))) $posY = ($imgH - $textH) / 2 + $textH;
elseif (in_array($position, array(7, 8, 9))) $posY = $imgH - $pad;
imagettftext($img, $fontSize, $angle, $posX, $posY, $color, $fontFile, $textVal);
}
}
}
if ($ext === 'png' || $ext === 'webp') {
imagealphablending($img, false);
imagesavealpha($img, true);
}
if ($ext === 'jpg' || $ext === 'jpeg') { imagejpeg($img, $realCachedFile, 90); }
elseif ($ext === 'png') { imagepng($img, $realCachedFile, 9); }
elseif ($ext === 'webp') { imagewebp($img, $realCachedFile, 90); }
imagedestroy($img);
}
}
return true;
}
private function roundCornersGD($im, $radius) {
$w = imagesx($im);
$h = imagesy($im);
$new_im = imagecreatetruecolor($w, $h);
imagealphablending($new_im, false);
imagesavealpha($new_im, true);
$transparent = imagecolorallocatealpha($new_im, 255, 255, 255, 127);
imagefill($new_im, 0, 0, $transparent);
for ($x = 0; $x < $w; $x++) {
for ($y = 0; $y < $h; $y++) {
$rgba = imagecolorat($im, $x, $y);
$in_corner = false;
if ($x < $radius && $y < $radius) {
if (pow($radius - $x, 2) + pow($radius - $y, 2) > pow($radius, 2)) $in_corner = true;
} elseif ($x >= $w - $radius && $y < $radius) {
if (pow($x - ($w - $radius - 1), 2) + pow($radius - $y, 2) > pow($radius, 2)) $in_corner = true;
} elseif ($x < $radius && $y >= $h - $radius) {
if (pow($radius - $x, 2) + pow($y - ($h - $radius - 1), 2) > pow($radius, 2)) $in_corner = true;
} elseif ($x >= $w - $radius && $y >= $h - $radius) {
if (pow($x - ($w - $radius - 1), 2) + pow($y - ($h - $radius - 1), 2) > pow($radius, 2)) $in_corner = true;
}
if ($in_corner) {
imagesetpixel($new_im, $x, $y, $transparent);
} else {
imagesetpixel($new_im, $x, $y, $rgba);
}
}
}
imagedestroy($im);
return $new_im;
}
private function hex2rgb($hex) {
$hex = str_replace('#', '', $hex);
if (strlen($hex) == 3) {
$r = hexdec(substr($hex, 0, 1) . substr($hex, 0, 1));
$g = hexdec(substr($hex, 1, 1) . substr($hex, 1, 1));
$b = hexdec(substr($hex, 2, 1) . substr($hex, 2, 1));
} else {
$r = hexdec(substr($hex, 0, 2));
$g = hexdec(substr($hex, 2, 2));
$b = hexdec(substr($hex, 4, 2));
}
return array($r, $g, $b);
}
private function imagecopymerge_alpha($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct) {
$opacity = $pct / 100;
for ($x = 0; $x < $src_w; $x++) {
for ($y = 0; $y < $src_h; $y++) {
$src_color_idx = imagecolorat($src_im, $src_x + $x, $src_y + $y);
$src_color = imagecolorsforindex($src_im, $src_color_idx);
$src_alpha = (127 - $src_color['alpha']) / 127;
$src_alpha = $src_alpha * $opacity;
if ($src_alpha <= 0) {
continue;
}
$dst_x_pos = $dst_x + $x;
$dst_y_pos = $dst_y + $y;
if ($dst_x_pos < 0 || $dst_x_pos >= imagesx($dst_im) || $dst_y_pos < 0 || $dst_y_pos >= imagesy($dst_im)) {
continue;
}
$dst_color_idx = imagecolorat($dst_im, $dst_x_pos, $dst_y_pos);
$dst_color = imagecolorsforindex($dst_im, $dst_color_idx);
$dst_alpha = (127 - $dst_color['alpha']) / 127;
$out_alpha = $src_alpha + $dst_alpha * (1 - $src_alpha);
if ($out_alpha > 0) {
$out_r = ($src_color['red'] * $src_alpha + $dst_color['red'] * $dst_alpha * (1 - $src_alpha)) / $out_alpha;
$out_g = ($src_color['green'] * $src_alpha + $dst_color['green'] * $dst_alpha * (1 - $src_alpha)) / $out_alpha;
$out_b = ($src_color['blue'] * $src_alpha + $dst_color['blue'] * $dst_alpha * (1 - $src_alpha)) / $out_alpha;
$out_alpha_val = 127 - round($out_alpha * 127);
} else {
$out_r = 0;
$out_g = 0;
$out_b = 0;
$out_alpha_val = 127;
}
$out_r = max(0, min(255, round($out_r)));
$out_g = max(0, min(255, round($out_g)));
$out_b = max(0, min(255, round($out_b)));
$out_alpha_val = max(0, min(127, $out_alpha_val));
$new_color = imagecolorallocatealpha($dst_im, $out_r, $out_g, $out_b, $out_alpha_val);
if ($new_color === false) {
$new_color = imagecolorresolvealpha($dst_im, $out_r, $out_g, $out_b, $out_alpha_val);
}
imagesetpixel($dst_im, $dst_x_pos, $dst_y_pos, $new_color);
}
}
}
public function scan_small_photos($data) {
$this->loadLanguageSafe();
$rows = array();
$dir = realpath(DIR_IMAGE . 'catalog');
$realImageDir = realpath(DIR_IMAGE);
$minW = isset($data['module_img_opti_min_width']) ? (int)$data['module_img_opti_min_width'] : 300;
$minH = isset($data['module_img_opti_min_height']) ? (int)$data['module_img_opti_min_height'] : 300;
if (!$dir || strpos(str_replace('\\', '/', $dir), str_replace('\\', '/', $realImageDir)) !== 0 || !is_dir($dir)) {
return array('error' => $this->language->get('text_err_dir'));
}
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS));
$this->load->model('tool/image');
foreach ($iterator as $file) {
if ($file->isFile() && in_array(strtolower($file->getExtension()), array('jpg', 'jpeg', 'png', 'webp'))) {
$realPath = realpath($file->getPathname());
if (!$realPath || strpos(str_replace('\\', '/', $realPath), str_replace('\\', '/', $dir)) !== 0) continue;
$size = @getimagesize($realPath);
if ($size && ($size[0] < $minW || $size[1] < $minH)) {
$relPath = $this->getRelativeImagePath($realPath);
$thumb = $this->model_tool_image->resize($relPath, 100, 100);
$entities = $this->getEntitiesByImagePath($relPath);
$entities_html = !empty($entities) ? implode('<br>', $entities) : '<i>' . $this->language->get('text_no_relations') . '</i>';
$rows[] = array(
'id' => 'small_photo-' . md5($relPath),
'preview' => '<img src="' . $thumb . '" class="img-thumbnail" style="max-width:100px; max-height:100px;">',
'name' => '<b>' . htmlspecialchars($relPath, ENT_QUOTES, 'UTF-8') . '</b><br>' . $entities_html,
'before' => $this->language->get('text_grid_too_small') . ': ' . $size[0] . 'x' . $size[1] . ' px',
'after' => $this->language->get('text_grid_small_photo_after'),
'actions' => array(
'delete' => $this->language->get('text_action_delete'),
'placeholder' => $this->language->get('text_action_placeholder'),
'ignore' => $this->language->get('text_action_ignore')
),
'selected_action' => 'ignore',
'meta' => array(
'path' => $relPath
)
);
}
}
}
file_put_contents(DIR_CACHE . 'img_opti_small_photos_grid.json', json_encode($rows));
return array(
'success' => true,
'headers' => array(
$this->language->get('text_th_preview'),
$this->language->get('text_th_entity'),
$this->language->get('text_th_before'),
$this->language->get('text_th_after'),
$this->language->get('text_th_action')
),
'rows' => $rows,
'backup_available' => false,
'quarantine_available' => true,
'description' => sprintf($this->language->get('text_description_small_photos'), $minW, $minH)
);
}
public function fix_small_photos($data) {
$selected = isset($data['selected']) ? (array)$data['selected'] : array();
$fix_all = isset($data['fix_all']) ? (int)$data['fix_all'] : 0;
$cache_file = DIR_CACHE . 'img_opti_small_photos_grid.json';
if (!file_exists($cache_file)) {
return array('error' => $this->language->get('error_no_scan_data'));
}
$grid = json_decode(file_get_contents($cache_file), true);
$rows_map = array();
foreach ($grid as $row) {
$rows_map[$row['id']] = $row;
if ($fix_all === 1 || empty($selected)) {
$selected[$row['id']] = $row['selected_action'];
}
}
$ph_product_path = DIR_IMAGE . (string)$this->config->get('module_img_opti_ph_product');
if (!is_file($ph_product_path)) {
$ph_product_path = DIR_IMAGE . 'no_image.png';
}
foreach ($selected as $row_id => $act) {
if ($act === 'ignore' || !isset($rows_map[$row_id])) continue;
$row = $rows_map[$row_id];
$relPath = $row['meta']['path'];
$fullPath = DIR_IMAGE . $relPath;
if ($act === 'delete') {
$this->quarantineFile($relPath, 'small_photos');
} elseif ($act === 'placeholder') {
$this->quarantineFile($relPath, 'small_photos');
if (is_file($ph_product_path)) {
@copy($ph_product_path, $fullPath);
}
}
}
@unlink($cache_file);
return array('success' => true);
}
public function scan_html_broken($data) {
$this->loadLanguageSafe();
$rows = array();
$realImageDir = realpath(DIR_IMAGE);
$catalog_url = (defined('HTTPS_CATALOG') && HTTPS_CATALOG) ? HTTPS_CATALOG : (defined('HTTP_CATALOG') ? HTTP_CATALOG : '');
$tables = array(
'product_description' => array('id' => 'product_id', 'field' => 'description', 'name_field' => 'name', 'route' => 'catalog/product/edit', 'param' => 'product_id', 'type' => 'Product'),
'category_description' => array('id' => 'category_id', 'field' => 'description', 'name_field' => 'name', 'route' => 'catalog/category/edit', 'param' => 'category_id', 'type' => 'Category'),
'information_description' => array('id' => 'information_id', 'field' => 'description', 'name_field' => 'title', 'route' => 'catalog/information/edit', 'param' => 'information_id', 'type' => 'Information')
);
foreach ($tables as $table => $cfg) {
if ($table === 'product_description') {
$query = $this->db->query("SELECT pd.`" . $cfg['id'] . "`, pd.`" . $cfg['field'] . "`, pd.`" . $cfg['name_field'] . "`, p.model FROM `" . DB_PREFIX . $table . "` pd LEFT JOIN `" . DB_PREFIX . "product` p ON (pd.product_id = p.product_id) WHERE pd.language_id = '" . (int)$this->config->get('config_language_id') . "'");
} else {
$query = $this->db->query("SELECT `" . $cfg['id'] . "`, `" . $cfg['field'] . "`, `" . $cfg['name_field'] . "` FROM `" . DB_PREFIX . $table . "` WHERE language_id = '" . (int)$this->config->get('config_language_id') . "'");
}
foreach ($query->rows as $row) {
$desc = html_entity_decode($row[$cfg['field']], ENT_QUOTES, 'UTF-8');
if (preg_match_all('/<img[^>]+src=(?:\"|\')([^"\']+)(?:\"|\')[^>]*>/i', $desc, $matches)) {
foreach ($matches[1] as $src) {
if (strpos($src, 'image/catalog') !== false) {
$rel_path = preg_replace('/^.*image\/(catalog\/.*)$/i', '$1', $src);
$rel_path = urldecode($rel_path);
$cleanRelPath = str_replace(array('../', '..\\', "\0"), '', $rel_path);
$fullPath = realpath(DIR_IMAGE . $cleanRelPath);
if (!$fullPath || strpos(str_replace('\\', '/', $fullPath), str_replace('\\', '/', $realImageDir)) !== 0 || !is_file($fullPath)) {
$admin_link = $this->url->link($cfg['route'], 'user_token=' . $this->session->data['user_token'] . '&' . $cfg['param'] . '=' . (int)$row[$cfg['id']], true);
$search_links = $this->getSearchLinks($row[$cfg['name_field']], ($table === 'product_description' ? $row['model'] : ''));
$rows[] = array(
'id' => 'html_broken-' . $table . '-' . (int)$row[$cfg['id']] . '-' . md5($cleanRelPath),
'preview' => '<i class="fa fa-file-code-o text-danger" style="font-size:24px;"></i>',
'name' => '<span class="label label-info">' . $cfg['type'] . '</span> <a href="' . $admin_link . '" target="_blank">' . htmlspecialchars($row[$cfg['name_field']] ? $row[$cfg['name_field']] : $cfg['type'] . ' ID ' . (int)$row[$cfg['id']], ENT_QUOTES, 'UTF-8') . '</a>' . $search_links,
'before' => $this->language->get('text_grid_missing_html') . ': ' . htmlspecialchars($cleanRelPath, ENT_QUOTES, 'UTF-8'),
'after' => $this->language->get('text_grid_html_after'),
'actions' => array(
'remove' => $this->translate('text_action_remove_tag', 'Remove Tag'),
'placeholder' => $this->language->get('text_action_placeholder'),
'ignore' => $this->language->get('text_action_ignore')
),
'selected_action' => 'remove',
'meta' => array(
'table' => $table,
'id_field' => $cfg['id'],
'id' => (int)$row[$cfg['id']],
'field' => $cfg['field'],
'src' => $src,
'rel_path' => $cleanRelPath
)
);
}
}
}
}
}
}
file_put_contents(DIR_CACHE . 'img_opti_html_broken_grid.json', json_encode($rows));
return array(
'success' => true,
'headers' => array(
$this->language->get('text_th_preview'),
$this->language->get('text_th_entity'),
$this->language->get('text_th_before'),
$this->language->get('text_th_after'),
$this->language->get('text_th_action')
),
'rows' => $rows,
'backup_available' => false,
'quarantine_available' => false,
'description' => $this->language->get('text_description_html_broken')
);
}
public function fix_html_broken($data) {
$selected = isset($data['selected']) ? (array)$data['selected'] : array();
$fix_all = isset($data['fix_all']) ? (int)$data['fix_all'] : 0;
$cache_path = DIR_CACHE . 'img_opti_html_broken_grid.json';
if (!file_exists($cache_path)) {
return array('success' => true);
}
$grid = json_decode(file_get_contents($cache_path), true);
if (!$grid) return array('success' => true);
$rows_map = array();
foreach ($grid as $row) {
$rows_map[$row['id']] = $row;
if ($fix_all === 1 || empty($selected)) {
$selected[$row['id']] = $row['selected_action'];
}
}
$ph_product = (string)$this->config->get('module_img_opti_ph_product');
if (!$ph_product) $ph_product = 'no_image.png';
foreach ($selected as $row_id => $act) {
if ($act === 'ignore' || !isset($rows_map[$row_id])) continue;
$row = $rows_map[$row_id];
$meta = $row['meta'];
$table = preg_replace('/[^a-zA-Z0-9_]/', '', $meta['table']);
$id_field = preg_replace('/[^a-zA-Z0-9_]/', '', $meta['id_field']);
$field = preg_replace('/[^a-zA-Z0-9_]/', '', $meta['field']);
$id = (int)$meta['id'];
$src = $meta['src'];
$query = $this->db->query("SELECT `" . $field . "` FROM `" . DB_PREFIX . $table . "` WHERE `" . $id_field . "` = '" . $id . "'");
if ($query->num_rows) {
$desc = html_entity_decode($query->row[$field], ENT_QUOTES, 'UTF-8');
if ($act === 'remove') {
$desc = preg_replace('/<img[^>]+src=(?:"|\')' . preg_quote($src, '/') . '(?:"|\')[^>]*>/i', '', $desc);
} elseif ($act === 'placeholder') {
$catalog_url = (defined('HTTPS_CATALOG') && HTTPS_CATALOG) ? HTTPS_CATALOG : (defined('HTTP_CATALOG') ? HTTP_CATALOG : '');
$new_src = $catalog_url . 'image/' . $ph_product;
$desc = str_replace($src, $new_src, $desc);
}
$this->db->query("UPDATE `" . DB_PREFIX . $table . "` SET `" . $field . "` = '" . $this->db->escape($desc) . "' WHERE `" . $id_field . "` = '" . $id . "'");
}
}
@unlink($cache_path);
return array('success' => true);
}
public function scan_folder_tree($data) {
$this->loadLanguageSafe();
$dir = realpath(DIR_IMAGE . 'catalog');
$realImageDir = realpath(DIR_IMAGE);
if (!$dir || strpos(str_replace('\\', '/', $dir), str_replace('\\', '/', $realImageDir)) !== 0 || !is_dir($dir)) {
return array('error' => $this->language->get('text_err_dir'));
}
$sizes = array();
$img_sizes = array();
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS));
foreach ($iterator as $file) {
if ($file->isFile()) {
$realPath = realpath($file->getPathname());
if (!$realPath || strpos(str_replace('\\', '/', $realPath), str_replace('\\', '/', $dir)) !== 0) continue;
$folderPath = $this->mb_dirname($realPath);
$rel = ltrim(str_replace(str_replace('\\', '/', $realImageDir), '', str_replace('\\', '/', $folderPath)), '/');
if (!isset($sizes[$rel])) $sizes[$rel] = 0;
$sizes[$rel] += filesize($realPath);
$ext = strtolower($file->getExtension());
if (in_array($ext, array('jpg', 'jpeg', 'png', 'webp'))) {
if (!isset($img_sizes[$rel])) $img_sizes[$rel] = 0;
$img_sizes[$rel] += filesize($realPath);
}
}
}
arsort($sizes);
$rows = array();
foreach ($sizes as $folder => $size) {
$img_sz = isset($img_sizes[$folder]) ? $img_sizes[$folder] : 0;
$estimated_size = ($size - $img_sz) + ($img_sz * 0.65);
$after_text = $this->formatBytes($estimated_size) . ' (~' . $this->language->get('text_estimated') . ')';
$rows[] = array(
'id' => 'folder-' . md5($folder),
'preview' => '<i class="fa fa-folder text-warning" style="font-size:20px;"></i>',
'name' => '<b>' . htmlspecialchars($folder, ENT_QUOTES, 'UTF-8') . '</b>',
'before' => $this->formatBytes($size),
'after' => $after_text,
'actions' => array(
'ignore' => $this->language->get('text_action_ignore'),
'compress' => $this->language->get('text_action_compress'),
'archive' => $this->language->get('text_action_archive'),
'delete' => $this->language->get('text_action_delete'),
'archive_delete' => $this->language->get('text_action_archive_delete')
),
'selected_action' => 'ignore',
'meta' => array(
'path' => $folder
)
);
}
file_put_contents(DIR_CACHE . 'img_opti_folder_tree_grid.json', json_encode($rows));
return array(
'success' => true,
'headers' => array(
$this->language->get('text_th_preview'),
$this->language->get('text_th_entity'),
$this->language->get('text_th_before'),
$this->language->get('text_th_after'),
$this->language->get('text_th_action')
),
'rows' => $rows,
'backup_available' => false,
'quarantine_available' => false,
'description' => $this->language->get('text_description_folder_tree')
);
}
public function fix_folder_tree($data) {
$selected = isset($data['selected']) ? (array)$data['selected'] : array();
$fix_all = isset($data['fix_all']) ? (int)$data['fix_all'] : 0;
$cache_file = DIR_CACHE . 'img_opti_folder_tree_grid.json';
if (!file_exists($cache_file)) {
return array('error' => $this->language->get('error_no_scan_data'));
}
$grid = json_decode(file_get_contents($cache_file), true);
$rows_map = array();
foreach ($grid as $row) {
$rows_map[$row['id']] = $row;
if ($fix_all === 1 || empty($selected)) {
$selected[$row['id']] = $row['selected_action'];
}
}
$backup_dir = DIR_IMAGE . 'catalog_trash/backup/';
if (!is_dir($backup_dir)) {
@mkdir($backup_dir, 0755, true);
}
$this->registerModuleFolder('catalog_trash/backup');
foreach ($selected as $row_id => $act) {
if ($act === 'ignore' || !isset($rows_map[$row_id])) continue;
$row = $rows_map[$row_id];
$relPath = $row['meta']['path'];
$fullPath = realpath(DIR_IMAGE . $relPath);
$realImageDir = realpath(DIR_IMAGE);
if (!$fullPath || strpos(str_replace('\\', '/', $fullPath), str_replace('\\', '/', $realImageDir)) !== 0 || !is_dir($fullPath)) continue;
if ($relPath === 'catalog' || $relPath === 'catalog/') continue;
if ($act === 'archive' || $act === 'archive_delete') {
$zipName = 'archive_folder_' . str_replace('/', '_', trim($relPath, '/')) . '_' . date('Ymd_His') . '.zip';
$zipPath = $backup_dir . $zipName;
$this->zipDirectory($fullPath, $zipPath);
}
if ($act === 'delete' || $act === 'archive_delete') {
$this->clearDbLinksForFolder($fullPath);
$this->cleanDir($fullPath);
}
if ($act === 'compress') {
$maxWidth = (int)$this->config->get('module_img_opti_max_width') ?: 1600;
$maxHeight = (int)$this->config->get('module_img_opti_max_height') ?: 1600;
$jpgQ = (int)$this->config->get('module_img_opti_jpg_quality') ?: 75;
$pngQ = (int)$this->config->get('module_img_opti_png_quality') ?: 7;
$webpQ = (int)$this->config->get('module_img_opti_webp_quality') ?: 80;
$engine = $this->config->get('module_img_opti_engine') === 'imagick' && extension_loaded('imagick') ? 'imagick' : 'gd';
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($fullPath, RecursiveDirectoryIterator::SKIP_DOTS));
foreach ($iterator as $file) {
$limit = $this->getMemoryLimitInBytesHelper();
if ($limit > 0) {
if (memory_get_usage(true) > $limit * 0.85) {
break;
}
}
if ($file->isFile()) {
$ext = strtolower($file->getExtension());
if (in_array($ext, array('jpg', 'jpeg', 'png', 'webp'))) {
$filePath = $file->getPathname();
$tempPath = $filePath . '.tmp';
if ($this->optimizeImageHelper($filePath, $tempPath, $maxWidth, $maxHeight, $jpgQ, $pngQ, $webpQ, $engine)) {
if (file_exists($tempPath) && filesize($tempPath) > 0 && filesize($tempPath) < filesize($filePath)) {
@unlink($filePath);
@rename($tempPath, $filePath);
if (file_exists($tempPath)) {
@unlink($tempPath);
}
} else {
@unlink($tempPath);
}
}
}
}
}
}
}
@unlink($cache_file);
return array('success' => true);
}
private function clearDbLinksForFolder($dir) {
$realDir = realpath($dir);
$realImageDir = realpath(DIR_IMAGE);
if (!$realDir || strpos(str_replace('\\', '/', $realDir), str_replace('\\', '/', $realImageDir)) !== 0 || !is_dir($realDir)) return;
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($realDir, RecursiveDirectoryIterator::SKIP_DOTS));
foreach ($iterator as $file) {
if ($file->isFile()) {
$filePath = str_replace('\\', '/', $file->getPathname());
$relPath = ltrim(str_replace(str_replace('\\', '/', $realImageDir), '', $filePath), '/');
$escaped = $this->db->escape($relPath);
$this->db->query("UPDATE `" . DB_PREFIX . "product` SET image = '' WHERE image = '" . $escaped . "'");
$this->db->query("DELETE FROM `" . DB_PREFIX . "product_image` WHERE image = '" . $escaped . "'");
$this->db->query("UPDATE `" . DB_PREFIX . "category` SET image = '' WHERE image = '" . $escaped . "'");
$this->db->query("UPDATE `" . DB_PREFIX . "manufacturer` SET image = '' WHERE image = '" . $escaped . "'");
$table_query = $this->db->query("SHOW TABLES LIKE '" . DB_PREFIX . "banner_image'");
if ($table_query->num_rows) {
$this->db->query("DELETE FROM `" . DB_PREFIX . "banner_image` WHERE image = '" . $escaped . "'");
}
$blog_tables = array(
'oct_blog_article' => 'image',
'simple_blog_article' => 'image',
'newsblog_article' => 'image',
'information' => 'image'
);
foreach ($blog_tables as $table => $column) {
$table_query = $this->db->query("SHOW TABLES LIKE '" . DB_PREFIX . $table . "'");
if ($table_query->num_rows) {
$column_query = $this->db->query("SHOW COLUMNS FROM `" . DB_PREFIX . $table . "` LIKE '" . $column . "'");
if ($column_query->num_rows) {
try {
$this->db->query("UPDATE `" . DB_PREFIX . $table . "` SET `" . $column . "` = '' WHERE `" . $column . "` = '" . $escaped . "'");
} catch (Exception $e) {}
}
}
}
}
}
}
private function zipDirectory($source, $destination) {
if (!class_exists('ZipArchive')) {
return false;
}
$zip = new ZipArchive();
if (!$zip->open($destination, ZipArchive::CREATE | ZipArchive::OVERWRITE)) {
return false;
}
$source = str_replace('\\', '/', realpath($source));
if (is_dir($source) === true) {
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source), RecursiveIteratorIterator::LEAVES_ONLY);
foreach ($files as $name => $file) {
if (!$file->isDir()) {
$filePath = $file->getRealPath();
$relativePath = substr($filePath, strlen($source) + 1);
$zip->addFile($filePath, $relativePath);
}
}
} elseif (is_file($source) === true) {
$zip->addFromString($this->mb_basename($source), file_get_contents($source));
}
return $zip->close();
}
private function transliterate($text) {
$cyr = array('а','б','в','г','д','е','ё','ж','з','и','й','к','л','м','н','о','п','р','с','т','у','ф','х','ц','ч','ш','щ','ъ','ы','ь','э','ю','я','А','Б','В','Г','Д','Е','Ё','Ж','З','И','Й','К','Л','М','Н','О','П','Р','С','Т','У','Ф','Х','Ц','Ч','Ш','Щ','Ъ','Ы','Ь','Э','Ю','Я','є','і','ї','ґ','Є','І','Ї','Ґ','',' ','!','@','#','$','%','^','&','*','(',')','+','=','[',']','{','}','|','\\',':',';','"',"'",'<','>','?','/','~','`',',');
$lat = array('a','b','v','g','d','e','e','zh','z','i','y','k','l','m','n','o','p','r','s','t','u','f','h','ts','ch','sh','sch','','y','','e','yu','ya','A','B','V','G','D','E','E','Zh','Z','I','Y','K','L','M','N','O','P','R','S','T','U','F','H','Ts','Ch','Sh','Sch','','Y','','E','Yu','Ya','ye','i','yi','g','Ye','I','Yi','G','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_');
$text = str_replace($cyr, $lat, (string)$text);
$text = preg_replace('/_+/', '_', $text);
return strtolower(trim($text, '_'));
}
private function formatBytes($bytes) {
if ($bytes >= 1048576) return round($bytes / 1048576, 2) . ' MB';
if ($bytes >= 1024) return round($bytes / 1024, 0) . ' KB';
return $bytes . ' B';
}
public function generateCompareUrls($tool, $id) {
$this->loadLanguageSafe();
$original_path = '';
$type = '';
if ($tool === 'png_jpg') {
$grid_file = DIR_CACHE . 'img_opti_png_jpg_grid.json';
if (!file_exists($grid_file)) {
return array('error' => $this->language->get('error_no_scan_data'));
}
$grid = json_decode(file_get_contents($grid_file), true);
$found = false;
foreach ($grid as $row) {
if ($row['id'] === $id && isset($row['meta']['path'])) {
$original_path = $row['meta']['path'];
$type = 'png_jpg';
$found = true;
break;
}
}
if (!$found) {
return array('error' => $this->language->get('error_row_not_found'));
}
} elseif ($tool === 'watermark') {
if (is_file(DIR_IMAGE . $id)) {
$original_path = $id;
$type = 'watermark';
}
} elseif ($tool === 'optimization' || $tool === 'compress') {
if (is_file(DIR_IMAGE . $id)) {
$original_path = $id;
$type = 'compress';
}
}
if (empty($original_path) || !is_file(DIR_IMAGE . $original_path)) {
$clean_id = str_replace(array('../', '..\\', "\0"), '', $id);
if (is_file(DIR_IMAGE . $clean_id)) {
$original_path = $clean_id;
if (empty($type)) {
$type = 'compress';
}
} else {
return array('error' => $this->language->get('error_file_not_found') ?: 'File not found');
}
}
$ext = strtolower($this->mb_pathinfo($original_path, PATHINFO_EXTENSION));
$catalog_url = (defined('HTTPS_CATALOG') && HTTPS_CATALOG) ? HTTPS_CATALOG : (defined('HTTP_CATALOG') ? HTTP_CATALOG : '');
$compare_dir = DIR_IMAGE . 'cache/compare/';
if (!is_dir($compare_dir)) {
@mkdir($compare_dir, 0755, true);
}
$file_hash = md5($original_path . time());
$temp_before_name = 'before_' . $file_hash . '.' . $ext;
$temp_before_path = $compare_dir . $temp_before_name;
@copy(DIR_IMAGE . $original_path, $temp_before_path);
$temp_after_name = 'after_' . $file_hash . '.';
if ($type === 'png_jpg') {
$temp_after_name .= 'jpg';
} else {
$temp_after_name .= $ext;
}
$temp_after_path = $compare_dir . $temp_after_name;
if ($type === 'png_jpg') {
$this->convertPngToJpgHelper(DIR_IMAGE . $original_path, $temp_after_path);
} elseif ($type === 'watermark') {
@copy(DIR_IMAGE . $original_path, $temp_after_path);
$this->applyDynamicWatermark($temp_after_path, $original_path);
} else {
$maxWidth = 1600;
$maxHeight = 1600;
$jpgQ = (int)$this->config->get('module_img_opti_jpg_quality') ?: 75;
$pngQ = (int)$this->config->get('module_img_opti_png_quality') ?: 7;
$webpQ = (int)$this->config->get('module_img_opti_webp_quality') ?: 80;
$engine = $this->config->get('module_img_opti_engine') === 'imagick' && extension_loaded('imagick') ? 'imagick' : 'gd';
if (isset($this->request->post['max_width'])) $maxWidth = (int)$this->request->post['max_width'];
if (isset($this->request->post['max_height'])) $maxHeight = (int)$this->request->post['max_height'];
if (isset($this->request->post['jpg_quality'])) $jpgQ = (int)$this->request->post['jpg_quality'];
if (isset($this->request->post['png_quality'])) $pngQ = (int)$this->request->post['png_quality'];
if (isset($this->request->post['webp_quality'])) $webpQ = (int)$this->request->post['webp_quality'];
$this->optimizeImageHelper(DIR_IMAGE . $original_path, $temp_after_path, $maxWidth, $maxHeight, $jpgQ, $pngQ, $webpQ, $engine);
}
$url_before = $catalog_url . 'image/cache/compare/' . $temp_before_name;
$url_after = $catalog_url . 'image/cache/compare/' . $temp_after_name;
$size_before = filesize(DIR_IMAGE . $original_path);
$size_after = file_exists($temp_after_path) ? filesize($temp_after_path) : 0;
return array(
'success' => true,
'before' => $url_before,
'after' => $url_after,
'size_before' => $this->formatBytes($size_before),
'size_after' => $this->formatBytes($size_after),
'saving' => ($size_before > 0) ? round((($size_before - $size_after) / $size_before) * 100, 1) : 0
);
}
public function convertPngToJpgHelper($src, $dest) {
$img = @imagecreatefrompng($src);
if ($img) {
if (function_exists('imagepalettetotruecolor') && !imageistruecolor($img)) {
imagepalettetotruecolor($img);
}
imagealphablending($img, true);
$bg = imagecreatetruecolor(imagesx($img), imagesy($img));
imagefill($bg, 0, 0, imagecolorallocate($bg, 255, 255, 255));
imagecopy($bg, $img, 0, 0, 0, 0, imagesx($img), imagesy($img));
$res = imagejpeg($bg, $dest, 85);
imagedestroy($img);
imagedestroy($bg);
return $res;
}
return false;
}
public function optimizeImageHelper($src, $dest, $maxWidth, $maxHeight, $jpgQuality, $pngCompression, $webpQuality, $engine = 'gd') {
if ($engine == 'imagick' && extension_loaded('imagick')) {
try {
$img = new Imagick($src);
$img->transformImageColorspace(Imagick::COLORSPACE_SRGB);
$width = $img->getImageWidth();
$height = $img->getImageHeight();
$ext = strtolower($this->mb_pathinfo($src, PATHINFO_EXTENSION));
if (($maxWidth > 0 && $width > $maxWidth) || ($maxHeight > 0 && $height > $maxHeight)) {
$targetW = $maxWidth;
$targetH = $maxHeight;
if ($targetW == 0) {
$targetW = (int)round($width * ($targetH / $height));
}
if ($targetH == 0) {
$targetH = (int)round($height * ($targetW / $width));
}
$img->resizeImage($targetW, $targetH, Imagick::FILTER_LANCZOS, 1, true);
}
$img->stripImage();
if ($ext === 'png') {
$img->setImageFormat('png');
$img->setCompressionQuality($pngCompression * 10);
} elseif ($ext === 'webp') {
$img->setImageFormat('webp');
$img->setCompressionQuality($webpQuality);
} else {
$img->setImageFormat('jpeg');
$img->setImageCompression(Imagick::COMPRESSION_JPEG);
$img->setCompressionQuality($jpgQuality);
}
return $img->writeImage($dest);
} catch (Exception $e) {
$engine = 'gd';
}
}
if ($engine == 'gd') {
$img = @imagecreatefromstring(file_get_contents($src));
if (!$img) return false;
if (function_exists('imagepalettetotruecolor') && !imageistruecolor($img)) {
imagepalettetotruecolor($img);
}
$width = imagesx($img);
$height = imagesy($img);
$ext = strtolower($this->mb_pathinfo($src, PATHINFO_EXTENSION));
if ($ext === 'png' || $ext === 'webp') {
imagealphablending($img, false);
imagesavealpha($img, true);
}
if (($maxWidth > 0 && $width > $maxWidth) || ($maxHeight > 0 && $height > $maxHeight)) {
$ratioX = ($maxWidth > 0) ? ($maxWidth / $width) : 1;
$ratioY = ($maxHeight > 0) ? ($maxHeight / $height) : 1;
$ratio = min($ratioX, $ratioY);
$newW = (int)($width * $ratio);
$newH = (int)($height * $ratio);
$newImg = imagecreatetruecolor($newW, $newH);
if ($ext === 'png' || $ext === 'webp') {
imagealphablending($newImg, false);
imagesavealpha($newImg, true);
$transparent = imagecolorallocatealpha($newImg, 255, 255, 255, 127);
imagefill($newImg, 0, 0, $transparent);
} else {
$white = imagecolorallocate($newImg, 255, 255, 255);
imagefill($newImg, 0, 0, $white);
}
imagecopyresampled($newImg, $img, 0, 0, 0, 0, $newW, $newH, $width, $height);
imagedestroy($img);
$img = $newImg;
}
$res = false;
if ($ext === 'png') {
$res = imagepng($img, $dest, $pngCompression);
} elseif ($ext === 'webp' && function_exists('imagewebp')) {
$res = imagewebp($img, $dest, $webpQuality);
} else {
$res = imagejpeg($img, $dest, $jpgQuality);
}
imagedestroy($img);
return $res;
}
return false;
}
private function safe_utf8($str) {
$str = (string)$str;
if (mb_check_encoding($str, 'UTF-8')) {
return $str;
}
$converted = @mb_convert_encoding($str, 'UTF-8', 'Windows-1251');
return $converted ? $converted : $str;
}
public function scan_sanitizer($data = array()) {
$this->loadLanguageSafe();
$rows = array();
$dir = realpath(DIR_IMAGE . 'catalog');
$realImageDir = realpath(DIR_IMAGE);
if (!$dir || strpos(str_replace('\\', '/', $dir), str_replace('\\', '/', $realImageDir)) !== 0 || !is_dir($dir)) {
return array('error' => $this->language->get('text_err_dir'));
}
$limit = isset($data['module_img_opti_sanitizer_limit']) ? (int)$data['module_img_opti_sanitizer_limit'] : 100;
if ($limit <= 0) $limit = 100;
$sz_translit = isset($data['module_img_opti_sz_translit']) ? (int)$data['module_img_opti_sz_translit'] : 1;
$sz_spaces = isset($data['module_img_opti_sz_spaces']) ? (int)$data['module_img_opti_sz_spaces'] : 1;
$sz_special = isset($data['module_img_opti_sz_special']) ? (int)$data['module_img_opti_sz_special'] : 1;
$sz_lowercase_name = isset($data['module_img_opti_sz_lowercase_name']) ? (int)$data['module_img_opti_sz_lowercase_name'] : 1;
$sz_lowercase_ext = isset($data['module_img_opti_sz_lowercase_ext']) ? (int)$data['module_img_opti_sz_lowercase_ext'] : 1;
$sz_normalize_ext = isset($data['module_img_opti_sz_normalize_ext']) ? (int)$data['module_img_opti_sz_normalize_ext'] : 1;
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS));
$this->load->model('tool/image');
$assigned_paths = array();
foreach ($iterator as $file) {
if ($file->isFile()) {
$realPath = realpath($file->getPathname());
if ($realPath && strpos(str_replace('\\', '/', $realPath), str_replace('\\', '/', $dir)) === 0) {
$relPath = $this->getRelativeImagePath($realPath);
$filename = $this->mb_basename($relPath);
$proposedRel = $this->sanitizeRelativePath($relPath, $sz_translit, $sz_spaces, $sz_special, $sz_lowercase_name, $sz_lowercase_ext, $sz_normalize_ext);
if ($relPath !== $proposedRel) {
$proposedFullPath = DIR_IMAGE . $proposedRel;
if (file_exists($proposedFullPath) || isset($assigned_paths[$proposedRel]) || $this->isPathUsedInDb($proposedRel)) {
$ext = $this->mb_pathinfo($proposedRel, PATHINFO_EXTENSION);
$filename_no_ext = $this->mb_pathinfo($proposedRel, PATHINFO_FILENAME);
$dirname = $this->mb_pathinfo($proposedRel, PATHINFO_DIRNAME);
$dir_prefix = ($dirname === '.' || $dirname === '') ? '' : $dirname . '/';
$counter = 1;
while (true) {
$candidate = $dir_prefix . $filename_no_ext . '_' . $counter . ($ext !== '' ? '.' . $ext : '');
if (!file_exists(DIR_IMAGE . $candidate) && !isset($assigned_paths[$candidate]) && !$this->isPathUsedInDb($candidate)) {
$proposedRel = $candidate;
break;
}
$counter++;
}
}
$assigned_paths[$proposedRel] = true;
$thumb = $this->model_tool_image->resize($relPath, 100, 100);
$entities = $this->getEntitiesByImagePath($relPath);
$entities_html = !empty($entities) ? implode('<br>', $entities) : '<i>' . $this->language->get('text_no_relations') . '</i>';
$rows[] = array(
'id' => 'sanitizer-' . md5($relPath),
'preview' => '<img src="' . $thumb . '" class="img-thumbnail" style="max-width:100px; max-height:100px;">',
'name' => '<b>' . htmlspecialchars($relPath, ENT_QUOTES, 'UTF-8') . '</b><br>' . $entities_html,
'before' => $filename,
'after' => $this->mb_basename($proposedRel),
'actions' => array(
'rename' => $this->language->get('text_action_apply_changes'),
'ignore' => $this->language->get('text_action_ignore')
),
'selected_action' => 'rename',
'meta' => array(
'path' => $relPath,
'new_path' => $proposedRel
)
);
if (count($rows) >= $limit) {
break;
}
}
}
}
}
file_put_contents(DIR_CACHE . 'img_opti_sanitizer_grid.json', json_encode($rows));
return array(
'success' => true,
'headers' => array(
$this->language->get('text_th_preview'),
$this->language->get('text_th_entity'),
$this->language->get('text_th_before'),
$this->language->get('text_th_after'),
$this->language->get('text_th_action')
),
'rows' => $rows,
'backup_available' => true,
'quarantine_available' => true,
'description' => $this->language->get('text_description_sanitizer')
);
}
public function fix_sanitizer($data) {
$this->backupCatalogTables('sanitizer');
$selected = isset($data['selected']) ? (array)$data['selected'] : array();
$fix_all = isset($data['fix_all']) ? (int)$data['fix_all'] : 0;
$grid = json_decode(file_get_contents(DIR_CACHE . 'img_opti_sanitizer_grid.json'), true);
$rows_map = array();
foreach ($grid as $row) {
$rows_map[$row['id']] = $row;
if ($fix_all === 1 || empty($selected)) {
$selected[$row['id']] = $row['selected_action'];
}
}
$realImageDir = realpath(DIR_IMAGE);
foreach ($selected as $row_id => $act) {
if ($act !== 'rename' || !isset($rows_map[$row_id])) continue;
$row = $rows_map[$row_id];
$cleanBadFile = str_replace(array('../', '..\\', "\0"), '', (string)$row['meta']['path']);
$fullPath = realpath(DIR_IMAGE . $cleanBadFile);
if (!$fullPath || strpos(str_replace('\\', '/', $fullPath), str_replace('\\', '/', $realImageDir)) !== 0 || !is_file($fullPath)) continue;
$newRel = (string)$row['meta']['new_path'];
$newFullPathStr = DIR_IMAGE . $newRel;
$newDirStr = $this->mb_dirname($newFullPathStr);
if (!is_dir($newDirStr)) {
@mkdir($newDirStr, 0755, true);
}
$realNewDir = realpath($newDirStr);
if (!$realNewDir || strpos(str_replace('\\', '/', $realNewDir), str_replace('\\', '/', $realImageDir)) !== 0) continue;
$newFullPath = $realNewDir . DIRECTORY_SEPARATOR . $this->mb_basename($newFullPathStr);
if (@copy($fullPath, $newFullPath)) {
$dbOld = $this->db->escape($cleanBadFile);
$dbNew = $this->db->escape($newRel);
$this->db->query("UPDATE `" . DB_PREFIX . "product` SET image = '" . $dbNew . "' WHERE image = '" . $dbOld . "'");
$this->db->query("UPDATE `" . DB_PREFIX . "product_image` SET image = '" . $dbNew . "' WHERE image = '" . $dbOld . "'");
$this->db->query("UPDATE `" . DB_PREFIX . "category` SET image = '" . $dbNew . "' WHERE image = '" . $dbOld . "'");
$this->db->query("UPDATE `" . DB_PREFIX . "manufacturer` SET image = '" . $dbNew . "' WHERE image = '" . $dbOld . "'");
$table_query = $this->db->query("SHOW TABLES LIKE '" . DB_PREFIX . "banner_image'");
if ($table_query->num_rows) {
$this->db->query("UPDATE `" . DB_PREFIX . "banner_image` SET image = '" . $dbNew . "' WHERE image = '" . $dbOld . "'");
}
$blog_tables = array(
'oct_blog_article' => 'image',
'simple_blog_article' => 'image',
'newsblog_article' => 'image',
'information' => 'image'
);
foreach ($blog_tables as $table => $column) {
$table_query = $this->db->query("SHOW TABLES LIKE '" . DB_PREFIX . $table . "'");
if ($table_query->num_rows) {
$column_query = $this->db->query("SHOW COLUMNS FROM `" . DB_PREFIX . $table . "` LIKE '" . $column . "'");
if ($column_query->num_rows) {
try {
$this->db->query("UPDATE `" . DB_PREFIX . $table . "` SET `" . $column . "` = '" . $dbNew . "' WHERE `" . $column . "` = '" . $dbOld . "'");
} catch (Exception $e) {}
}
}
}
$this->quarantineFile($cleanBadFile, 'sanitizer');
}
}
@unlink(DIR_CACHE . 'img_opti_sanitizer_grid.json');
return array('success' => true);
}
private function sanitizeRelativePath($relPath, $sz_translit, $sz_spaces, $sz_special, $sz_lowercase_name, $sz_lowercase_ext, $sz_normalize_ext) {
$parts = explode('/', str_replace('\\', '/', $relPath));
$newParts = array();
for ($i = 0; $i < count($parts) - 1; $i++) {
$part = $parts[$i];
if ($part === 'catalog' || $part === '') {
$newParts[] = $part;
} else {
$newParts[] = $this->sanitizePathSegment($part, $sz_translit, $sz_spaces, $sz_special, $sz_lowercase_name);
}
}
$filename = end($parts);
if ($filename !== '') {
$ext = $this->mb_pathinfo($filename, PATHINFO_EXTENSION);
$nameOnly = $this->mb_pathinfo($filename, PATHINFO_FILENAME);
$newName = $this->sanitizePathSegment($nameOnly, $sz_translit, $sz_spaces, $sz_special, $sz_lowercase_name);
$newExt = $ext;
if ($sz_lowercase_ext) {
$newExt = function_exists('mb_strtolower') ? mb_strtolower($newExt, 'UTF-8') : strtolower($newExt);
}
if ($sz_normalize_ext) {
if (strtolower($newExt) === 'jpeg') {
$newExt = 'jpg';
}
}
$cleanName = $newName . ($newExt !== '' ? '.' . $newExt : '');
$newParts[] = $cleanName;
}
return implode('/', $newParts);
}
private function sanitizePathSegment($segment, $sz_translit, $sz_spaces, $sz_special, $sz_lowercase) {
$newSegment = $segment;
if ($sz_translit) {
$newSegment = $this->transliterate($newSegment);
}
if ($sz_spaces) {
$newSegment = str_replace(' ', '_', $newSegment);
}
if ($sz_special) {
$newSegment = preg_replace('/[^a-zA-Z0-9_\-\.]/', '', $newSegment);
}
if ($sz_lowercase) {
$newSegment = function_exists('mb_strtolower') ? mb_strtolower($newSegment, 'UTF-8') : strtolower($newSegment);
}
$newSegment = preg_replace('/_+/', '_', $newSegment);
$newSegment = preg_replace('/\-+/', '-', $newSegment);
$newSegment = trim($newSegment, '_-');
if ($newSegment === '') {
$fallback = $this->transliterate($segment);
if ($sz_special) {
$fallback = preg_replace('/[^a-zA-Z0-9_\-\.]/', '', $fallback);
}
if ($sz_lowercase) {
$fallback = function_exists('mb_strtolower') ? mb_strtolower($fallback, 'UTF-8') : strtolower($fallback);
}
$fallback = trim(preg_replace('/_+/', '_', str_replace(' ', '_', $fallback)), '_-');
if ($fallback !== '') {
$newSegment = $fallback;
} else {
$newSegment = 'img_' . substr(md5($segment), 0, 8);
}
}
return $newSegment;
}
private function translate($key, $default = '') {
if (isset($this->translations[$key])) {
return $this->translations[$key];
}
return $default ? $default : $key;
}
private function loadLanguageSafe() {
$data = array();
$en_paths = array(
DIR_LANGUAGE . 'en-gb/extension/module/img_opti.php',
DIR_LANGUAGE . 'english/extension/module/img_opti.php'
);
foreach ($en_paths as $path) {
if (is_file($path)) {
$_ = array();
require($path);
$data = array_merge($data, $_);
break;
}
}
$lang = $this->config->get('config_admin_language');
if (!$lang) {
$lang = $this->config->get('config_language');
}
if (isset($this->session->data['language'])) {
$lang = $this->session->data['language'];
}
if (!$lang) {
$lang = 'en-gb';
}
$lang_lower = strtolower((string)$lang);
if ($lang_lower && $lang_lower !== 'en-gb' && $lang_lower !== 'english') {
$paths = array(
DIR_LANGUAGE . $lang . '/extension/module/img_opti.php',
DIR_LANGUAGE . $lang_lower . '/extension/module/img_opti.php'
);
if (isset($this->db)) {
try {
$escaped_lang = $this->db->escape($lang);
$query_lang = $this->db->query("SELECT directory FROM `" . DB_PREFIX . "language` WHERE code = '" . $escaped_lang . "' OR directory = '" . $escaped_lang . "' LIMIT 1");
if ($query_lang && $query_lang->num_rows && !empty($query_lang->row['directory'])) {
$db_dir = $query_lang->row['directory'];
$paths[] = DIR_LANGUAGE . $db_dir . '/extension/module/img_opti.php';
$paths[] = DIR_LANGUAGE . strtolower($db_dir) . '/extension/module/img_opti.php';
}
} catch (Exception $e) {}
}
if (strpos($lang_lower, 'ru') !== false || strpos($lang_lower, 'rus') !== false) {
$paths[] = DIR_LANGUAGE . 'ru-ru/extension/module/img_opti.php';
$paths[] = DIR_LANGUAGE . 'russian/extension/module/img_opti.php';
$paths[] = DIR_LANGUAGE . 'ru-RU/extension/module/img_opti.php';
$paths[] = DIR_LANGUAGE . 'ru/extension/module/img_opti.php';
} elseif (strpos($lang_lower, 'uk') !== false || strpos($lang_lower, 'ua') !== false || strpos($lang_lower, 'ukr') !== false) {
$paths[] = DIR_LANGUAGE . 'uk-ua/extension/module/img_opti.php';
$paths[] = DIR_LANGUAGE . 'ukrainian/extension/module/img_opti.php';
$paths[] = DIR_LANGUAGE . 'uk-UA/extension/module/img_opti.php';
$paths[] = DIR_LANGUAGE . 'uk/extension/module/img_opti.php';
$paths[] = DIR_LANGUAGE . 'ua/extension/module/img_opti.php';
$paths[] = DIR_LANGUAGE . 'uk_ua/extension/module/img_opti.php';
$paths[] = DIR_LANGUAGE . 'uk_UA/extension/module/img_opti.php';
}
foreach ($paths as $path) {
if (is_file($path)) {
$_ = array();
require($path);
$data = array_merge($data, $_);
break;
}
}
}
foreach ($data as $key => $value) {
$this->language->set($key, $value);
}
$this->translations = $data;
return $data;
}
private function getMemoryLimitInBytesHelper() {
$limit = ini_get('memory_limit');
if (!$limit || (int)$limit === -1) return 512 * 1024 * 1024;
$unit = strtolower(substr($limit, -1));
$bytes = (int)$limit;
switch ($unit) {
case 'g': $bytes *= 1024;
case 'm': $bytes *= 1024;
case 'k': $bytes *= 1024;
}
return $bytes;
}
public function scan_cmyk($data) {
$this->loadLanguageSafe();
$rows = array();
$dir = realpath(DIR_IMAGE . 'catalog');
$realImageDir = realpath(DIR_IMAGE);
if (!$dir || strpos(str_replace('\\', '/', $dir), str_replace('\\', '/', $realImageDir)) !== 0 || !is_dir($dir)) {
return array('error' => $this->language->get('text_err_dir'));
}
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS));
$this->load->model('tool/image');
foreach ($iterator as $file) {
if ($file->isFile() && in_array(strtolower($file->getExtension()), array('jpg', 'jpeg'))) {
$realPath = realpath($file->getPathname());
if ($realPath && strpos(str_replace('\\', '/', $realPath), str_replace('\\', '/', $dir)) === 0) {
$imgSize = @getimagesize($realPath);
if ($imgSize) {
$is_cmyk = false;
if (isset($imgSize['channels']) && $imgSize['channels'] == 4) {
$is_cmyk = true;
}
if (extension_loaded('imagick')) {
try {
$img = new Imagick($realPath);
if ($img->getImageColorspace() == Imagick::COLORSPACE_CMYK) {
$is_cmyk = true;
}
$img->clear(); $img->destroy();
} catch (Exception $e) {}
}
if ($is_cmyk) {
$relPath = $this->getRelativeImagePath($realPath);
$thumb = $this->model_tool_image->resize($relPath, 100, 100);
$entities = $this->getEntitiesByImagePath($relPath);
$entities_html = !empty($entities) ? implode('<br>', $entities) : '<i>' . $this->language->get('text_no_relations') . '</i>';
$size = filesize($realPath);
$rows[] = array(
'id' => 'cmyk-' . md5($relPath),
'preview' => '<img src="' . $thumb . '" class="img-thumbnail" style="max-width:100px; max-height:100px;">',
'name' => '<b>' . htmlspecialchars($relPath, ENT_QUOTES, 'UTF-8') . '</b><br>' . $entities_html,
'before' => 'CMYK',
'after' => 'sRGB',
'actions' => array(
'convert' => $this->translate('text_action_convert_rgb', 'Convert to RGB'),
'ignore' => $this->language->get('text_action_ignore')
),
'selected_action' => 'convert',
'meta' => array(
'path' => $relPath
)
);
}
}
}
}
}
file_put_contents(DIR_CACHE . 'img_opti_cmyk_grid.json', json_encode($rows));
return array(
'success' => true,
'headers' => array(
$this->language->get('text_th_preview'),
$this->language->get('text_th_entity'),
$this->language->get('text_th_before'),
$this->language->get('text_th_after'),
$this->language->get('text_th_action')
),
'rows' => $rows,
'backup_available' => true,
'quarantine_available' => false,
'description' => $this->translate('text_description_cmyk', 'Found JPEGs in CMYK colorspace. Conversion to sRGB ensures they are displayed correctly on all platforms, including iOS/macOS.')
);
}
public function fix_cmyk($data) {
$this->backupCatalogTables('cmyk');
$selected = isset($data['selected']) ? (array)$data['selected'] : array();
$fix_all = isset($data['fix_all']) ? (int)$data['fix_all'] : 0;
$grid = json_decode(file_get_contents(DIR_CACHE . 'img_opti_cmyk_grid.json'), true);
$rows_map = array();
if ($grid) {
foreach ($grid as $row) {
$rows_map[$row['id']] = $row;
if ($fix_all === 1 || empty($selected)) {
$selected[$row['id']] = $row['selected_action'];
}
}
}
$realImageDir = realpath(DIR_IMAGE);
foreach ($selected as $row_id => $act) {
if ($act !== 'convert' || !isset($rows_map[$row_id])) continue;
$row = $rows_map[$row_id];
$relPath = str_replace(array('../', '..\\', "\0"), '', (string)$row['meta']['path']);
$fullPath = realpath(DIR_IMAGE . $relPath);
if (!$fullPath || strpos(str_replace('\\', '/', $fullPath), str_replace('\\', '/', $realImageDir)) !== 0 || !is_file($fullPath)) continue;
if ($this->normalizeCmykToRgbJpeg($fullPath, 90)) {
$this->deleteImageCache($relPath);
}
}
@unlink(DIR_CACHE . 'img_opti_cmyk_grid.json');
return array('success' => true);
}
public function scan_heavy_files($data) {
$this->loadLanguageSafe();
$rows = array();
$dir = realpath(DIR_IMAGE . 'catalog');
$realImageDir = realpath(DIR_IMAGE);
if (!$dir || strpos(str_replace('\\', '/', $dir), str_replace('\\', '/', $realImageDir)) !== 0 || !is_dir($dir)) {
return array('error' => $this->language->get('text_err_dir'));
}
$limit = isset($data['module_img_opti_heavy_limit']) ? (int)$data['module_img_opti_heavy_limit'] : 20;
if ($limit <= 0) $limit = 20;
$min_size = isset($data['module_img_opti_heavy_min_size']) ? (int)$data['module_img_opti_heavy_min_size'] : 500;
if ($min_size < 0) $min_size = 500;
$min_size_bytes = $min_size * 1024;
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS));
$all_files = array();
foreach ($iterator as $file) {
if ($file->isFile() && in_array(strtolower($file->getExtension()), array('jpg', 'jpeg', 'png', 'webp'))) {
$realPath = realpath($file->getPathname());
if ($realPath && strpos(str_replace('\\', '/', $realPath), str_replace('\\', '/', $dir)) === 0) {
$size = filesize($realPath);
if ($size >= $min_size_bytes) {
$all_files[$realPath] = $size;
}
}
}
}
arsort($all_files);
$top_files = array_slice($all_files, 0, $limit, true);
$this->load->model('tool/image');
foreach ($top_files as $realPath => $size) {
$relPath = $this->getRelativeImagePath($realPath);
$thumb = $this->model_tool_image->resize($relPath, 100, 100);
$entities = $this->getEntitiesByImagePath($relPath);
$entities_html = !empty($entities) ? implode('<br>', $entities) : '<i>' . $this->language->get('text_no_relations') . '</i>';
$w = 0; $h = 0;
$imgSize = @getimagesize($realPath);
if ($imgSize) {
$w = $imgSize[0];
$h = $imgSize[1];
}
$rows[] = array(
'id' => 'heavy-' . md5($relPath),
'preview' => '<img src="' . $thumb . '" class="img-thumbnail" style="max-width:100px; max-height:100px;">',
'name' => '<b>' . htmlspecialchars($relPath, ENT_QUOTES, 'UTF-8') . '</b><br>' . $entities_html,
'before' => $this->formatBytes($size) . ($w ? ' (' . $w . 'x' . $h . 'px)' : ''),
'after' => $this->translate('text_action_compress', 'Compress'),
'actions' => array(
'compress' => $this->translate('text_action_compress', 'Compress'),
'ignore' => $this->language->get('text_action_ignore')
),
'selected_action' => 'compress',
'meta' => array(
'path' => $relPath
)
);
}
file_put_contents(DIR_CACHE . 'img_opti_heavy_files_grid.json', json_encode($rows));
return array(
'success' => true,
'headers' => array(
$this->language->get('text_th_preview'),
$this->language->get('text_th_entity'),
$this->language->get('text_th_before'),
$this->language->get('text_th_after'),
$this->language->get('text_th_action')
),
'rows' => $rows,
'backup_available' => true,
'quarantine_available' => false,
'description' => sprintf($this->translate('text_description_heavy', 'List of the top %d heaviest files in the image directory. You can optimize them here directly.'), $limit)
);
}
public function fix_heavy_files($data) {
$this->backupCatalogTables('heavy_files');
$selected = isset($data['selected']) ? (array)$data['selected'] : array();
$fix_all = isset($data['fix_all']) ? (int)$data['fix_all'] : 0;
$grid = json_decode(file_get_contents(DIR_CACHE . 'img_opti_heavy_files_grid.json'), true);
$rows_map = array();
if ($grid) {
foreach ($grid as $row) {
$rows_map[$row['id']] = $row;
if ($fix_all === 1 || empty($selected)) {
$selected[$row['id']] = $row['selected_action'];
}
}
}
$realImageDir = realpath(DIR_IMAGE);
$maxWidth = (int)$this->config->get('module_img_opti_max_width');
if (!$maxWidth) $maxWidth = 1600;
$maxHeight = (int)$this->config->get('module_img_opti_max_height');
if (!$maxHeight) $maxHeight = 1600;
$jpgQ = (int)$this->config->get('module_img_opti_jpg_quality');
if (!$jpgQ) $jpgQ = 72;
$pngQ = (int)$this->config->get('module_img_opti_png_quality');
if (!$pngQ) $pngQ = 7;
$webpQ = (int)$this->config->get('module_img_opti_webp_quality');
if (!$webpQ) $webpQ = 80;
$engine = $this->config->get('module_img_opti_engine') == 'imagick' && extension_loaded('imagick') ? 'imagick' : 'gd';
foreach ($selected as $row_id => $act) {
if ($act !== 'compress' || !isset($rows_map[$row_id])) continue;
$row = $rows_map[$row_id];
$relPath = str_replace(array('../', '..\\', "\0"), '', (string)$row['meta']['path']);
$fullPath = realpath(DIR_IMAGE . $relPath);
if (!$fullPath || strpos(str_replace('\\', '/', $fullPath), str_replace('\\', '/', $realImageDir)) !== 0 || !is_file($fullPath)) continue;
$tempPath = DIR_CACHE . 'opti_temp_heavy_' . md5($fullPath);
if ($this->optimizeImageHelper($fullPath, $tempPath, $maxWidth, $maxHeight, $jpgQ, $pngQ, $webpQ, $engine)) {
if (file_exists($tempPath)) {
@unlink($fullPath);
if (@rename($tempPath, $fullPath)) {
$this->deleteImageCache($relPath);
} else {
if (@copy($tempPath, $fullPath)) {
@unlink($tempPath);
$this->deleteImageCache($relPath);
}
}
}
}
}
@unlink(DIR_CACHE . 'img_opti_heavy_files_grid.json');
return array('success' => true);
}
private function deleteImageCache($relPath) {
$info = $this->mb_pathinfo($relPath);
if (!isset($info['filename'])) return;
$filename = $info['filename'];
$cache_dir = realpath(DIR_IMAGE . 'cache');
if (!$cache_dir || !is_dir($cache_dir)) return;
try {
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($cache_dir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($iterator as $file) {
if ($file->isFile()) {
$fn = $file->getFilename();
if (strpos($fn, $filename . '-') === 0 || strpos($fn, $filename . '.') === 0 || strpos($fn, $filename . '_') === 0) {
@unlink($file->getPathname());
}
}
}
} catch (\Exception $e) {}
}
private function mb_pathinfo($path, $options = null) {
$ret = array('dirname' => '', 'basename' => '', 'extension' => '', 'filename' => '');
$path = str_replace('\\', '/', $path);
$last_slash = strrpos($path, '/');
if ($last_slash !== false) {
$ret['dirname'] = substr($path, 0, $last_slash);
$basename = substr($path, $last_slash + 1);
} else {
$basename = $path;
}
$ret['basename'] = $basename;
$last_dot = strrpos($basename, '.');
if ($last_dot !== false) {
$ret['filename'] = substr($basename, 0, $last_dot);
$ret['extension'] = substr($basename, $last_dot + 1);
} else {
$ret['filename'] = $basename;
}
if ($options === PATHINFO_DIRNAME) return $ret['dirname'];
if ($options === PATHINFO_BASENAME) return $ret['basename'];
if ($options === PATHINFO_EXTENSION) return $ret['extension'];
if ($options === PATHINFO_FILENAME) return $ret['filename'];
return $ret;
}
private function isPathUsedInDb($path) {
$escaped = $this->db->escape((string)$path);
$query = $this->db->query("SELECT product_id FROM `" . DB_PREFIX . "product` WHERE image = '" . $escaped . "' LIMIT 1");
if ($query->num_rows) return true;
$query = $this->db->query("SELECT product_id FROM `" . DB_PREFIX . "product_image` WHERE image = '" . $escaped . "' LIMIT 1");
if ($query->num_rows) return true;
$query = $this->db->query("SELECT category_id FROM `" . DB_PREFIX . "category` WHERE image = '" . $escaped . "' LIMIT 1");
if ($query->num_rows) return true;
$query = $this->db->query("SELECT manufacturer_id FROM `" . DB_PREFIX . "manufacturer` WHERE image = '" . $escaped . "' LIMIT 1");
if ($query->num_rows) return true;
return false;
}
private function mb_dirname($path) {
$path = str_replace('\\', '/', $path);
$last_slash = strrpos($path, '/');
if ($last_slash !== false) {
return substr($path, 0, $last_slash);
}
return '.';
}
private function mb_basename($path) {
$path = str_replace('\\', '/', $path);
$parts = explode('/', $path);
return end($parts);
}
private function getRelativeImagePath($absolutePath) {
$absolutePath = str_replace('\\', '/', $absolutePath);
$dirImageNorm = str_replace('\\', '/', DIR_IMAGE);
$rel = $absolutePath;
if (substr($absolutePath, 0, strlen($dirImageNorm)) === $dirImageNorm) {
$rel = substr($absolutePath, strlen($dirImageNorm));
} elseif (strtolower(substr($absolutePath, 0, 3)) === strtolower(substr($dirImageNorm, 0, 3))) {
$dirImageNormLower = strtolower($dirImageNorm);
$absLower = strtolower($absolutePath);
if (substr($absLower, 0, strlen($dirImageNormLower)) === $dirImageNormLower) {
$rel = substr($absolutePath, strlen($dirImageNorm));
}
}
$rel = ltrim($rel, '/');
return $this->safe_utf8($rel);
}
public function autoRotateImage($filePath, $im = null) {
if (!function_exists('exif_read_data')) {
return $im;
}
$ext = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
if (!in_array($ext, array('jpg', 'jpeg', 'tiff'))) {
return $im;
}
$exif = @exif_read_data($filePath);
if (!$exif || !isset($exif['Orientation'])) {
return $im;
}
$orientation = (int)$exif['Orientation'];
$angle = 0;
switch ($orientation) {
case 3:
$angle = 180;
break;
case 6:
$angle = -90;
break;
case 8:
$angle = 90;
break;
}
if ($angle !== 0) {
if ($im !== null) {
$im = @imagerotate($im, $angle, 0);
} else {
$img = @imagecreatefromjpeg($filePath);
if ($img) {
$img = @imagerotate($img, $angle, 0);
@imagejpeg($img, $filePath, 95);
@imagedestroy($img);
}
}
}
return $im;
}
public function convertPngToJpgClean($sourcePngPath, $targetJpgPath, $quality = 100, $bgColorHex = '#ffffff') {
if (extension_loaded('imagick')) {
try {
$im = new \Imagick($sourcePngPath);
$bg = new \Imagick();
$fillColor = new \ImagickPixel($bgColorHex);
$bg->newImage($im->getImageWidth(), $im->getImageHeight(), $fillColor);
$bg->compositeImage($im, \Imagick::COMPOSITE_OVER, 0, 0);
$bg->setImageFormat('jpg');
$bg->setImageCompressionQuality($quality);
$bg->writeImage($targetJpgPath);
$bg->clear(); $bg->destroy();
$im->clear(); $im->destroy();
return true;
} catch (\Exception $e) {}
}
$img = @imagecreatefrompng($sourcePngPath);
if (!$img) return false;
if (function_exists('imagepalettetotruecolor') && !imageistruecolor($img)) {
imagepalettetotruecolor($img);
}
$width = imagesx($img);
$height = imagesy($img);
$canvas = imagecreatetruecolor($width, $height);
$hex = ltrim($bgColorHex, '#');
if (strlen($hex) === 3) {
$r = hexdec(substr($hex, 0, 1) . substr($hex, 0, 1));
$g = hexdec(substr($hex, 1, 1) . substr($hex, 1, 1));
$b = hexdec(substr($hex, 2, 1) . substr($hex, 2, 1));
} else {
$r = hexdec(substr($hex, 0, 2));
$g = hexdec(substr($hex, 2, 2));
$b = hexdec(substr($hex, 4, 2));
}
$bgColor = imagecolorallocate($canvas, $r, $g, $b);
imagefill($canvas, 0, 0, $bgColor);
imagealphablending($canvas, true);
imagecopy($canvas, $img, 0, 0, 0, 0, $width, $height);
$success = @imagejpeg($canvas, $targetJpgPath, $quality);
imagedestroy($img);
imagedestroy($canvas);
return $success;
}
public function convertPngToWebpClean($sourcePngPath, $targetWebpPath, $quality = 80) {
if (!function_exists('imagewebp')) return false;
$img = @imagecreatefrompng($sourcePngPath);
if (!$img) return false;
if (function_exists('imagepalettetotruecolor') && !imageistruecolor($img)) {
imagepalettetotruecolor($img);
}
imagealphablending($img, false);
imagesavealpha($img, true);
$success = @imagewebp($img, $targetWebpPath, $quality);
imagedestroy($img);
return $success;
}
public function normalizePngPalette($img) {
if ($img && function_exists('imagepalettetotruecolor') && !imageistruecolor($img)) {
imagepalettetotruecolor($img);
}
return $img;
}
public function normalizeCmykToRgbJpeg($fullPath, $quality = 90) {
if (!is_file($fullPath)) return false;
if (extension_loaded('imagick')) {
try {
$img = new \Imagick($fullPath);
if ($img->getImageColorspace() == \Imagick::COLORSPACE_CMYK) {
$img->transformImageColorspace(\Imagick::COLORSPACE_SRGB);
$img->setImageFormat('jpeg');
$img->writeImage($fullPath);
$img->clear();
$img->destroy();
return true;
}
$img->clear();
$img->destroy();
} catch (\Exception $e) {}
}
$info = @getimagesize($fullPath);
if (isset($info['channels']) && $info['channels'] == 4) {
$img = @imagecreatefromjpeg($fullPath);
if ($img) {
$width = imagesx($img);
$height = imagesy($img);
$rgbCanvas = imagecreatetruecolor($width, $height);
for ($x = 0; $x < $width; $x++) {
for ($y = 0; $y < $height; $y++) {
$pixel = imagecolorat($img, $x, $y);
$c = ($pixel >> 16) & 0xFF;
$m = ($pixel >> 8) & 0xFF;
$y_val = $pixel & 0xFF;
$r = (int)(($c * $y_val) / 255);
$g = (int)(($m * $y_val) / 255);
$b = (int)($y_val);
$color = imagecolorallocate($rgbCanvas, min(255, max(0, $r)), min(255, max(0, $g)), min(255, max(0, $b)));
imagesetpixel($rgbCanvas, $x, $y, $color);
}
}
@imagejpeg($rgbCanvas, $fullPath, $quality);
imagedestroy($img);
imagedestroy($rgbCanvas);
return true;
}
}
return false;
}
public function sanitizeSvgFile($filePath) {
if (!is_file($filePath)) {
return false;
}
$content = @file_get_contents($filePath);
if ($content === false) {
return false;
}
$clean = $this->sanitizeSvgContent($content);
return (@file_put_contents($filePath, $clean) !== false);
}
public function sanitizeSvgContent($content) {
if (empty($content)) {
return $content;
}
$content = preg_replace('/<!DOCTYPE[^>]*\[.*?\]>/si', '', $content);
$content = preg_replace('/<!ENTITY[^>]*>/i', '', $content);
$previous_libxml_state = false;
if (PHP_VERSION_ID < 80000 && function_exists('libxml_disable_entity_loader')) {
$previous_libxml_state = @libxml_disable_entity_loader(true);
}
$dom = new \DOMDocument();
$dom->formatOutput = true;
$libxml_flags = LIBXML_NONET | LIBXML_NOWARNING | LIBXML_NOERROR;
if (defined('LIBXML_NOENT')) {
$libxml_flags |= LIBXML_NOENT;
}
$loaded = @$dom->loadXML($content, $libxml_flags);
if ($previous_libxml_state !== false && PHP_VERSION_ID < 80000 && function_exists('libxml_disable_entity_loader')) {
@libxml_disable_entity_loader($previous_libxml_state);
}
if ($loaded) {
$disallowed_tags = array('script', 'foreignobject', 'object', 'embed', 'iframe', 'applet', 'meta', 'link');
foreach ($disallowed_tags as $tag) {
$nodes = $dom->getElementsByTagName($tag);
while ($nodes->length > 0) {
$item = $nodes->item(0);
if ($item && $item->parentNode) {
$item->parentNode->removeChild($item);
}
}
}
$xpath = new \DOMXPath($dom);
$nodes = $xpath->query('//@*[starts-with(name(), "on") or starts-with(name(), "ON")]');
if ($nodes) {
$attrs_to_remove = array();
foreach ($nodes as $attr) {
$attrs_to_remove[] = $attr;
}
foreach ($attrs_to_remove as $attr) {
if ($attr->ownerElement) {
$attr->ownerElement->removeAttribute($attr->name);
}
}
}
$nodes = $xpath->query('//@*[name()="href" or name()="xlink:href" or name()="src" or name()="action" or name()="formaction" or name()="data" or name()="values"]');
if ($nodes) {
foreach ($nodes as $attr) {
$val = strtolower(trim($attr->value));
if (strpos($val, 'javascript:') !== false || strpos($val, 'data:text/html') !== false || strpos($val, 'data:application/javascript') !== false) {
if ($attr->ownerElement) {
$attr->ownerElement->removeAttribute($attr->name);
}
}
}
}
$styles = $dom->getElementsByTagName('style');
foreach ($styles as $styleNode) {
$styleText = $styleNode->nodeValue;
if ($styleText) {
$styleText = preg_replace('/expression\s*\(/i', 'blocked(', $styleText);
$styleText = preg_replace('/url\s*\(\s*["\']?\s*javascript:[^"\']*\s*["\']?\s*\)/i', 'none', $styleText);
$styleText = preg_replace('/url\s*\(\s*["\']?\s*data:text\/html[^"\']*\s*["\']?\s*\)/i', 'none', $styleText);
$styleNode->nodeValue = $styleText;
}
}
$clean_content = $dom->saveXML();
if ($clean_content !== false) {
return $clean_content;
}
}
$content = preg_replace('/<script\b[^>]*>(.*?)<\/script>/is', '', $content);
$content = preg_replace('/\bon\w+\s*=\s*(?:"[^"]*"|\'[^\']*\')/i', '', $content);
$content = preg_replace('/(?:href|xlink:href|src)\s*=\s*["\']\s*javascript:[^"\']*["\']/i', '', $content);
$content = preg_replace('/<\s*(?:foreignObject|object|embed|iframe|applet)\b[^>]*>.*?<\/\s*\1\s*>/is', '', $content);
$content = preg_replace('/<\s*(?:foreignObject|object|embed|iframe|applet)\b[^>]*\/?\s*>/i', '', $content);
$content = preg_replace('/expression\s*\(/i', 'blocked(', $content);
return $content;
}
public function detectSvgThreats($content) {
$threats = array();
if (empty($content)) return $threats;
if (preg_match('/<!DOCTYPE[^>]*\[.*?\]>/si', $content) || preg_match('/<!ENTITY/i', $content)) {
$threats[] = 'xxe_entity';
}
if (preg_match('/<script\b[^>]*>/i', $content)) {
$threats[] = 'script_tag';
}
if (preg_match('/\bon[a-z]+\s*=/i', $content)) {
$threats[] = 'event_handler';
}
if (preg_match('/(?:href|xlink:href|src|action|data)\s*=\s*["\']\s*javascript:/i', $content)) {
$threats[] = 'javascript_uri';
}
if (preg_match('/<\s*foreignObject\b/i', $content)) {
$threats[] = 'foreign_object';
}
if (preg_match('/<\s*(?:object|embed|iframe|applet)\b/i', $content)) {
$threats[] = 'embed_object';
}
if (preg_match('/expression\s*\(|url\s*\(\s*["\']?\s*javascript:/i', $content)) {
$threats[] = 'css_injection';
}
return $threats;
}
public function scan_svg_security($data = array()) {
$this->loadLanguageSafe();
$rows = array();
$dir = realpath(DIR_IMAGE . 'catalog');
$realImageDir = realpath(DIR_IMAGE);
if (!$dir || strpos(str_replace('\\', '/', $dir), str_replace('\\', '/', $realImageDir)) !== 0 || !is_dir($dir)) {
return array('error' => $this->language->get('text_err_dir'));
}
$limit = isset($data['module_img_opti_svg_security_limit']) ? (int)$data['module_img_opti_svg_security_limit'] : 100;
if ($limit <= 0) $limit = 100;
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS));
foreach ($iterator as $file) {
if ($file->isFile()) {
$realPath = realpath($file->getPathname());
if ($realPath && strpos(str_replace('\\', '/', $realPath), str_replace('\\', '/', $dir)) === 0) {
$ext = strtolower(pathinfo($realPath, PATHINFO_EXTENSION));
if ($ext === 'svg') {
$content = @file_get_contents($realPath);
if ($content !== false) {
$threats = $this->detectSvgThreats($content);
if (!empty($threats)) {
$relPath = $this->getRelativeImagePath($realPath);
$filename = $this->mb_basename($relPath);
$threat_badges = array();
foreach ($threats as $t) {
$lang_key = 'text_svg_threat_' . $t;
$label = $this->language->get($lang_key);
if ($label === $lang_key) $label = $t;
$threat_badges[] = '<span class="label label-danger" style="margin-right:2px; display:inline-block; margin-bottom:2px;">' . htmlspecialchars($label, ENT_QUOTES, 'UTF-8') . '</span>';
}
$preview_url = '../image/' . str_replace('\\', '/', $relPath);
$entities = $this->getEntitiesByImagePath($relPath);
$entities_html = !empty($entities) ? implode('<br>', $entities) : '<i>' . $this->language->get('text_no_relations') . '</i>';
$rows[] = array(
'id' => 'svg_security-' . md5($relPath),
'preview' => '<img src="' . $preview_url . '" class="img-thumbnail" style="max-width:100px; max-height:100px; object-fit:contain;">',
'name' => '<b>' . htmlspecialchars($relPath, ENT_QUOTES, 'UTF-8') . '</b><br>' . $entities_html,
'before' => '<span class="text-danger"><b>' . sprintf($this->language->get('text_svg_threats_found'), count($threats)) . '</b></span><br>' . implode(' ', $threat_badges),
'after' => '<span class="label label-success"><i class="fa fa-shield"></i> ' . $this->language->get('text_svg_clean_success') . '</span>',
'actions' => array(
'sanitize' => $this->language->get('text_action_apply_changes'),
'ignore' => $this->language->get('text_action_ignore')
),
'selected_action' => 'sanitize',
'meta' => array(
'path' => $relPath
)
);
if (count($rows) >= $limit) {
break;
}
}
}
}
}
}
}
file_put_contents(DIR_CACHE . 'img_opti_svg_security_grid.json', json_encode($rows));
return array(
'success' => true,
'headers' => array(
$this->language->get('text_th_preview'),
$this->language->get('text_th_entity'),
$this->language->get('text_th_before'),
$this->language->get('text_th_after'),
$this->language->get('text_th_action')
),
'rows' => $rows,
'backup_available' => true,
'quarantine_available' => true,
'description' => $this->language->get('text_description_svg_security')
);
}
public function fix_svg_security($data) {
$this->backupCatalogTables('svg_security');
$selected = isset($data['selected']) ? (array)$data['selected'] : array();
$fix_all = isset($data['fix_all']) ? (int)$data['fix_all'] : 0;
$cache_file = DIR_CACHE . 'img_opti_svg_security_grid.json';
if (!is_file($cache_file)) {
return $this->formatFixResult(0, 0);
}
$grid = json_decode(file_get_contents($cache_file), true);
if (!is_array($grid)) {
return $this->formatFixResult(0, 0);
}
$rows_map = array();
foreach ($grid as $row) {
$rows_map[$row['id']] = $row;
if ($fix_all === 1 || empty($selected)) {
$selected[$row['id']] = $row['selected_action'];
}
}
$realImageDir = realpath(DIR_IMAGE);
$processed = 0;
foreach ($selected as $row_id => $act) {
if ($act !== 'sanitize' || !isset($rows_map[$row_id])) continue;
$row = $rows_map[$row_id];
$relPath = (string)$row['meta']['path'];
$cleanBadFile = str_replace(array('../', '..\\', "\0"), '', $relPath);
$fullPath = DIR_IMAGE . $cleanBadFile;
if (!is_file($fullPath)) continue;
$realFullPath = realpath($fullPath);
if (!$realFullPath || strpos(str_replace('\\', '/', $realFullPath), str_replace('\\', '/', $realImageDir)) !== 0) continue;
$svgContent = @file_get_contents($fullPath);
if ($svgContent === false) continue;
$this->quarantineFile($relPath, 'svg_security');
$cleanSvg = $this->sanitizeSvgContent($svgContent);
if (@file_put_contents($fullPath, $cleanSvg) !== false) {
$processed++;
}
}
return $this->formatFixResult($processed, count($selected));
}
public function replaceBlackBackground($img, $bgColorHex = '#FFFFFF', $darkness = 30) {
return $this->replaceBlackBackgroundAdaptive($img, $bgColorHex, $darkness, 'edges_only');
}
public function replaceBlackBackgroundAdaptive($img, $bgColorHex = '#FFFFFF', $darkness = 30, $mode = 'edges_only') {
if (!$img) return $img;
$width = imagesx($img);
$height = imagesy($img);
if ($width <= 0 || $height <= 0) return $img;
$r_target = 255; $g_target = 255; $b_target = 255;
if (preg_match('/^#?([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})$/i', $bgColorHex, $m)) {
$r_target = hexdec($m[1]);
$g_target = hexdec($m[2]);
$b_target = hexdec($m[3]);
}
$sample_points = array();
for ($i = 0; $i < 20; $i++) {
$sample_points[] = array((int)($i * ($width - 1) / 19), 0);
$sample_points[] = array((int)($i * ($width - 1) / 19), $height - 1);
$sample_points[] = array(0, (int)($i * ($height - 1) / 19));
$sample_points[] = array($width - 1, (int)($i * ($height - 1) / 19));
}
$sum_r = 0; $sum_g = 0; $sum_b = 0; $bg_samples = 0;
foreach ($sample_points as $p) {
$rgb = imagecolorat($img, $p[0], $p[1]);
$alpha = ($rgb & 0x7F000000) >> 24;
if ($alpha <= 10) {
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
if ($r <= $darkness + 35 && $g <= $darkness + 35 && $b <= $darkness + 35) {
$sum_r += $r;
$sum_g += $g;
$sum_b += $b;
$bg_samples++;
}
}
}
$base_r = ($bg_samples > 0) ? (int)($sum_r / $bg_samples) : 0;
$base_g = ($bg_samples > 0) ? (int)($sum_g / $bg_samples) : 0;
$base_b = ($bg_samples > 0) ? (int)($sum_b / $bg_samples) : 0;
$tolerance_inner = ($mode === 'all_smooth') ? max(45.0, (float)$darkness * 1.3) : max(30.0, (float)$darkness * 0.9);
$tolerance_outer = ($mode === 'all_smooth') ? ($tolerance_inner + 45.0) : ($tolerance_inner + 25.0); // Feathering range for anti-aliasing
$fillColor = imagecolorallocate($img, $r_target, $g_target, $b_target);
if ($mode === 'all' || $mode === 'all_smooth') {
for ($y = 0; $y < $height; $y++) {
for ($x = 0; $x < $width; $x++) {
$rgb = imagecolorat($img, $x, $y);
$alpha = ($rgb & 0x7F000000) >> 24;
if ($alpha > 10) continue;
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
$dist = sqrt(pow($r - $base_r, 2) + pow($g - $base_g, 2) + pow($b - $base_b, 2));
if ($dist <= $tolerance_inner) {
imagesetpixel($img, $x, $y, $fillColor);
} elseif ($dist <= $tolerance_outer) {
$blendFactor = ($dist - $tolerance_inner) / ($tolerance_outer - $tolerance_inner);
$r_new = (int)($r * $blendFactor + $r_target * (1.0 - $blendFactor));
$g_new = (int)($g * $blendFactor + $g_target * (1.0 - $blendFactor));
$b_new = (int)($b * $blendFactor + $b_target * (1.0 - $blendFactor));
$blendedColor = imagecolorallocate($img, $r_new, $g_new, $b_new);
imagesetpixel($img, $x, $y, $blendedColor);
}
}
}
} else {
$seeds = array();
for ($x = 0; $x < $width; $x += 10) {
$seeds[] = array($x, 0);
$seeds[] = array($x, $height - 1);
}
for ($y = 0; $y < $height; $y += 10) {
$seeds[] = array(0, $y);
$seeds[] = array($width - 1, $y);
}
$seeds[] = array($width - 1, $height - 1);
$queue = array();
$visited = array_fill(0, $height, array_fill(0, $width, false));
foreach ($seeds as $c) {
$x = $c[0]; $y = $c[1];
$rgb = imagecolorat($img, $x, $y);
$r = ($rgb >> 16) & 0xFF; $g = ($rgb >> 8) & 0xFF; $b = $rgb & 0xFF;
$dist = sqrt(pow($r - $base_r, 2) + pow($g - $base_g, 2) + pow($b - $base_b, 2));
if ($dist <= $tolerance_outer && !$visited[$y][$x]) {
$queue[] = array($x, $y);
$visited[$y][$x] = true;
}
}
while (!empty($queue)) {
$curr = array_pop($queue);
$cx = $curr[0]; $cy = $curr[1];
$rgb = imagecolorat($img, $cx, $cy);
$r = ($rgb >> 16) & 0xFF; $g = ($rgb >> 8) & 0xFF; $b = $rgb & 0xFF;
$dist = sqrt(pow($r - $base_r, 2) + pow($g - $base_g, 2) + pow($b - $base_b, 2));
if ($dist <= $tolerance_inner) {
imagesetpixel($img, $cx, $cy, $fillColor);
} else {
$blendFactor = ($dist - $tolerance_inner) / ($tolerance_outer - $tolerance_inner);
$r_new = (int)($r * $blendFactor + $r_target * (1.0 - $blendFactor));
$g_new = (int)($g * $blendFactor + $g_target * (1.0 - $blendFactor));
$b_new = (int)($b * $blendFactor + $b_target * (1.0 - $blendFactor));
$blendedColor = imagecolorallocate($img, $r_new, $g_new, $b_new);
imagesetpixel($img, $cx, $cy, $blendedColor);
}
$neighbors = array(
array($cx + 1, $cy), array($cx - 1, $cy),
array($cx, $cy + 1), array($cx, $cy - 1)
);
foreach ($neighbors as $n) {
$nx = $n[0]; $ny = $n[1];
if ($nx >= 0 && $nx < $width && $ny >= 0 && $ny < $height && !$visited[$ny][$nx]) {
$visited[$ny][$nx] = true;
$nrgb = imagecolorat($img, $nx, $ny);
$nr = ($nrgb >> 16) & 0xFF; $ng = ($nrgb >> 8) & 0xFF; $nb = $nrgb & 0xFF;
$ndist = sqrt(pow($nr - $base_r, 2) + pow($ng - $base_g, 2) + pow($nb - $base_b, 2));
if ($ndist <= $tolerance_outer) {
$queue[] = array($nx, $ny);
}
}
}
}
}
return $img;
}
public function replaceBlackBackgroundImagick($filePath, $bgColorHex = '#FFFFFF', $darkness = 30, $mode = 'all_smooth') {
if (!extension_loaded('imagick') || !is_file($filePath)) {
return false;
}
try {
$im = new \Imagick($filePath);
$r_target = 255; $g_target = 255; $b_target = 255;
if (preg_match('/^#?([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})$/i', $bgColorHex, $m)) {
$r_target = hexdec($m[1]);
$g_target = hexdec($m[2]);
$b_target = hexdec($m[3]);
}
$fillColor = new \ImagickPixel("rgb($r_target, $g_target, $b_target)");
$fuzz = (\Imagick::getQuantum() * (max(30, $darkness * 1.3) / 255));
if ($mode === 'edges_only') {
$w = $im->getImageWidth();
$h = $im->getImageHeight();
$seeds = array();
for ($x = 0; $x < $w; $x += 15) {
$seeds[] = array($x, 0);
$seeds[] = array($x, $h - 1);
}
for ($y = 0; $y < $h; $y += 15) {
$seeds[] = array(0, $y);
$seeds[] = array($w - 1, $y);
}
$seeds[] = array($w - 1, $h - 1);
foreach ($seeds as $c) {
$borderPixel = $im->getImagePixelColor($c[0], $c[1]);
$colorArray = $borderPixel->getColor();
if ($colorArray['r'] <= ($darkness + 40) && $colorArray['g'] <= ($darkness + 40) && $colorArray['b'] <= ($darkness + 40)) {
@$im->floodfillPaintImage($fillColor, $fuzz, $borderPixel, $c[0], $c[1], false);
}
}
} else {
$im->transparentPaintImage(new \ImagickPixel('rgb(0,0,0)'), 0.0, $fuzz, false);
$bg = new \Imagick();
$bg->newImage($im->getImageWidth(), $im->getImageHeight(), $fillColor);
$bg->compositeImage($im, \Imagick::COMPOSITE_OVER, 0, 0);
$im = $bg;
}
$ext = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
if ($ext === 'png') {
$im->setImageFormat('png');
} else {
$im->setImageFormat('jpeg');
$im->setImageCompression(\Imagick::COMPRESSION_JPEG);
$im->setImageCompressionQuality(100);
$im->setOption('jpeg:sampling-factor', '4:4:4');
}
$im->writeImage($filePath);
$im->clear();
$im->destroy();
return true;
} catch (\Exception $e) {
return false;
}
}
public function scan_black_bg($data = array()) {
$this->loadLanguageSafe();
$rows = array();
$dir = realpath(DIR_IMAGE . 'catalog');
$realImageDir = realpath(DIR_IMAGE);
if (!$dir || strpos(str_replace('\\', '/', $dir), str_replace('\\', '/', $realImageDir)) !== 0 || !is_dir($dir)) {
return array('error' => $this->language->get('text_err_dir'));
}
$limit = isset($data['module_img_opti_bb_limit']) ? (int)$data['module_img_opti_bb_limit'] : 100;
if ($limit <= 0) $limit = 100;
$darkness = isset($data['module_img_opti_bb_darkness']) ? (int)$data['module_img_opti_bb_darkness'] : 30;
$threshold = isset($data['module_img_opti_bb_threshold']) ? (int)$data['module_img_opti_bb_threshold'] : 60;
$scan_fake = isset($data['module_img_opti_bb_scan_fake']) ? (int)$data['module_img_opti_bb_scan_fake'] : 1;
$scan_black = isset($data['module_img_opti_bb_scan_black']) ? (int)$data['module_img_opti_bb_scan_black'] : 1;
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS));
$this->load->model('tool/image');
foreach ($iterator as $file) {
if ($file->isFile()) {
$realPath = realpath($file->getPathname());
if ($realPath && strpos(str_replace('\\', '/', $realPath), str_replace('\\', '/', $dir)) === 0) {
$ext = strtolower(pathinfo($realPath, PATHINFO_EXTENSION));
if (!in_array($ext, array('jpg', 'jpeg'))) continue;
$relPath = $this->getRelativeImagePath($realPath);
if (preg_match('/(wp-content|elementor|thumbs|templates?)/i', $relPath)) continue;
$is_fake = false;
$is_black = false;
if ($scan_fake && in_array($ext, array('jpg', 'jpeg'))) {
$header = @file_get_contents($realPath, false, null, 0, 8);
if ($header === "\x89PNG\r\n\x1a\n") {
$is_fake = true;
}
}
if ($scan_black && !$is_fake) {
$img = @imagecreatefromstring(@file_get_contents($realPath));
if ($img) {
$w = imagesx($img);
$h = imagesy($img);
if ($w >= 20 && $h >= 20) {
$corners = array(
array(0, 0), array($w - 1, 0),
array(0, $h - 1), array($w - 1, $h - 1)
);
$black_corners = 0;
foreach ($corners as $c) {
$rgb = imagecolorat($img, $c[0], $c[1]);
$alpha = ($rgb & 0x7F000000) >> 24;
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
if ($alpha <= 10 && $r <= ($darkness + 15) && $g <= ($darkness + 15) && $b <= ($darkness + 15)) {
$black_corners++;
}
}
if ($black_corners >= 3) {
$sample_points = array();
for ($i = 0; $i < 20; $i++) {
$sample_points[] = array((int)($i * ($w - 1) / 19), 0);
$sample_points[] = array((int)($i * ($w - 1) / 19), $h - 1);
$sample_points[] = array(0, (int)($i * ($h - 1) / 19));
$sample_points[] = array($w - 1, (int)($i * ($h - 1) / 19));
}
$black_count = 0;
foreach ($sample_points as $p) {
$rgb = imagecolorat($img, $p[0], $p[1]);
$alpha = ($rgb & 0x7F000000) >> 24;
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
if ($alpha <= 10 && $r <= $darkness && $g <= $darkness && $b <= $darkness) {
$black_count++;
}
}
$pct = ($black_count / count($sample_points)) * 100;
if ($pct >= $threshold) {
$is_black = true;
}
}
}
imagedestroy($img);
}
}
if ($is_fake || $is_black) {
$thumb = $this->model_tool_image->resize($relPath, 100, 100);
$entities = $this->getEntitiesByImagePath($relPath);
$entities_html = !empty($entities) ? implode('<br>', $entities) : '<i>' . $this->language->get('text_no_relations') . '</i>';
$issue_type = $is_fake ? 'fake_jpg' : 'black_bg';
$issue_title = $is_fake ? $this->language->get('text_bb_fake_jpg') : $this->language->get('text_bb_black_bg');
$before_desc = $is_fake ? '<span class="label label-danger">' . $this->language->get('text_bb_type_fake') . '</span>' : '<span class="label label-warning">' . $this->language->get('text_bb_type_black') . '</span>';
if ($is_fake) {
$after_desc = '<span class="label label-success"><i class="fa fa-check"></i> ' . $this->language->get('text_bb_fix_flatten_white') . '</span>';
$actions = array(
'fix_flatten_white' => $this->language->get('text_bb_fix_flatten_white'),
'fix_convert_png' => $this->language->get('text_bb_fix_convert_png'),
'ignore' => $this->language->get('text_action_ignore')
);
$default_action = 'fix_flatten_white';
} else {
$after_desc = '<span class="label label-success"><i class="fa fa-check"></i> ' . $this->language->get('text_bb_fix_white') . '</span>';
$actions = array(
'fix_white' => $this->language->get('text_bb_fix_white'),
'fix_white_all' => $this->language->get('text_bb_fix_white_all'),
'fix_white_smooth' => $this->language->get('text_bb_fix_white_smooth')
);
if (extension_loaded('imagick')) {
$actions['fix_white_imagick'] = $this->language->get('text_bb_fix_white_imagick');
$actions['fix_white_all_imagick'] = $this->language->get('text_bb_fix_white_all_imagick');
$actions['fix_white_smooth_imagick'] = $this->language->get('text_bb_fix_white_smooth_imagick');
}
$actions['ignore'] = $this->language->get('text_action_ignore');
$default_action = extension_loaded('imagick') ? 'fix_white_smooth_imagick' : 'fix_white_smooth';
}
$rows[] = array(
'id' => 'black_bg-' . md5($relPath),
'preview' => '<img src="' . $thumb . '" class="img-thumbnail" style="max-width:100px; max-height:100px;">',
'name' => '<b>' . htmlspecialchars($relPath, ENT_QUOTES, 'UTF-8') . '</b><br>' . $entities_html,
'before' => $issue_title . '<br>' . $before_desc,
'after' => $after_desc,
'actions' => $actions,
'selected_action' => $default_action,
'meta' => array(
'path' => $relPath,
'type' => $issue_type
)
);
if (count($rows) >= $limit) {
break;
}
}
}
}
}
file_put_contents(DIR_CACHE . 'img_opti_black_bg_grid.json', json_encode($rows));
return array(
'success' => true,
'headers' => array(
$this->language->get('text_th_preview'),
$this->language->get('text_th_entity'),
$this->language->get('text_th_before'),
$this->language->get('text_th_after'),
$this->language->get('text_th_action')
),
'rows' => $rows,
'backup_available' => true,
'quarantine_available' => true,
'description' => $this->language->get('text_description_black_bg')
);
}
public function fix_black_bg($data) {
$this->backupCatalogTables('black_bg');
$selected = isset($data['selected']) ? (array)$data['selected'] : array();
$fix_all = isset($data['fix_all']) ? (int)$data['fix_all'] : 0;
$darkness = isset($data['module_img_opti_bb_darkness']) ? (int)$data['module_img_opti_bb_darkness'] : 30;
$cache_file = DIR_CACHE . 'img_opti_black_bg_grid.json';
if (!is_file($cache_file)) {
return $this->formatFixResult(0, 0);
}
$grid = json_decode(file_get_contents($cache_file), true);
if (!is_array($grid)) {
return $this->formatFixResult(0, 0);
}
$rows_map = array();
foreach ($grid as $row) {
$rows_map[$row['id']] = $row;
if ($fix_all === 1 || empty($selected)) {
$selected[$row['id']] = $row['selected_action'];
}
}
$realImageDir = realpath(DIR_IMAGE);
$processed = 0;
$allowed_actions = array('fix_flatten_white', 'fix_convert_png', 'fix_white', 'fix_white_all', 'fix_white_smooth', 'fix_white_imagick', 'fix_white_all_imagick', 'fix_white_smooth_imagick');
foreach ($selected as $row_id => $act) {
if (!in_array($act, $allowed_actions) || !isset($rows_map[$row_id])) continue;
$row = $rows_map[$row_id];
$relPath = (string)$row['meta']['path'];
$issueType = (string)$row['meta']['type'];
$cleanBadFile = str_replace(array('../', '..\\', "\0"), '', $relPath);
$fullPath = DIR_IMAGE . $cleanBadFile;
if (!is_file($fullPath)) continue;
$realFullPath = realpath($fullPath);
if (!$realFullPath || strpos(str_replace('\\', '/', $realFullPath), str_replace('\\', '/', $realImageDir)) !== 0) continue;
$imgData = @file_get_contents($fullPath);
if ($imgData === false) continue;
$this->quarantineFile($relPath, 'black_bg');
if ($act === 'fix_flatten_white') {
$pngImg = @imagecreatefromstring($imgData);
if ($pngImg) {
$w = imagesx($pngImg);
$h = imagesy($pngImg);
$jpgCanvas = imagecreatetruecolor($w, $h);
imagealphablending($jpgCanvas, true);
$white = imagecolorallocate($jpgCanvas, 255, 255, 255);
imagefilledrectangle($jpgCanvas, 0, 0, $w - 1, $h - 1, $white);
imagealphablending($pngImg, true);
imagesavealpha($pngImg, true);
imagecopy($jpgCanvas, $pngImg, 0, 0, 0, 0, $w, $h);
$jpgCanvas = $this->replaceBlackBackgroundAdaptive($jpgCanvas, '#FFFFFF', $darkness, 'edges_only');
@imagejpeg($jpgCanvas, $fullPath, 100);
imagedestroy($pngImg);
imagedestroy($jpgCanvas);
$this->deleteImageCache($relPath);
$this->load->model('tool/image');
@$this->model_tool_image->resize($cleanBadFile, 100, 100);
$processed++;
}
} elseif ($act === 'fix_convert_png') {
$pngImg = @imagecreatefromstring($imgData);
if ($pngImg) {
imagesavealpha($pngImg, true);
imagealphablending($pngImg, false);
$newRelPath = preg_replace('/\.(jpe?g)$/i', '.png', $relPath);
$newFullPath = DIR_IMAGE . str_replace(array('../', '..\\', "\0"), '', $newRelPath);
$newDir = dirname($newFullPath);
if (!is_dir($newDir)) {
@mkdir($newDir, 0755, true);
}
@imagepng($pngImg, $newFullPath, 9);
imagedestroy($pngImg);
if (is_file($fullPath)) {
@unlink($fullPath);
}
$this->updateImagePathInDb($relPath, $newRelPath);
$this->deleteImageCache($relPath);
$this->load->model('tool/image');
@$this->model_tool_image->resize($newRelPath, 100, 100);
$processed++;
}
} elseif ($act === 'fix_white') {
$img = @imagecreatefromstring($imgData);
if ($img) {
$img = $this->replaceBlackBackgroundAdaptive($img, '#FFFFFF', $darkness, 'edges_only');
$ext = strtolower(pathinfo($fullPath, PATHINFO_EXTENSION));
if ($ext === 'png') {
@imagepng($img, $fullPath, 9);
} else {
@imagejpeg($img, $fullPath, 100);
}
imagedestroy($img);
$this->deleteImageCache($relPath);
$this->load->model('tool/image');
@$this->model_tool_image->resize($cleanBadFile, 100, 100);
$processed++;
}
} elseif ($act === 'fix_white_all') {
$img = @imagecreatefromstring($imgData);
if ($img) {
$img = $this->replaceBlackBackgroundAdaptive($img, '#FFFFFF', $darkness, 'all');
$ext = strtolower(pathinfo($fullPath, PATHINFO_EXTENSION));
if ($ext === 'png') {
@imagepng($img, $fullPath, 9);
} else {
@imagejpeg($img, $fullPath, 100);
}
imagedestroy($img);
$this->deleteImageCache($relPath);
$this->load->model('tool/image');
@$this->model_tool_image->resize($cleanBadFile, 100, 100);
$processed++;
}
} elseif ($act === 'fix_white_smooth') {
$img = @imagecreatefromstring($imgData);
if ($img) {
$img = $this->replaceBlackBackgroundAdaptive($img, '#FFFFFF', $darkness, 'all_smooth');
$ext = strtolower(pathinfo($fullPath, PATHINFO_EXTENSION));
if ($ext === 'png') {
@imagepng($img, $fullPath, 9);
} else {
@imagejpeg($img, $fullPath, 100);
}
imagedestroy($img);
$this->deleteImageCache($relPath);
$this->load->model('tool/image');
@$this->model_tool_image->resize($cleanBadFile, 100, 100);
$processed++;
}
} elseif (in_array($act, array('fix_white_imagick', 'fix_white_all_imagick', 'fix_white_smooth_imagick'))) {
$imagick_mode = 'all_smooth';
if ($act === 'fix_white_imagick') {
$imagick_mode = 'edges_only';
} elseif ($act === 'fix_white_all_imagick') {
$imagick_mode = 'all';
}
if ($this->replaceBlackBackgroundImagick($fullPath, '#FFFFFF', $darkness, $imagick_mode)) {
$this->deleteImageCache($relPath);
$this->load->model('tool/image');
@$this->model_tool_image->resize($cleanBadFile, 100, 100);
$processed++;
} else {
$img = @imagecreatefromstring($imgData);
if ($img) {
$img = $this->replaceBlackBackgroundAdaptive($img, '#FFFFFF', $darkness, $imagick_mode);
@imagejpeg($img, $fullPath, 100);
imagedestroy($img);
$this->deleteImageCache($relPath);
$this->load->model('tool/image');
@$this->model_tool_image->resize($cleanBadFile, 100, 100);
$processed++;
}
}
}
}
return $this->formatFixResult($processed, count($selected));
}
public function preview_black_bg_row($data) {
$row_id = isset($data['row_id']) ? (string)$data['row_id'] : '';
$act = isset($data['action_type']) ? (string)$data['action_type'] : 'fix_white';
$darkness = isset($data['module_img_opti_bb_darkness']) ? (int)$data['module_img_opti_bb_darkness'] : 30;
$cache_file = DIR_CACHE . 'img_opti_black_bg_grid.json';
if (!is_file($cache_file)) {
return array('error' => 'Cache file not found');
}
$grid = json_decode(file_get_contents($cache_file), true);
if (!is_array($grid)) {
return array('error' => 'Invalid grid cache');
}
$targetRow = null;
foreach ($grid as $row) {
if ($row['id'] === $row_id) {
$targetRow = $row;
break;
}
}
if (!$targetRow) {
return array('error' => 'Row not found');
}
$relPath = (string)$targetRow['meta']['path'];
$cleanBadFile = str_replace(array('../', '..\\', "\0"), '', $relPath);
$fullPath = DIR_IMAGE . $cleanBadFile;
if (!is_file($fullPath)) {
return array('error' => 'Original image file not found on disk');
}
$imgData = @file_get_contents($fullPath);
if ($imgData === false) {
return array('error' => 'Failed to read image file');
}
$this->load->model('tool/image');
$thumbBefore = $this->model_tool_image->resize($cleanBadFile, 350, 350);
$cache_dir = DIR_IMAGE . 'cache';
if (!is_dir($cache_dir)) {
@mkdir($cache_dir, 0755, true);
}
$old_previews = glob($cache_dir . '/img_opti_preview_*.jpg');
if ($old_previews) {
$now = time();
foreach ($old_previews as $old_file) {
if (($now - filemtime($old_file)) > 300) {
@unlink($old_file);
}
}
}
$tempFilename = 'img_opti_preview_' . md5($relPath . '_' . $act . '_' . microtime(true)) . '.jpg';
$tempRelPath = 'cache/' . $tempFilename;
$tempFullPath = DIR_IMAGE . $tempRelPath;
if ($act === 'ignore') {
$thumbAfter = $thumbBefore;
} elseif (in_array($act, array('fix_white_imagick', 'fix_white_all_imagick', 'fix_white_smooth_imagick'))) {
$imagick_mode = 'all_smooth';
if ($act === 'fix_white_imagick') {
$imagick_mode = 'edges_only';
} elseif ($act === 'fix_white_all_imagick') {
$imagick_mode = 'all';
}
@copy($fullPath, $tempFullPath);
if (!$this->replaceBlackBackgroundImagick($tempFullPath, '#FFFFFF', $darkness, $imagick_mode)) {
$img = @imagecreatefromstring($imgData);
if ($img) {
$img = $this->replaceBlackBackgroundAdaptive($img, '#FFFFFF', $darkness, $imagick_mode);
@imagejpeg($img, $tempFullPath, 100);
@imagedestroy($img);
}
}
$thumbAfter = $this->model_tool_image->resize($tempRelPath, 350, 350);
} else {
$gd_mode = 'edges_only';
if ($act === 'fix_white_all') {
$gd_mode = 'all';
} elseif ($act === 'fix_white_smooth') {
$gd_mode = 'all_smooth';
}
$img = @imagecreatefromstring($imgData);
if ($img) {
$img = $this->replaceBlackBackgroundAdaptive($img, '#FFFFFF', $darkness, $gd_mode);
@imagejpeg($img, $tempFullPath, 100);
@imagedestroy($img);
}
$thumbAfter = $this->model_tool_image->resize($tempRelPath, 350, 350);
}
return array(
'success' => true,
'title' => $relPath,
'thumb_before' => $thumbBefore,
'thumb_after' => $thumbAfter
);
}
}