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 = ' '; $html .= ' '; 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 = ' '; } $search_links = $this->getSearchLinks($row['name'], $row['model']); $entities[] = 'Product ' . htmlspecialchars($row['name'] ? $row['name'] : 'Product ID ' . $row['product_id'], ENT_QUOTES, 'UTF-8') . '' . $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 = ' '; } $search_links = $this->getSearchLinks($row['name'], $row['model']); $entities[] = 'Product (Add.) ' . htmlspecialchars($row['name'] ? $row['name'] : 'Product ID ' . $row['product_id'], ENT_QUOTES, 'UTF-8') . '' . $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 = ' '; } $search_links = $this->getSearchLinks($row['name']); $entities[] = 'Category ' . htmlspecialchars($row['name'] ? $row['name'] : 'Category ID ' . $row['category_id'], ENT_QUOTES, 'UTF-8') . '' . $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 = ' '; } $search_links = $this->getSearchLinks($row['name']); $entities[] = 'Manufacturer ' . htmlspecialchars($row['name'], ENT_QUOTES, 'UTF-8') . '' . $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[] = 'Banner ' . htmlspecialchars($row['name'] ? $row['name'] : 'Banner ID ' . $row['banner_id'], ENT_QUOTES, 'UTF-8') . '' . $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[] = '' . $cfg['type'] . ' ' . htmlspecialchars($row['name'] ? $row['name'] : $cfg['type'] . ' ID ' . $row[$cfg['id']], ENT_QUOTES, 'UTF-8') . '' . $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 = ' '; } $search_links = $this->getSearchLinks($row['name'], $row['model']); $rows[] = array( 'id' => 'product_main-' . (int)$row['id'], 'preview' => '', 'name' => 'Product ' . htmlspecialchars($row['name'] ? $row['name'] : 'Product ID ' . (int)$row['id'], ENT_QUOTES, 'UTF-8') . '' . $catalog_link_html . $search_links, 'before' => '' . $this->language->get('text_grid_missing_file') . ': ' . 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 = ' '; } $search_links = $this->getSearchLinks($row['name'], $row['model']); $rows[] = array( 'id' => 'product_additional-' . (int)$row['id'], 'preview' => '', 'name' => 'Product (Add.) ' . htmlspecialchars($row['name'] ? $row['name'] : 'Product ID ' . (int)$row['product_id'], ENT_QUOTES, 'UTF-8') . '' . $catalog_link_html . $search_links, 'before' => '' . $this->language->get('text_grid_missing_file') . ': ' . 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 = ' '; } $search_links = $this->getSearchLinks($row['name']); $rows[] = array( 'id' => 'category-' . (int)$row['id'], 'preview' => '', 'name' => 'Category ' . htmlspecialchars($row['name'] ? $row['name'] : 'Category ID ' . (int)$row['id'], ENT_QUOTES, 'UTF-8') . '' . $catalog_link_html . $search_links, 'before' => '' . $this->language->get('text_grid_missing_file') . ': ' . 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 = ' '; } $search_links = $this->getSearchLinks($row['name']); $rows[] = array( 'id' => 'manufacturer-' . (int)$row['id'], 'preview' => '', 'name' => 'Manufacturer ' . htmlspecialchars($row['name'], ENT_QUOTES, 'UTF-8') . '' . $catalog_link_html . $search_links, 'before' => '' . $this->language->get('text_grid_missing_file') . ': ' . 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) { // Файл > 50MB — пропускаем GD-проверку во избежание OOM $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('
', $entities) : '' . $this->language->get('text_no_relations') . ''; $rows[] = array( 'id' => 'broken_file-' . md5($relPath), 'preview' => '', 'name' => '' . htmlspecialchars($relPath, ENT_QUOTES, 'UTF-8') . '
' . $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('
', $entities) : '' . $this->language->get('text_no_relations') . ''; $preview_html = '
'; $preview_html .= '
'; $preview_html .= '
'; $preview_html .= ' ' . $this->language->get('text_dupe_original_label') . ''; $preview_html .= '
'; $preview_html .= '
'; $preview_html .= '
'; $preview_html .= ' ' . $this->language->get('text_dupe_duplicate_label') . ''; $preview_html .= '
'; $preview_html .= '
'; $name_html = 'Duplicate path:
' . htmlspecialchars($duplicate, ENT_QUOTES, 'UTF-8') . '
'; $name_html .= 'Relations:
' . $entities_html; $rows[] = array( 'id' => 'duplicate-' . md5($duplicate), 'preview' => $preview_html, 'name' => $name_html, 'before' => '' . $this->language->get('text_grid_dupe_before') . '
' . htmlspecialchars($duplicate, ENT_QUOTES, 'UTF-8') . '
' . $this->formatBytes($original_size) . '', 'after' => ' ' . $this->language->get('text_grid_dupe_after') . '
' . htmlspecialchars($original, ENT_QUOTES, 'UTF-8') . '
' . $this->formatBytes($original_size) . '', '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('
', $entities) : '' . $this->language->get('text_no_relations') . ''; $rows[] = array( 'id' => 'png_jpg-' . md5($relPath), 'preview' => '', 'name' => '' . htmlspecialchars($relPath, ENT_QUOTES, 'UTF-8') . '
' . $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('
', $entities) : '' . $this->language->get('text_no_relations') . ''; $rows[] = array( 'id' => 'translit-' . md5($relPath), 'preview' => '', 'name' => '' . htmlspecialchars($relPath, ENT_QUOTES, 'UTF-8') . '
' . $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' => '', 'name' => '' . htmlspecialchars($relPath, ENT_QUOTES, 'UTF-8') . '', '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' => '', 'name' => '' . htmlspecialchars($rel_cache, ENT_QUOTES, 'UTF-8') . '', '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('
', $entities) : '' . $this->language->get('text_no_relations') . ''; $rows[] = array( 'id' => 'cache_restore-' . md5($rel_cache), 'preview' => '', 'name' => '' . htmlspecialchars($orig_rel, ENT_QUOTES, 'UTF-8') . '
' . $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('
', $entities) : '' . $this->language->get('text_no_relations') . ''; $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' => '', 'name' => '' . htmlspecialchars($relPath, ENT_QUOTES, 'UTF-8') . '
' . $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 = ' '; } $search_links = $this->getSearchLinks($row['name'], $row['model']); $rows[] = array( 'id' => 'placeholder_product-' . (int)$row['id'], 'preview' => is_file(DIR_IMAGE . $matched_image) ? '' : '', 'name' => 'Product ' . htmlspecialchars($row['name'] ? $row['name'] : 'Product ID ' . (int)$row['id'], ENT_QUOTES, 'UTF-8') . '' . $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 = ' '; } $search_links = $this->getSearchLinks($row['name']); $rows[] = array( 'id' => 'placeholder_category-' . (int)$row['id'], 'preview' => is_file(DIR_IMAGE . $matched_image) ? '' : '', 'name' => 'Category ' . htmlspecialchars($row['name'] ? $row['name'] : 'Category ID ' . (int)$row['id'], ENT_QUOTES, 'UTF-8') . '' . $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 = ' '; } $search_links = $this->getSearchLinks($row['name']); $rows[] = array( 'id' => 'placeholder_manufacturer-' . (int)$row['id'], 'preview' => is_file(DIR_IMAGE . $matched_image) ? '' : '', 'name' => 'Manufacturer ' . htmlspecialchars($row['name'], ENT_QUOTES, 'UTF-8') . '' . $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') . '
' . $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('
', $entities) : '' . $this->language->get('text_no_relations') . ''; $rows[] = array( 'id' => 'small_photo-' . md5($relPath), 'preview' => '', 'name' => '' . htmlspecialchars($relPath, ENT_QUOTES, 'UTF-8') . '
' . $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('/]+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' => '', 'name' => '' . $cfg['type'] . ' ' . htmlspecialchars($row[$cfg['name_field']] ? $row[$cfg['name_field']] : $cfg['type'] . ' ID ' . (int)$row[$cfg['id']], ENT_QUOTES, 'UTF-8') . '' . $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' => '', 'name' => '' . htmlspecialchars($folder, ENT_QUOTES, 'UTF-8') . '', '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('
', $entities) : '' . $this->language->get('text_no_relations') . ''; $rows[] = array( 'id' => 'sanitizer-' . md5($relPath), 'preview' => '', 'name' => '' . htmlspecialchars($relPath, ENT_QUOTES, 'UTF-8') . '
' . $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', DIR_LANGUAGE . 'en-gb/extension/module/img_opti.php', DIR_LANGUAGE . 'english/extension/module/img_opti.php' ); foreach ($en_paths as $path) { if (is_file($path)) { $_ = array(); require($path); $data = array_merge($data, $_); break; } } $lang = $this->config->get('config_admin_language'); if (!$lang) { $lang = $this->config->get('config_language'); } if (isset($this->session->data['language'])) { $lang = $this->session->data['language']; } if (!$lang) { $lang = 'en-gb'; } $lang_lower = strtolower((string)$lang); if ($lang_lower && $lang_lower !== 'en-gb' && $lang_lower !== 'english') { $paths = array( DIR_LANGUAGE . $lang . '/module/img_opti.php', DIR_LANGUAGE . $lang_lower . '/module/img_opti.php', DIR_LANGUAGE . $lang . '/extension/module/img_opti.php', DIR_LANGUAGE . $lang_lower . '/extension/module/img_opti.php' ); if (isset($this->db)) { try { $escaped_lang = $this->db->escape($lang); $query_lang = $this->db->query("SELECT directory FROM `" . DB_PREFIX . "language` WHERE code = '" . $escaped_lang . "' OR directory = '" . $escaped_lang . "' LIMIT 1"); if ($query_lang && $query_lang->num_rows && !empty($query_lang->row['directory'])) { $db_dir = $query_lang->row['directory']; $paths[] = DIR_LANGUAGE . $db_dir . '/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('
', $entities) : '' . $this->language->get('text_no_relations') . ''; $size = filesize($realPath); $rows[] = array( 'id' => 'cmyk-' . md5($relPath), 'preview' => '', 'name' => '' . htmlspecialchars($relPath, ENT_QUOTES, 'UTF-8') . '
' . $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('
', $entities) : '' . $this->language->get('text_no_relations') . ''; $w = 0; $h = 0; $imgSize = @getimagesize($realPath); if ($imgSize) { $w = $imgSize[0]; $h = $imgSize[1]; } $rows[] = array( 'id' => 'heavy-' . md5($relPath), 'preview' => '', 'name' => '' . htmlspecialchars($relPath, ENT_QUOTES, 'UTF-8') . '
' . $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); } }