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

3666 lines
199 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();
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 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 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_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');
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 . "'");
}
}
}
@unlink(DIR_CACHE . 'img_opti_broken_db_grid.json');
return array('success' => true);
}
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 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_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)), '/');
$img = @imagecreatefrompng($fullPath);
if ($img) {
if (function_exists('imagepalettetotruecolor') && !imageistruecolor($img)) {
imagepalettetotruecolor($img);
}
$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));
if (imagejpeg($bg, $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');
}
imagedestroy($img);
imagedestroy($bg);
}
}
@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);
if (!$realCachedFile || strpos(str_replace('\\', '/', $realCachedFile), str_replace('\\', '/', $realImageDir)) !== 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'),
'text_color' => $this->config->get('module_img_opti_wm_text_color'),
'text_font' => $this->config->get('module_img_opti_wm_text_font')
);
} 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);
if ($imagePath && strpos(str_replace('\\', '/', $imagePath), str_replace('\\', '/', $realImageDir)) !== 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;
$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 === '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 = max(12, $imgW * 0.04);
$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);
if ($imagePath && strpos(str_replace('\\', '/', $imagePath), str_replace('\\', '/', $realImageDir)) !== 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;
$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) {
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 === '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;
}
}
}
}
if (!is_file($fontFile)) {
$fontSize = max(3, intval($imgW / 50));
$fontWidth = imagefontwidth($fontSize) * strlen($textVal);
$fontHeight = imagefontheight($fontSize);
$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, $fontSize, $posX, $posY, $textVal, $color);
} else {
$fontSize = max(10, intval($imgW / 25));
$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(
'ignore' => $this->language->get('text_action_ignore')
),
'selected_action' => 'ignore'
);
}
}
}
}
}
}
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 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/module/img_opti.php',
DIR_LANGUAGE . 'english/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 . '/module/img_opti.php',
DIR_LANGUAGE . $lang_lower . '/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 . '/module/img_opti.php';
$paths[] = DIR_LANGUAGE . strtolower($db_dir) . '/module/img_opti.php';
}
} catch (Exception $e) {}
}
if (strpos($lang_lower, 'ru') !== false || strpos($lang_lower, 'rus') !== false) {
$paths[] = DIR_LANGUAGE . 'ru-ru/module/img_opti.php';
$paths[] = DIR_LANGUAGE . 'russian/module/img_opti.php';
$paths[] = DIR_LANGUAGE . 'ru-RU/module/img_opti.php';
$paths[] = DIR_LANGUAGE . 'ru/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/module/img_opti.php';
$paths[] = DIR_LANGUAGE . 'ukrainian/module/img_opti.php';
$paths[] = DIR_LANGUAGE . 'uk-UA/module/img_opti.php';
$paths[] = DIR_LANGUAGE . 'uk/module/img_opti.php';
$paths[] = DIR_LANGUAGE . 'ua/module/img_opti.php';
$paths[] = DIR_LANGUAGE . 'uk_ua/module/img_opti.php';
$paths[] = DIR_LANGUAGE . 'uk_UA/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 (extension_loaded('imagick')) {
try {
$img = new Imagick($fullPath);
if ($img->getImageColorspace() == Imagick::COLORSPACE_CMYK) {
$img->transformImageColorspace(Imagick::COLORSPACE_SRGB);
$img->writeImage($fullPath);
$this->deleteImageCache($relPath);
}
$img->clear(); $img->destroy();
} catch (Exception $e) {}
}
}
@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['extension']) || !isset($info['dirname'])) return;
$dir = DIR_IMAGE . 'cache/' . $info['dirname'];
if (!is_dir($dir)) return;
$filename = $info['filename'];
$ext = $info['extension'];
$files = glob($dir . '/' . $filename . '-*x*.' . $ext);
if ($files) {
foreach ($files as $file) {
@unlink($file);
}
}
$files_webp = glob($dir . '/' . $filename . '-*x*.webp');
if ($files_webp) {
foreach ($files_webp as $file) {
@unlink($file);
}
}
}
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);
}
}