load->language('module/img_opti');
$this->document->setTitle($this->language->get('heading_title_raw'));
$this->load->model('setting/setting');
if (($this->request->server['REQUEST_METHOD'] == 'POST') && $this->validate()) {
if (!isset($this->request->post['module_img_opti_cron_folders'])) {
$this->request->post['module_img_opti_cron_folders'] = array();
}
if (!isset($this->request->post['module_img_opti_cron_exclude_folders'])) {
$this->request->post['module_img_opti_cron_exclude_folders'] = array();
}
if (!isset($this->request->post['module_img_opti_cron_entities'])) {
$this->request->post['module_img_opti_cron_entities'] = array();
}
if (!isset($this->request->post['module_img_opti_wm_categories'])) {
$this->request->post['module_img_opti_wm_categories'] = array();
}
if (!isset($this->request->post['module_img_opti_wm_brands'])) {
$this->request->post['module_img_opti_wm_brands'] = array();
}
$quality_keys = array('module_img_opti_jpg_quality', 'module_img_opti_png_quality', 'module_img_opti_webp_quality', 'module_img_opti_max_width', 'module_img_opti_max_height', 'module_img_opti_engine');
$quality_changed = false;
foreach ($quality_keys as $qk) {
$old_val = $this->config->get($qk);
$new_val = isset($this->request->post[$qk]) ? $this->request->post[$qk] : null;
if ((string)$old_val !== (string)$new_val) {
$quality_changed = true;
break;
}
}
$this->model_setting_setting->editSetting('module_img_opti', $this->request->post);
$this->cache->delete('img_opti_disk_stats');
if ($quality_changed) {
$cache_file = DIR_CACHE . 'img_opti_optimized.json';
if (file_exists($cache_file)) {
@unlink($cache_file);
}
}
$this->session->data['success'] = $this->language->get('text_success');
$this->response->redirect($this->url->link('module/img_opti', 'user_token=' . $this->session->data['user_token'], true));
}
if (isset($this->session->data['success'])) {
$data['success'] = $this->session->data['success'];
unset($this->session->data['success']);
} else {
$data['success'] = '';
}
if (isset($this->error['warning'])) {
$data['error_warning'] = $this->error['warning'];
} else {
$data['error_warning'] = '';
}
$data['gd_enabled'] = extension_loaded('gd');
$data['webp_enabled'] = function_exists('imagewebp');
$data['imagick_enabled'] = extension_loaded('imagick');
$data['breadcrumbs'] = array();
$data['breadcrumbs'][] = array('text' => $this->language->get('text_home'), 'href' => $this->url->link('common/dashboard', 'user_token=' . $this->session->data['user_token'], true));
$data['breadcrumbs'][] = array('text' => $this->language->get('text_extension'), 'href' => $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=module', true));
$data['breadcrumbs'][] = array('text' => $this->language->get('heading_title_raw'), 'href' => $this->url->link('module/img_opti', 'user_token=' . $this->session->data['user_token'], true));
$data['action'] = $this->url->link('module/img_opti', 'user_token=' . $this->session->data['user_token'], true);
$data['cancel'] = $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=module', true);
$fields = array(
'module_img_opti_status' => 1,
'module_img_opti_wm_status' => 0,
'module_img_opti_ph_status' => 0,
'module_img_opti_wm_rules' => '',
'module_img_opti_ph_rules' => '',
'module_img_opti_menu_position' => 'both',
'module_img_opti_token' => '',
'module_img_opti_engine' => 'gd',
'module_img_opti_support_svg' => 0,
'module_img_opti_support_webp' => 0,
'module_img_opti_cron_new_only' => 1,
'module_img_opti_cron_folder_mode' => 'exclude',
'module_img_opti_cron_folders' => array(),
'module_img_opti_cron_exclude_folders'=> array(),
'module_img_opti_cron_entities' => array('product', 'category', 'manufacturer', 'banner', 'blog'),
'module_img_opti_cron_quality_override'=> 0,
'module_img_opti_cron_jpg_quality' => 72,
'module_img_opti_cron_webp_quality' => 80,
'module_img_opti_cron_max_width' => 1600,
'module_img_opti_cron_max_height' => 1600,
'module_img_opti_cron_tasks' => '',
'module_img_opti_wm_type' => 'image',
'module_img_opti_wm_image' => '',
'module_img_opti_wm_text_val' => '',
'module_img_opti_wm_position' => '5',
'module_img_opti_wm_opacity' => '50',
'module_img_opti_wm_categories' => array(),
'module_img_opti_wm_brands' => array(),
'module_img_opti_wm_target_product' => 1,
'module_img_opti_wm_target_category' => 0,
'module_img_opti_wm_target_brand' => 0,
'module_img_opti_wm_target_banner' => 0,
'module_img_opti_wm_target_blog' => 0,
'module_img_opti_wm_angle' => 0,
'module_img_opti_wm_corner_radius' => 0,
'module_img_opti_wm_size_type' => 'original',
'module_img_opti_wm_size_percent' => 20,
'module_img_opti_wm_text_color' => '#ffffff',
'module_img_opti_wm_text_font' => '',
'module_img_opti_wm_filter_mode' => 'exclude',
'module_img_opti_min_width' => '300',
'module_img_opti_min_height' => '300',
'module_img_opti_ph_product' => '',
'module_img_opti_ph_category' => '',
'module_img_opti_ph_brand' => '',
'module_img_opti_ph_target_product' => 1,
'module_img_opti_ph_target_category' => 1,
'module_img_opti_ph_target_brand' => 1,
'module_img_opti_sz_translit' => 1,
'module_img_opti_sz_spaces' => 1,
'module_img_opti_sz_special' => 1,
'module_img_opti_sz_lowercase_name' => 1,
'module_img_opti_sz_lowercase_ext' => 1,
'module_img_opti_sz_normalize_ext' => 1,
'module_img_opti_webp_on_fly' => 0,
'module_img_opti_lazy_load' => 0,
'module_img_opti_broken_fallback' => 0,
'module_img_opti_fallback_image' => '',
'module_img_opti_log_level' => 'changed',
'module_img_opti_min_savings_bytes' => 100,
'module_img_opti_min_savings_percent' => 0.3,
'module_img_opti_folder_rules' => array(),
'module_img_opti_heavy_limit' => 50,
'module_img_opti_heavy_min_size' => 500,
'module_img_opti_png_jpg_limit' => 100,
'module_img_opti_png_jpg_min_size' => 500,
'module_img_opti_sanitizer_limit' => 100,
'module_img_opti_disk_quota_total' => 0,
'module_img_opti_disk_quota_used' => 0,
'module_img_opti_disk_status' => 0,
'module_img_opti_wm_max_width' => 0,
'module_img_opti_wm_max_height' => 0,
'module_img_opti_truefilemanager_webp'=> (is_file(DIR_APPLICATION . 'controller/common/truefilemanager.php') || is_file(DIR_SYSTEM . 'library/sitecreator/trueFileManager/ControllerCommonTrueFileManager.php')) ? 1 : 0
);
foreach ($fields as $key => $default) {
if (isset($this->request->post[$key])) {
$data[$key] = $this->request->post[$key];
} else {
$data[$key] = ($this->config->get($key) !== null) ? $this->config->get($key) : $default;
}
}
$data['module_img_opti_wm_rules_json'] = $data['module_img_opti_wm_rules'];
$data['module_img_opti_ph_rules_json'] = $data['module_img_opti_ph_rules'];
$data['module_img_opti_cron_tasks_json'] = $data['module_img_opti_cron_tasks'];
if (empty($data['module_img_opti_cron_tasks'])) {
$default_cron_tasks = array(
array(
'status' => 0,
'name' => 'Default Task',
'new_only' => (int)$data['module_img_opti_cron_new_only'],
'entities' => $data['module_img_opti_cron_entities'],
'folder_mode' => $data['module_img_opti_cron_folder_mode'],
'folders' => $data['module_img_opti_cron_folders'],
'quality_override' => (int)$data['module_img_opti_cron_quality_override'],
'jpg_quality' => (int)$data['module_img_opti_cron_jpg_quality'],
'webp_quality' => (int)$data['module_img_opti_cron_webp_quality'],
'max_width' => (int)$data['module_img_opti_cron_max_width'],
'max_height' => (int)$data['module_img_opti_cron_max_height']
)
);
$data['module_img_opti_cron_tasks_json'] = json_encode($default_cron_tasks, JSON_UNESCAPED_UNICODE);
}
if (empty($data['module_img_opti_wm_rules'])) {
$default_wm_rules = array();
if ($data['module_img_opti_wm_type']) {
$categories_data = array();
$this->load->model('catalog/category');
foreach ((array)$data['module_img_opti_wm_categories'] as $c_id) {
$c_info = $this->model_catalog_category->getCategory($c_id);
if ($c_info) {
$categories_data[] = array('id' => (int)$c_id, 'name' => $c_info['name']);
}
}
$brands_data = array();
$this->load->model('catalog/manufacturer');
foreach ((array)$data['module_img_opti_wm_brands'] as $b_id) {
$b_info = $this->model_catalog_manufacturer->getManufacturer($b_id);
if ($b_info) {
$brands_data[] = array('id' => (int)$b_id, 'name' => $b_info['name']);
}
}
$default_wm_rules[] = array(
'status' => 1,
'name' => 'Default Rule',
'type' => $data['module_img_opti_wm_type'],
'image' => $data['module_img_opti_wm_image'],
'text_val' => $data['module_img_opti_wm_text_val'],
'position' => $data['module_img_opti_wm_position'],
'opacity' => $data['module_img_opti_wm_opacity'],
'size_type' => $data['module_img_opti_wm_size_type'],
'size_percent' => $data['module_img_opti_wm_size_percent'],
'angle' => $data['module_img_opti_wm_angle'],
'corner_radius' => $data['module_img_opti_wm_corner_radius'],
'text_font' => $data['module_img_opti_wm_text_font'],
'text_color' => $data['module_img_opti_wm_text_color'],
'target_product' => (int)$data['module_img_opti_wm_target_product'],
'target_category' => (int)$data['module_img_opti_wm_target_category'],
'target_brand' => (int)$data['module_img_opti_wm_target_brand'],
'target_banner' => (int)$data['module_img_opti_wm_target_banner'],
'target_blog' => (int)$data['module_img_opti_wm_target_blog'],
'filter_mode' => $data['module_img_opti_wm_filter_mode'],
'categories' => $categories_data,
'brands' => $brands_data
);
}
$data['module_img_opti_wm_rules_json'] = json_encode($default_wm_rules);
}
if (empty($data['module_img_opti_ph_rules'])) {
$default_ph_rules = array();
if ($data['module_img_opti_ph_product']) {
$default_ph_rules[] = array(
'status' => 1,
'name' => 'Products Placeholder',
'target_product' => 1,
'target_category' => 0,
'target_brand' => 0,
'image' => $data['module_img_opti_ph_product'],
'filter_mode' => 'exclude',
'categories' => array(),
'brands' => array()
);
}
if ($data['module_img_opti_ph_category']) {
$default_ph_rules[] = array(
'status' => 1,
'name' => 'Categories Placeholder',
'target_product' => 0,
'target_category' => 1,
'target_brand' => 0,
'image' => $data['module_img_opti_ph_category'],
'filter_mode' => 'exclude',
'categories' => array(),
'brands' => array()
);
}
if ($data['module_img_opti_ph_brand']) {
$default_ph_rules[] = array(
'status' => 1,
'name' => 'Brands Placeholder',
'target_product' => 0,
'target_category' => 0,
'target_brand' => 1,
'image' => $data['module_img_opti_ph_brand'],
'filter_mode' => 'exclude',
'categories' => array(),
'brands' => array()
);
}
$data['module_img_opti_ph_rules_json'] = json_encode($default_ph_rules);
}
foreach (array('module_img_opti_cron_folders', 'module_img_opti_cron_exclude_folders', 'module_img_opti_cron_entities') as $arr_key) {
if (isset($data[$arr_key]) && !is_array($data[$arr_key])) {
if (is_string($data[$arr_key])) {
if (strpos($data[$arr_key], '[') === 0 || strpos($data[$arr_key], '{') === 0) {
$decoded = json_decode(html_entity_decode($data[$arr_key], ENT_QUOTES, 'UTF-8'), true);
$data[$arr_key] = is_array($decoded) ? $decoded : array();
} else {
$data[$arr_key] = explode(',', $data[$arr_key]);
}
} else {
$data[$arr_key] = array();
}
}
}
if (isset($data['module_img_opti_folder_rules'])) {
if (!is_array($data['module_img_opti_folder_rules'])) {
$rules = json_decode(html_entity_decode((string)$data['module_img_opti_folder_rules'], ENT_QUOTES, 'UTF-8'), true);
$data['module_img_opti_folder_rules'] = is_array($rules) ? $rules : array();
}
} else {
$data['module_img_opti_folder_rules'] = array();
}
$this->load->model('tool/image');
$data['placeholder'] = $this->model_tool_image->resize('no_image.png', 100, 100);
$image_fields = array('wm_image', 'ph_product', 'ph_category', 'ph_brand', 'fallback_image');
foreach ($image_fields as $img_field) {
$cfg_val = $data['module_img_opti_' . $img_field];
if (is_string($cfg_val) && is_file(DIR_IMAGE . $cfg_val)) {
$data['thumb_' . $img_field] = $this->model_tool_image->resize($cfg_val, 100, 100);
} else {
$data['thumb_' . $img_field] = $data['placeholder'];
}
}
$this->load->model('catalog/category');
$data['wm_categories'] = array();
if (!empty($data['module_img_opti_wm_categories'])) {
foreach ($data['module_img_opti_wm_categories'] as $category_id) {
$category_info = $this->model_catalog_category->getCategory((int)$category_id);
if ($category_info) {
$data['wm_categories'][] = array(
'category_id' => $category_info['category_id'],
'name' => ($category_info['path']) ? $category_info['path'] . ' > ' . $category_info['name'] : $category_info['name']
);
}
}
}
$this->load->model('catalog/manufacturer');
$data['wm_brands'] = array();
if (!empty($data['module_img_opti_wm_brands'])) {
foreach ($data['module_img_opti_wm_brands'] as $manufacturer_id) {
$manufacturer_info = $this->model_catalog_manufacturer->getManufacturer((int)$manufacturer_id);
if ($manufacturer_info) {
$data['wm_brands'][] = array(
'manufacturer_id' => $manufacturer_info['manufacturer_id'],
'name' => $manufacturer_info['name']
);
}
}
}
$data['user_token'] = $this->session->data['user_token'];
$data['catalog_url'] = (defined('HTTPS_CATALOG') && HTTPS_CATALOG) ? HTTPS_CATALOG : (defined('HTTP_CATALOG') ? HTTP_CATALOG : '');
$data['is_licensed'] = $this->checkLicense($data['module_img_opti_token']);
$data['catalog_dir'] = DIR_IMAGE . 'catalog';
$data['catalog_items'] = array();
$data['cron_folders_list'] = array();
$base_dir = DIR_IMAGE . 'catalog/';
if ($data['is_licensed'] && is_dir($base_dir)) {
$data['cron_folders_list'][] = 'catalog';
$cached_tree = $this->cache->get('img_opti_cron_folders_tree');
if (is_array($cached_tree)) {
$tree = $cached_tree;
} else {
$tree = $this->getFoldersTree($base_dir, 'catalog');
$this->cache->set('img_opti_cron_folders_tree', $tree);
}
$data['cron_folders_list'] = array_merge($data['cron_folders_list'], $tree);
$items = array_diff(scandir($base_dir), array('.', '..'));
foreach ($items as $item) {
if (is_dir($base_dir . $item)) {
$data['catalog_items'][] = array('type' => 'folder', 'name' => $item, 'path' => 'catalog/' . $item);
}
}
}
if ($this->config->get('module_img_opti_cron_folders') === null && !isset($this->request->post['module_img_opti_cron_folders'])) {
$data['module_img_opti_cron_folders'] = array();
}
$open_basedir = ini_get('open_basedir');
$data['ttf_fonts'] = array();
$font_dirs = array(
DIR_SYSTEM . 'library/font/',
DIR_SYSTEM . 'fonts/',
DIR_SYSTEM . 'font/',
DIR_CATALOG . 'view/theme/default/fonts/'
);
if (DIRECTORY_SEPARATOR === '/') {
if (!$open_basedir) {
$font_dirs[] = '/usr/share/fonts/truetype/dejavu/';
$font_dirs[] = '/usr/share/fonts/truetype/liberation/';
$font_dirs[] = '/usr/share/fonts/truetype/freefont/';
}
} else {
if (!$open_basedir) {
$font_dirs[] = 'C:/Windows/Fonts/';
}
}
$allowed_dirs = array();
if ($open_basedir) {
$allowed_dirs = explode(PATH_SEPARATOR, $open_basedir);
}
$font_added = array();
foreach ($font_dirs as $dir) {
$is_allowed = true;
if ($open_basedir) {
$is_allowed = false;
$norm_dir = rtrim(str_replace('\\', '/', $dir), '/');
foreach ($allowed_dirs as $allowed) {
$norm_allowed = rtrim(str_replace('\\', '/', $allowed), '/');
if ($norm_allowed && (strpos($norm_dir, $norm_allowed) === 0 || strpos($norm_allowed, $norm_dir) === 0)) {
$is_allowed = true;
break;
}
}
}
if (!$is_allowed) {
continue;
}
if (@is_dir($dir)) {
$files = @glob($dir . '*.ttf');
if ($files) {
$count = 0;
foreach ($files as $file) {
$name = $this->mb_basename($file);
if (!in_array($name, $font_added)) {
$data['ttf_fonts'][] = array(
'name' => $name,
'path' => $file
);
$font_added[] = $name;
$count++;
if ($count > 30) break;
}
}
}
}
}
$data['header'] = $this->load->controller('common/header');
$data['column_left'] = $this->load->controller('common/column_left');
$data['footer'] = $this->load->controller('common/footer');
$this->response->setOutput($this->load->view('module/img_opti', $data));
}
public function autocomplete_category() {
$this->loadLanguageSafe();
if (!isset($this->request->get['user_token']) || !isset($this->session->data['user_token']) || $this->request->get['user_token'] !== $this->session->data['user_token']) {
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode(array('error' => $this->language->get('error_permission'))));
return;
}
$json = array();
if (isset($this->request->get['filter_name'])) {
$this->load->model('catalog/category');
$filter_data = array(
'filter_name' => $this->request->get['filter_name'],
'sort' => 'name',
'order' => 'ASC',
'start' => 0,
'limit' => 15
);
$results = $this->model_catalog_category->getCategories($filter_data);
foreach ($results as $result) {
$json[] = array(
'category_id' => $result['category_id'],
'name' => strip_tags(html_entity_decode($result['name'], ENT_QUOTES, 'UTF-8'))
);
}
}
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode($json));
}
public function autocomplete_manufacturer() {
$this->loadLanguageSafe();
if (!isset($this->request->get['user_token']) || !isset($this->session->data['user_token']) || $this->request->get['user_token'] !== $this->session->data['user_token']) {
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode(array('error' => $this->language->get('error_permission'))));
return;
}
$json = array();
if (isset($this->request->get['filter_name'])) {
$this->load->model('catalog/manufacturer');
$filter_data = array(
'filter_name' => $this->request->get['filter_name'],
'start' => 0,
'limit' => 15
);
$results = $this->model_catalog_manufacturer->getManufacturers($filter_data);
foreach ($results as $result) {
$json[] = array(
'manufacturer_id' => $result['manufacturer_id'],
'name' => strip_tags(html_entity_decode($result['name'], ENT_QUOTES, 'UTF-8'))
);
}
}
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode($json));
}
private function validate() {
if (!$this->user->hasPermission('modify', 'module/img_opti')) {
$this->error['warning'] = $this->language->get('error_permission');
}
return !$this->error;
}
private function getToken() {
if (!empty($this->request->post['module_token'])) {
return trim($this->request->post['module_token']);
}
if (!empty($this->request->post['module_img_opti_token'])) {
return trim($this->request->post['module_img_opti_token']);
}
return $this->config->get('module_img_opti_token');
}
public function scan() {
$this->loadLanguageSafe();
if (!isset($this->request->get['user_token']) || !isset($this->session->data['user_token']) || $this->request->get['user_token'] !== $this->session->data['user_token']) {
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode(array('error' => $this->language->get('error_permission'))));
return;
}
$json = array();
if (!$this->user->hasPermission('modify', 'module/img_opti')) {
$json['error'] = $this->language->get('error_permission');
$this->response->setOutput(json_encode($json));
return;
}
session_write_close();
if (!$this->checkLicense($this->getToken())) {
$json['error'] = $this->language->get('error_license') . $this->language->get('error_domain') . $_SERVER['HTTP_HOST'] . ')';
$this->response->setOutput(json_encode($json));
return;
}
$selected_items = isset($this->request->post['selected_items']) ? (array)$this->request->post['selected_items'] : array();
$allFiles = $this->scanDirectoryHelper($selected_items);
file_put_contents(DIR_CACHE . 'img_opti_queue.json', json_encode($allFiles, JSON_UNESCAPED_UNICODE | JSON_PARTIAL_OUTPUT_ON_ERROR));
$json['total'] = count($allFiles);
$json['success'] = $this->language->get('text_found') . $json['total'];
$this->response->setOutput(json_encode($json));
}
public function process() {
$this->loadLanguageSafe();
if (!isset($this->request->get['user_token']) || !isset($this->session->data['user_token']) || $this->request->get['user_token'] !== $this->session->data['user_token']) {
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode(array('error' => $this->language->get('error_permission'))));
return;
}
$json = array('log' => array(), 'done' => 0, 'finished' => false, 'batch_old' => 0, 'batch_new' => 0);
$queueFile = DIR_CACHE . 'img_opti_queue.json';
if (!$this->checkLicense($this->getToken())) {
$json['error'] = $this->language->get('error_license') . $this->language->get('error_domain') . $_SERVER['HTTP_HOST'] . ')';
$this->response->setOutput(json_encode($json));
return;
}
session_write_close();
if (!file_exists($queueFile)) {
$json['finished'] = true;
$this->response->setOutput(json_encode($json));
return;
}
$fp = fopen($queueFile, 'c+');
if (!$fp) {
$json['finished'] = true;
$this->response->setOutput(json_encode($json));
return;
}
flock($fp, LOCK_EX);
$filesize = filesize($queueFile);
$queue = array();
if ($filesize > 0) {
$content = fread($fp, $filesize);
$queue = json_decode($content, true);
}
if (empty($queue)) {
flock($fp, LOCK_UN);
fclose($fp);
@unlink($queueFile);
$json['finished'] = true;
$this->response->setOutput(json_encode($json));
return;
}
$batch = array();
$batchSize = isset($this->request->post['batch_size']) ? (int)$this->request->post['batch_size'] : 200;
for ($i = 0; $i < $batchSize; $i++) {
if (empty($queue)) break;
$batch[] = array_shift($queue);
}
ftruncate($fp, 0);
rewind($fp);
fwrite($fp, json_encode($queue, JSON_UNESCAPED_UNICODE | JSON_PARTIAL_OUTPUT_ON_ERROR));
fflush($fp);
flock($fp, LOCK_UN);
fclose($fp);
$sourceDir = DIR_IMAGE . 'catalog';
$mode = isset($this->request->post['mode']) ? preg_replace('/[^a-z]/', '', (string)$this->request->post['mode']) : 'copy';
$copyFolder = !empty($this->request->post['copy_folder']) ? preg_replace('/[^a-zA-Z0-9_-]/', '', $this->request->post['copy_folder']) : 'catalog_new';
$engine = $this->config->get('module_img_opti_engine') == 'imagick' && extension_loaded('imagick') ? 'imagick' : 'gd';
$this->load->model('tool/image');
if ($mode == 'copy') {
$this->registerModuleFolder($copyFolder);
}
$targetDir = ($mode == 'copy') ? DIR_IMAGE . $copyFolder : $sourceDir;
$maxWidth = isset($this->request->post['max_width']) ? (int)$this->request->post['max_width'] : 1600;
$maxHeight = isset($this->request->post['max_height']) ? (int)$this->request->post['max_height'] : 1600;
$jpgQ = isset($this->request->post['jpg_quality']) ? (int)$this->request->post['jpg_quality'] : 72;
$pngQ = isset($this->request->post['png_quality']) ? (int)$this->request->post['png_quality'] : 7;
$webpQ = isset($this->request->post['webp_quality']) ? (int)$this->request->post['webp_quality'] : 80;
$threshold = isset($this->request->post['size_threshold']) ? (float)$this->request->post['size_threshold'] : 0;
$min_savings_bytes = isset($this->request->post['module_img_opti_min_savings_bytes']) ? (int)$this->request->post['module_img_opti_min_savings_bytes'] : 100;
$min_savings_percent = isset($this->request->post['module_img_opti_min_savings_percent']) ? (float)$this->request->post['module_img_opti_min_savings_percent'] : 0.3;
$log_level = isset($this->request->post['module_img_opti_log_level']) ? (string)$this->request->post['module_img_opti_log_level'] : (isset($this->request->post['log_level']) ? (string)$this->request->post['log_level'] : 'changed');
$processed = 0;
$batchOptimized = 0;
$batchOldSize = 0;
$batchNewSize = 0;
$dirImageNorm = rtrim(str_replace('\\', '/', DIR_IMAGE), '/') . '/';
$cache_updates = array();
foreach ($batch as $filePath) {
$realFilePath = realpath($filePath);
if (!$realFilePath || strpos(str_replace('\\', '/', $realFilePath), $dirImageNorm) !== 0 || !is_file($realFilePath)) continue;
$pathNorm = str_replace('\\', '/', $realFilePath);
$relativePath = preg_replace('/^' . preg_quote($dirImageNorm, '/') . '/i', '', $pathNorm);
$relativePath = ltrim($relativePath, '/');
$cleanRel = preg_replace('/^catalog\//i', '', $relativePath);
$cleanRel = ltrim($cleanRel, '/');
$targetDirNorm = rtrim(str_replace('\\', '/', $targetDir), '/');
$destPath = $targetDirNorm . '/' . $cleanRel;
if (!is_dir($this->mb_dirname($destPath))) {
@mkdir($this->mb_dirname($destPath), 0755, true);
}
$oldSize = filesize($realFilePath);
$tempPath = DIR_CACHE . 'opti_temp_' . md5($realFilePath);
$fileParams = $this->getImageParameters($relativePath, array(
'module_img_opti_folder_rules' => $this->config->get('module_img_opti_folder_rules'),
'module_img_opti_jpg_quality' => $jpgQ,
'module_img_opti_png_quality' => $pngQ,
'module_img_opti_webp_quality' => $webpQ,
'module_img_opti_max_width' => $maxWidth,
'module_img_opti_max_height' => $maxHeight
));
if ($this->optimizeImage($realFilePath, $tempPath, $fileParams['max_width'], $fileParams['max_height'], $fileParams['jpg_quality'], $fileParams['png_quality'], $fileParams['webp_quality'], $engine)) {
clearstatcache();
$newSize = filesize($tempPath);
$increasePercent = ($oldSize > 0) ? (($newSize - $oldSize) / $oldSize) * 100 : 0;
$statusClass = 'text-success';
$sign = $increasePercent > 0 ? '+' : '';
$statusText = $this->language->get('text_compressed') . ' (' . $sign . round($increasePercent, 1) . '%)';
$file_ok = true;
$savedBytes = $oldSize - $newSize;
$savedPercent = ($oldSize > 0) ? ($savedBytes / $oldSize) * 100 : 0;
$is_too_small_gain = ($savedBytes < $min_savings_bytes || $savedPercent < $min_savings_percent);
if ($increasePercent > $threshold || $is_too_small_gain) {
@unlink($tempPath);
$copied = true;
if ($mode == 'copy' || $realFilePath !== realpath($destPath)) {
$copied = @copy($realFilePath, $destPath);
}
if (!$copied) {
$file_ok = false;
$statusClass = 'text-danger';
$statusText = $this->language->get('text_error_process') . ' (Failed to copy original)';
} else {
$newSize = $oldSize;
$statusClass = 'text-muted';
$statusText = $this->language->get('text_skipped_no_gain') ? $this->language->get('text_skipped_no_gain') : 'Skipped';
$cacheKey = $this->getRelativeImagePath($realFilePath);
$cache_updates[$cacheKey] = array(
'size' => $oldSize,
'mtime' => @filemtime($realFilePath)
);
}
} else {
if (file_exists($destPath)) {
@unlink($destPath);
}
$moved = false;
if (@rename($tempPath, $destPath)) {
$moved = true;
} else {
if (@copy($tempPath, $destPath)) {
@unlink($tempPath);
$moved = true;
}
}
if (!$moved) {
$file_ok = false;
$statusClass = 'text-danger';
$statusText = $this->language->get('text_error_process') . ' (Failed to write optimized file)';
} else {
clearstatcache(true, $destPath);
$cacheKey = $this->getRelativeImagePath($realFilePath);
$cache_updates[$cacheKey] = array(
'size' => @filesize($destPath),
'mtime' => ($mode == 'replace') ? @filemtime($destPath) : @filemtime($realFilePath)
);
}
}
if ($file_ok) {
$batchOldSize += $oldSize;
$batchNewSize += $newSize;
}
$displayPath = $this->safe_utf8($cleanRel);
$isUnchanged = ($statusClass === 'text-muted');
if ($file_ok && !$isUnchanged) {
$batchOptimized++;
}
if (!$isUnchanged || $log_level === 'all') {
$compareBtn = '';
if (!$isUnchanged) {
$imgSize = @getimagesize($realFilePath);
if ($imgSize && $imgSize[0] >= 150 && $imgSize[1] >= 150) {
$compareBtn = sprintf(
'',
htmlspecialchars($relativePath, ENT_QUOTES, 'UTF-8'),
$this->translate('text_compare', 'Compare'),
$this->translate('text_compare', 'Compare')
);
}
}
$json['log'][] = sprintf(
"
[%s] %s (%s → %s)%s
",
$statusClass,
$statusText,
htmlspecialchars('image/catalog/' . $displayPath, ENT_QUOTES | ENT_IGNORE, 'UTF-8'),
$this->formatBytes($oldSize),
$this->formatBytes($newSize),
$isUnchanged ? '' : $compareBtn
);
}
} else {
$displayPath = $this->safe_utf8($cleanRel);
$json['log'][] = sprintf(
"%s %s
",
$this->language->get('text_error_process'),
htmlspecialchars('image/catalog/' . $displayPath, ENT_QUOTES | ENT_IGNORE, 'UTF-8')
);
}
$processed++;
gc_collect_cycles();
$memLimit = $this->getMemoryLimitInBytesHelper();
if (memory_get_usage(true) > 0.85 * $memLimit) {
break;
}
}
if (!empty($cache_updates)) {
$cache = $this->getOptimizedCache();
foreach ($cache_updates as $k => $v) {
$cache[$k] = $v;
}
$this->saveOptimizedCache($cache);
}
$json['done'] = $processed;
$json['optimized'] = $batchOptimized;
$json['batch_old'] = $batchOldSize;
$json['batch_new'] = $batchNewSize;
if (!file_exists($queueFile)) {
$json['finished'] = true;
}
$this->response->setOutput(json_encode($json));
}
public function applyChanges() {
$this->loadLanguageSafe();
if (!isset($this->request->get['user_token']) || !isset($this->session->data['user_token']) || $this->request->get['user_token'] !== $this->session->data['user_token']) {
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode(array('error' => $this->language->get('error_permission'))));
return;
}
$json = array();
if (!$this->user->hasPermission('modify', 'module/img_opti')) {
$json['error'] = $this->language->get('error_permission');
$this->response->setOutput(json_encode($json));
return;
}
$copyFolder = !empty($this->request->post['copy_folder']) ? preg_replace('/[^a-zA-Z0-9_-]/', '', (string)$this->request->post['copy_folder']) : 'catalog_new';
if (empty($copyFolder) || $copyFolder === 'catalog') {
$json['error'] = $this->language->get('error_invalid_folder');
$this->response->setOutput(json_encode($json));
return;
}
$source_dir = realpath(DIR_IMAGE . $copyFolder);
$target_dir = realpath(DIR_IMAGE . 'catalog');
$realImageDir = realpath(DIR_IMAGE);
if (!$source_dir || strpos($source_dir, $realImageDir) !== 0 || !is_dir($source_dir)) {
$json['error'] = $this->language->get('text_error_source');
$this->response->setOutput(json_encode($json));
return;
}
$backup_name = 'backup_' . date('Ymd_His');
$backup_dir = DIR_IMAGE . $backup_name;
$do_backup = isset($this->request->post['backup']) && (int)$this->request->post['backup'] === 1;
if ($do_backup) {
$this->registerModuleFolder($backup_name);
if (!is_dir($backup_dir)) @mkdir($backup_dir, 0755, true);
$backup_dir = realpath($backup_dir);
}
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source_dir, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST);
$errors = array();
$cache_updates = array();
foreach ($files as $file) {
$currentPath = str_replace('\\', '/', $file->getPathname());
$relative_path = preg_replace('/^' . preg_quote(str_replace('\\', '/', $source_dir), '/') . '/i', '', $currentPath);
$relative_path = ltrim($relative_path, '/');
$dest_file = rtrim(str_replace('\\', '/', $target_dir), '/') . '/' . $relative_path;
$dest_folder = $this->mb_dirname($dest_file);
if ($file->isDir()) {
if (!is_dir($dest_folder)) {
if (!@mkdir($dest_folder, 0755, true)) {
$errors[] = 'Failed to create directory: ' . $relative_path;
}
}
} else {
if (!is_dir($dest_folder)) {
@mkdir($dest_folder, 0755, true);
}
$replaced = true;
if (file_exists($dest_file)) {
if ($do_backup) {
$back_file = rtrim(str_replace('\\', '/', $backup_dir), '/') . '/' . $relative_path;
if (!is_dir($this->mb_dirname($back_file))) @mkdir($this->mb_dirname($back_file), 0755, true);
if (file_exists($back_file)) {
@unlink($back_file);
}
if (!@rename($dest_file, $back_file)) {
if (!@copy($dest_file, $back_file)) {
$replaced = false;
$errors[] = 'Backup failed: ' . $relative_path;
} else {
@unlink($dest_file);
}
}
} else {
if (!@unlink($dest_file)) {
$replaced = false;
$errors[] = 'Failed to delete original: ' . $relative_path;
}
}
}
if ($replaced) {
$moved = false;
if (@rename($file->getPathname(), $dest_file)) {
$moved = true;
} else {
if (@copy($file->getPathname(), $dest_file)) {
@unlink($file->getPathname());
$moved = true;
}
}
if (!$moved) {
$errors[] = 'Failed to apply changes: ' . $relative_path;
} else {
clearstatcache(true, $dest_file);
$cacheKey = $this->getRelativeImagePath($dest_file);
$cache_updates[$cacheKey] = array(
'size' => @filesize($dest_file),
'mtime' => @filemtime($dest_file)
);
}
}
}
}
if (!empty($cache_updates)) {
$cache = $this->getOptimizedCache();
foreach ($cache_updates as $k => $v) {
$cache[$k] = $v;
}
$this->saveOptimizedCache($cache);
}
$this->recursiveDelete($source_dir);
$msg = $this->language->get('text_apply_success');
$msg .= $do_backup ? $this->language->get('text_apply_backup_ok') . htmlspecialchars($backup_name, ENT_QUOTES, 'UTF-8') : $this->language->get('text_apply_no_backup');
if (!empty($errors)) {
$json['success'] = $msg . '
' . $this->language->get('text_apply_errors_warning') . '
' . implode('
', array_slice($errors, 0, 10)) . (count($errors) > 10 ? '
...' : '') . '';
} else {
$json['success'] = $msg;
}
$this->response->setOutput(json_encode($json));
}
public function resetCache() {
$this->loadLanguageSafe();
$json = array();
if (!isset($this->request->get['user_token']) || !isset($this->session->data['user_token']) || $this->request->get['user_token'] !== $this->session->data['user_token']) {
$json['error'] = $this->language->get('error_permission');
$this->response->setOutput(json_encode($json));
return;
}
$cache_file = DIR_CACHE . 'img_opti_optimized.json';
if (file_exists($cache_file)) {
@unlink($cache_file);
}
$this->cache->delete('img_opti_cron_folders_tree');
$json['success'] = $this->translate('text_cache_cleared', 'Optimization cache cleared. Next scan will check all files.');
$this->response->setOutput(json_encode($json));
}
public function clearTrash() {
$this->loadLanguageSafe();
if (!isset($this->request->get['user_token']) || !isset($this->session->data['user_token']) || $this->request->get['user_token'] !== $this->session->data['user_token']) {
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode(array('error' => $this->language->get('error_permission'))));
return;
}
$json = array();
if (!$this->user->hasPermission('modify', 'module/img_opti')) {
$json['error'] = $this->language->get('error_permission');
$this->response->setOutput(json_encode($json));
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();
$folders[] = 'catalog_trash';
$folders = array_unique($folders);
$realImageDir = realpath(DIR_IMAGE);
foreach ($folders as $folder) {
$folder = preg_replace('/[^a-zA-Z0-9_\-\/]/', '', (string)$folder);
if (empty($folder) || $folder === 'catalog') continue;
$path = realpath(DIR_IMAGE . $folder);
if ($path && strpos($path, $realImageDir) === 0 && is_dir($path)) {
$this->recursiveDelete($path);
}
}
$compare_path = realpath(DIR_IMAGE . 'cache/compare');
if ($compare_path && strpos($compare_path, $realImageDir) === 0 && is_dir($compare_path)) {
$this->recursiveDelete($compare_path);
}
$cache_file = DIR_CACHE . 'img_opti_optimized.json';
if (file_exists($cache_file)) {
@unlink($cache_file);
}
$this->db->query("DELETE FROM `" . DB_PREFIX . "setting` WHERE `code` = 'module_img_opti' AND `key` = 'module_img_opti_folders'");
$json['success'] = $this->language->get('text_quarantine_empty');
$this->response->setOutput(json_encode($json));
}
public function clear_backups() {
$this->loadLanguageSafe();
$json = array();
if (!isset($this->request->get['user_token']) || !isset($this->session->data['user_token']) || $this->request->get['user_token'] !== $this->session->data['user_token']) {
$json['error'] = $this->language->get('error_permission');
$this->response->setOutput(json_encode($json));
return;
}
if (!$this->user->hasPermission('modify', 'module/img_opti')) {
$json['error'] = $this->language->get('error_permission');
$this->response->setOutput(json_encode($json));
return;
}
$backup_dir = realpath(DIR_IMAGE . 'catalog_trash/backup');
$realImageDir = realpath(DIR_IMAGE);
if ($backup_dir && strpos($backup_dir, $realImageDir) === 0 && is_dir($backup_dir)) {
$this->recursiveDelete($backup_dir);
$json['success'] = $this->language->get('text_backups_cleared');
} else {
$json['error'] = $this->language->get('error_no_backups');
}
$this->response->setOutput(json_encode($json));
}
public function download_folder() {
if (!isset($this->request->get['user_token']) || !isset($this->session->data['user_token']) || $this->request->get['user_token'] !== $this->session->data['user_token']) {
return;
}
$folder = isset($this->request->get['folder']) ? preg_replace('/[^a-zA-Z0-9_-]/', '', (string)$this->request->get['folder']) : '';
if (empty($folder) || $folder === 'catalog') {
return;
}
$dir_path = realpath(DIR_IMAGE . $folder);
$realImageDir = realpath(DIR_IMAGE);
if (!$dir_path || strpos($dir_path, $realImageDir) !== 0 || !is_dir($dir_path)) {
return;
}
$zip_file = DIR_CACHE . $folder . '_' . date('Ymd_His') . '.zip';
$zip = new ZipArchive();
if ($zip->open($zip_file, ZipArchive::CREATE | ZipArchive::OVERWRITE) === TRUE) {
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir_path, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST);
foreach ($files as $file) {
if ($file->isFile()) {
$filePath = $file->getPathname();
$relativePath = substr($filePath, strlen($dir_path) + 1);
$zip->addFile($filePath, $relativePath);
}
}
$zip->close();
}
if (file_exists($zip_file)) {
header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="' . $this->mb_basename($zip_file) . '"');
header('Content-Length: ' . filesize($zip_file));
readfile($zip_file);
@unlink($zip_file);
exit;
}
}
public function compare_preview() {
$this->loadLanguageSafe();
$json = array();
if (!isset($this->request->get['user_token']) || !isset($this->session->data['user_token']) || $this->request->get['user_token'] !== $this->session->data['user_token']) {
$json['error'] = $this->language->get('error_permission');
$this->response->setOutput(json_encode($json));
return;
}
$id = isset($this->request->post['id']) ? str_replace(array('../', '..\\', "\0"), '', (string)$this->request->post['id']) : '';
$tool = isset($this->request->post['tool']) ? preg_replace('/[^a-zA-Z0-9_]/', '', (string)$this->request->post['tool']) : '';
$this->load->model('module/img_opti');
$json = $this->model_module_img_opti->generateCompareUrls($tool, $id);
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode($json));
}
private function registerModuleFolder($folder_name) {
$folder_name = preg_replace('/[^a-zA-Z0-9_\-\/]/', '', $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 recursiveDelete($dir) {
$realDir = realpath($dir);
if (!$realDir || strpos($realDir, realpath(DIR_IMAGE)) !== 0 || !is_dir($realDir)) return false;
$files = array_diff(scandir($realDir), array('.','..'));
foreach ($files as $file) {
$path = $realDir . DIRECTORY_SEPARATOR . $file;
(is_dir($path)) ? $this->recursiveDelete($path) : @unlink($path);
}
return @rmdir($realDir);
}
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;
}
private function scanDirectoryHelper($selected_items) {
$allFiles = array();
$scanRootFiles = false;
$dirImageNorm = rtrim(str_replace('\\', '/', DIR_IMAGE), '/');
$cache = $this->getOptimizedCache();
if (empty($selected_items) || in_array('all', (array)$selected_items)) {
$dirsToScan = array($dirImageNorm . '/catalog');
} else {
$dirsToScan = array();
if (in_array('root_files', (array)$selected_items)) {
$scanRootFiles = true;
}
foreach ((array)$selected_items as $item) {
if ($item === 'root_files' || $item === 'all') continue;
$clean_item = preg_replace('/[^a-zA-Z0-9_\-\/]/', '', (string)$item);
$clean_item = str_replace(array('../', '..\\'), '', $clean_item);
$fullPath = realpath($dirImageNorm . '/' . ltrim($clean_item, '/'));
if ($fullPath && strpos(str_replace('\\', '/', $fullPath), $dirImageNorm) === 0 && is_dir($fullPath)) {
$dirsToScan[] = str_replace('\\', '/', $fullPath);
}
}
}
if (!empty($dirsToScan)) {
foreach ($dirsToScan as $dir) {
if (!is_dir($dir)) continue;
try {
$directory = new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS);
$iterator = new RecursiveIteratorIterator($directory);
foreach ($iterator as $file) {
if ($file->isFile()) {
$ext = mb_strtolower($file->getExtension(), 'UTF-8');
if (in_array($ext, array('jpg', 'jpeg', 'png', 'webp', 'gif'))) {
$filePathNorm = str_replace('\\', '/', $file->getPathname());
$relPath = $this->getRelativeImagePath($filePathNorm);
$skip = false;
if (isset($cache[$relPath])) {
clearstatcache(true, $filePathNorm);
$size = @filesize($filePathNorm);
$mtime = @filemtime($filePathNorm);
if ($size === $cache[$relPath]['size'] && $mtime === $cache[$relPath]['mtime']) {
$skip = true;
}
}
if (!$skip) {
$allFiles[] = $filePathNorm;
}
}
}
}
} catch (Exception $e) {
continue;
}
}
}
if ($scanRootFiles) {
$base_dir = $dirImageNorm . '/catalog/';
if (is_dir($base_dir)) {
$items = scandir($base_dir);
foreach ($items as $item) {
if ($item == '.' || $item == '..') continue;
$fullPath = $base_dir . $item;
if (is_file($fullPath)) {
$ext = mb_strtolower($this->mb_pathinfo($fullPath, PATHINFO_EXTENSION), 'UTF-8');
if (in_array($ext, array('jpg', 'jpeg', 'png', 'webp', 'gif'))) {
$filePathNorm = str_replace('\\', '/', $fullPath);
$relPath = $this->getRelativeImagePath($filePathNorm);
$skip = false;
if (isset($cache[$relPath])) {
clearstatcache(true, $filePathNorm);
$size = @filesize($filePathNorm);
$mtime = @filemtime($filePathNorm);
if ($size === $cache[$relPath]['size'] && $mtime === $cache[$relPath]['mtime']) {
$skip = true;
}
}
if (!$skip) {
$allFiles[] = $filePathNorm;
}
}
}
}
}
}
return array_unique($allFiles);
}
private function optimizeImage($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;
$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 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 stop() {
$this->loadLanguageSafe();
if (!isset($this->request->get['user_token']) || !isset($this->session->data['user_token']) || $this->request->get['user_token'] !== $this->session->data['user_token']) {
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode(array('error' => $this->language->get('error_permission'))));
return;
}
$queueFile = DIR_CACHE . 'img_opti_queue.json';
if (file_exists($queueFile)) @unlink($queueFile);
$json = array('success' => true);
$this->response->setOutput(json_encode($json));
}
private function checkLicense($key) {
$host = explode(':', str_replace(array('http://', 'https://', 'www.'), '', strtolower($this->request->server['HTTP_HOST'])))[0];
if ($host === 'localhost' || $host === '127.0.0.1') return true;
if (empty($key)) return false;
$parts = explode('.', $host);
$v_k = 'k'; $v_to = 'to'; $v_bit = '_lom'; $v_end = 'aet_'; $v_img = 'image_'; $v_opt = 'optimizer_'; $v_mid = 'tot'; $v_core = '_redis'; $v_sfx = 'ka';
$salt = ($v_k . $v_to . $v_bit) . ($v_end . $v_img . $v_opt . $v_mid) . ($v_core . $v_sfx);
while(count($parts) >= 2) {
$domain = implode('.', $parts);
if ($key === strtoupper(implode('-', str_split(substr(md5($domain . $salt), 0, 16), 4)))) return true;
array_shift($parts);
}
return false;
}
public function run_tool() {
$this->loadLanguageSafe();
if (!isset($this->request->get['user_token']) || !isset($this->session->data['user_token']) || $this->request->get['user_token'] !== $this->session->data['user_token']) {
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode(array('error' => $this->language->get('error_permission'))));
return;
}
$json = array();
if (!$this->user->hasPermission('modify', 'module/img_opti')) {
$json['error'] = $this->language->get('error_permission');
$this->response->setOutput(json_encode($json));
return;
}
if (!$this->checkLicense($this->getToken())) {
$json['error'] = $this->language->get('error_license') . $this->language->get('error_domain') . $_SERVER['HTTP_HOST'] . ')';
$this->response->setOutput(json_encode($json));
return;
}
session_write_close();
$tool = isset($this->request->post['tool']) ? preg_replace('/[^a-zA-Z0-9_]/', '', (string)$this->request->post['tool']) : '';
$action = isset($this->request->post['action']) ? preg_replace('/[^a-zA-Z0-9_]/', '', (string)$this->request->post['action']) : 'scan';
$this->load->model('module/img_opti');
$method = $action . '_' . $tool;
if ($tool === 'broken') {
$method = $action . '_broken_db';
}
$allowed_methods = array(
'scan_broken_db', 'fix_broken_db',
'scan_broken_files', 'fix_broken_files',
'scan_duplicates', 'fix_duplicates',
'scan_png_jpg', 'fix_png_jpg',
'scan_translit', 'fix_translit',
'scan_empty_folders', 'fix_empty_folders',
'scan_smart_cache', 'fix_smart_cache',
'scan_cache_restore', 'fix_cache_restore',
'scan_exif', 'fix_exif',
'scan_placeholders', 'fix_placeholders',
'scan_watermark',
'scan_small_photos', 'fix_small_photos',
'scan_html_broken',
'scan_folder_tree', 'fix_folder_tree',
'scan_sanitizer', 'fix_sanitizer',
'scan_cmyk', 'fix_cmyk',
'scan_heavy_files', 'fix_heavy_files'
);
if (in_array($method, $allowed_methods)) {
$json = $this->model_module_img_opti->{$method}($this->request->post);
} else {
$json['error'] = 'Tool method not found: ' . $method;
}
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode($json));
}
public function upload_font() {
$this->loadLanguageSafe();
$json = array();
if (!isset($this->request->get['user_token']) || !isset($this->session->data['user_token']) || $this->request->get['user_token'] !== $this->session->data['user_token']) {
$json['error'] = $this->language->get('error_permission');
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode($json));
return;
}
if (!$this->user->hasPermission('modify', 'module/img_opti')) {
$json['error'] = $this->language->get('error_permission');
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode($json));
return;
}
if (!empty($this->request->files['file']['name']) && is_file($this->request->files['file']['tmp_name'])) {
$filename = $this->mb_basename(html_entity_decode($this->request->files['file']['name'], ENT_QUOTES, 'UTF-8'));
$filename = preg_replace('/[^a-zA-Z0-9_\-\.]/', '_', $filename);
$extension = strtolower($this->mb_pathinfo($filename, PATHINFO_EXTENSION));
if ($extension === 'ttf' && $this->request->files['file']['size'] < 5242880) {
$dir = DIR_SYSTEM . 'library/font/';
if (!is_dir($dir)) {
@mkdir($dir, 0755, true);
}
if (move_uploaded_file($this->request->files['file']['tmp_name'], $dir . $filename)) {
$json['success'] = $this->language->get('text_success_font_upload');
$json['font'] = array(
'name' => $filename,
'path' => $dir . $filename
);
} else {
$json['error'] = $this->language->get('error_font_upload');
}
} else {
$json['error'] = $this->language->get('error_font_upload');
}
} else {
$json['error'] = $this->language->get('error_font_upload');
}
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode($json));
}
public function scan_broken() {
$this->loadLanguageSafe();
if (!isset($this->request->get['user_token']) || !isset($this->session->data['user_token']) || $this->request->get['user_token'] !== $this->session->data['user_token']) {
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode(array('error' => $this->language->get('error_permission'))));
return;
}
$json = array();
if (!$this->user->hasPermission('modify', 'module/img_opti')) {
$json['error'] = $this->language->get('error_permission');
$this->response->setOutput(json_encode($json));
return;
}
if (!$this->checkLicense($this->getToken())) {
$json['error'] = $this->language->get('error_license') . $this->language->get('error_domain') . $_SERVER['HTTP_HOST'] . ')';
$this->response->setOutput(json_encode($json));
return;
}
session_write_close();
$this->load->model('module/img_opti');
$data = array(
'start' => isset($this->request->post['start']) ? (int)$this->request->post['start'] : 0,
'limit' => isset($this->request->post['limit']) ? (int)$this->request->post['limit'] : 200
);
$json = $this->model_module_img_opti->scan_broken_db($data);
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode($json));
}
public function fix_broken() {
$this->loadLanguageSafe();
if (!isset($this->request->get['user_token']) || !isset($this->session->data['user_token']) || $this->request->get['user_token'] !== $this->session->data['user_token']) {
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode(array('error' => $this->language->get('error_permission'))));
return;
}
$json = array();
if (!$this->user->hasPermission('modify', 'module/img_opti')) {
$json['error'] = $this->language->get('error_permission');
$this->response->setOutput(json_encode($json));
return;
}
if (!$this->checkLicense($this->getToken())) {
$json['error'] = $this->language->get('error_license') . $this->language->get('error_domain') . $_SERVER['HTTP_HOST'] . ')';
$this->response->setOutput(json_encode($json));
return;
}
session_write_close();
$this->load->model('module/img_opti');
$data = array(
'ids' => isset($this->request->post['ids']) && is_array($this->request->post['ids']) ? array_map(function($v) { return preg_replace('/[^a-zA-Z0-9_-]/', '', (string)$v); }, $this->request->post['ids']) : array(),
'fix_all' => isset($this->request->post['fix_all']) ? (int)$this->request->post['fix_all'] : 0
);
$this->model_module_img_opti->fix_broken_db($data);
$json['success'] = true;
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode($json));
}
public function build_index() {
$this->loadLanguageSafe();
if (!isset($this->request->get['user_token']) || !isset($this->session->data['user_token']) || $this->request->get['user_token'] !== $this->session->data['user_token']) {
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode(array('error' => $this->language->get('error_permission'))));
return;
}
$json = array();
if (!$this->user->hasPermission('modify', 'module/img_opti')) {
$json['error'] = $this->language->get('error_permission');
$this->response->setOutput(json_encode($json));
return;
}
$sysToken = isset($this->request->post['module_token']) ? trim((string)$this->request->post['module_token']) : '';
if (!$this->checkLicense($sysToken)) {
$json['error'] = $this->language->get('error_token');
$this->response->setOutput(json_encode($json));
return;
}
$whitelist = array();
$tables = $this->db->query("SHOW TABLES");
foreach ($tables->rows as $table_row) {
$table = array_values($table_row)[0];
$columns = $this->db->query("SHOW COLUMNS FROM `" . $this->db->escape((string)$table) . "`");
$text_cols = array();
foreach ($columns->rows as $col) {
$type = strtolower($col['Type']);
if (strpos($type, 'char') !== false || strpos($type, 'text') !== false) {
$text_cols[] = $col['Field'];
}
}
if (!empty($text_cols)) {
foreach ($text_cols as $col) {
$query = $this->db->query("SELECT `" . $this->db->escape((string)$col) . "` FROM `" . $this->db->escape((string)$table) . "` WHERE `" . $this->db->escape((string)$col) . "` LIKE '%catalog%'");
foreach ($query->rows as $row) {
$val = $row[$col];
$val = stripslashes((string)$val);
$val = html_entity_decode($val, ENT_QUOTES, 'UTF-8');
$val = urldecode($val);
if (preg_match_all('/(?:image\/)?(catalog\/[^\'"<>\\*|?\n\r\t]+?\.(?:jpg|jpeg|png|webp|gif|svg))/iu', $val, $matches)) {
foreach ($matches[1] as $match) {
$cleanPath = mb_strtolower(trim(str_replace('\\', '/', $match)), 'UTF-8');
$cleanPath = preg_replace('/^catalog\//i', '', $cleanPath);
$cleanPath = ltrim($cleanPath, '/');
if (!isset($whitelist[$cleanPath])) {
$whitelist[$cleanPath] = array();
}
$source = 'DB: ' . $table;
if (!in_array($source, $whitelist[$cleanPath])) {
$whitelist[$cleanPath][] = $source;
}
}
}
}
}
}
}
$theme_dir = realpath(DIR_CATALOG . 'view/theme/');
if ($theme_dir && is_dir($theme_dir)) {
$directory = new RecursiveDirectoryIterator($theme_dir, RecursiveDirectoryIterator::SKIP_DOTS);
$iterator = new RecursiveIteratorIterator($directory);
foreach ($iterator as $file) {
if ($file->isFile()) {
$ext = strtolower($file->getExtension());
if (in_array($ext, array('twig', 'tpl', 'css', 'php', 'js'))) {
$content = file_get_contents($file->getRealPath());
$content = urldecode($content);
if (preg_match_all('/(?:image\/)?(catalog\/[^\'"<>\\*|?\n\r\t]+?\.(?:jpg|jpeg|png|webp|gif|svg))/iu', $content, $matches)) {
foreach ($matches[1] as $match) {
$cleanPath = mb_strtolower(trim(str_replace('\\', '/', $match)), 'UTF-8');
$cleanPath = preg_replace('/^catalog\//i', '', $cleanPath);
$cleanPath = ltrim($cleanPath, '/');
if (!isset($whitelist[$cleanPath])) {
$whitelist[$cleanPath] = array();
}
$source = 'Theme: ' . $this->mb_basename($file->getFilename());
if (!in_array($source, $whitelist[$cleanPath])) {
$whitelist[$cleanPath][] = $source;
}
}
}
}
}
}
}
file_put_contents(DIR_CACHE . 'img_opti_whitelist.json', json_encode($whitelist, JSON_UNESCAPED_UNICODE | JSON_PARTIAL_OUTPUT_ON_ERROR));
$json['success'] = true;
$this->response->setOutput(json_encode($json));
}
public function scan_trash() {
$this->loadLanguageSafe();
if (!isset($this->request->get['user_token']) || !isset($this->session->data['user_token']) || $this->request->get['user_token'] !== $this->session->data['user_token']) {
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode(array('error' => $this->language->get('error_permission'))));
return;
}
$json = array('log' => array(), 'total' => 0, 'total_bytes' => 0, 'scanned_folders' => '');
if (!$this->user->hasPermission('modify', 'module/img_opti')) {
$json['error'] = $this->language->get('error_permission');
$this->response->setOutput(json_encode($json));
return;
}
if (!$this->checkLicense($this->getToken())) {
$json['error'] = $this->language->get('error_license') . $this->language->get('error_domain') . $_SERVER['HTTP_HOST'] . ')';
$this->response->setOutput(json_encode($json));
return;
}
session_write_close();
$extendedLog = isset($this->request->post['extended_log']) ? true : false;
$processBroken = isset($this->request->post['process_broken']) ? true : false;
$whitelist_file = DIR_CACHE . 'img_opti_whitelist.json';
$whitelist = file_exists($whitelist_file) ? json_decode(file_get_contents($whitelist_file), true) : array();
$selected_items = isset($this->request->post['clean_items']) ? (array)$this->request->post['clean_items'] : array();
if (empty($selected_items) || in_array('all', $selected_items)) {
$json['scanned_folders'] = $this->language->get('text_all_folders');
} else {
$folders_str = array();
foreach ($selected_items as $si) {
if ($si === 'root_files') {
$folders_str[] = $this->language->get('text_root_folder');
} else {
$clean_si = preg_replace('/[^a-zA-Z0-9_\-\/]/', '', (string)$si);
$folders_str[] = 'image/' . str_replace(array('../', '..\\'), '', $clean_si);
}
}
$json['scanned_folders'] = implode(', ', $folders_str);
}
$physicalFiles = $this->scanDirectoryHelper($selected_items);
$trashFiles = array();
$dirImageNorm = rtrim(str_replace('\\', '/', DIR_IMAGE), '/') . '/';
foreach ($physicalFiles as $path) {
$realPath = realpath($path);
if (!$realPath || strpos(str_replace('\\', '/', $realPath), $dirImageNorm) !== 0) continue;
$pathNorm = str_replace('\\', '/', $realPath);
$relPath = preg_replace('/^' . preg_quote($dirImageNorm, '/') . '/i', '', $pathNorm);
$cleanRelOrig = preg_replace('/^catalog\//i', '', ltrim(trim($relPath), '/'));
$cleanRelOrig = ltrim($cleanRelOrig, '/');
$cleanRelLower = mb_strtolower($cleanRelOrig, 'UTF-8');
if (strpos($cleanRelLower, 'catalog_trash/') === 0) continue;
$displayPath = $this->safe_utf8($cleanRelOrig);
if (!$processBroken && (strpos($displayPath, '?') !== false || strpos($cleanRelOrig, '?') !== false)) {
continue;
}
if (!isset($whitelist[$cleanRelLower])) {
$trashFiles[] = array(
'path_full' => $realPath,
'path_clean' => $cleanRelOrig,
'path_display' => $displayPath
);
$json['total_bytes'] += filesize($realPath);
$json['log'][] = "" . $this->language->get('text_log_trash') . " image/catalog/" . htmlspecialchars($displayPath, ENT_QUOTES | ENT_IGNORE, 'UTF-8') . "
";
} else {
if ($extendedLog) {
$sources = implode(', ', $whitelist[$cleanRelLower]);
$json['log'][] = "" . $this->language->get('text_log_used') . " image/catalog/" . htmlspecialchars($displayPath, ENT_QUOTES | ENT_IGNORE, 'UTF-8') . " (" . $this->language->get('text_used_in') . htmlspecialchars($sources, ENT_QUOTES | ENT_IGNORE, 'UTF-8') . ")
";
}
}
}
file_put_contents(DIR_CACHE . 'img_opti_trash_queue.json', json_encode($trashFiles, JSON_UNESCAPED_UNICODE | JSON_PARTIAL_OUTPUT_ON_ERROR));
$json['total'] = count($trashFiles);
$this->response->setOutput(json_encode($json));
}
public function process_trash() {
$this->loadLanguageSafe();
if (!isset($this->request->get['user_token']) || !isset($this->session->data['user_token']) || $this->request->get['user_token'] !== $this->session->data['user_token']) {
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode(array('error' => $this->language->get('error_permission'))));
return;
}
$json = array('log' => array(), 'finished' => false);
if (!$this->user->hasPermission('modify', 'module/img_opti')) {
$json['error'] = $this->language->get('error_permission');
$this->response->setOutput(json_encode($json));
return;
}
if (!$this->checkLicense($this->getToken())) {
$json['error'] = $this->language->get('error_license') . $this->language->get('error_domain') . $_SERVER['HTTP_HOST'] . ')';
$this->response->setOutput(json_encode($json));
return;
}
session_write_close();
$queueFile = DIR_CACHE . 'img_opti_trash_queue.json';
if (!file_exists($queueFile)) {
$json['finished'] = true;
$this->response->setOutput(json_encode($json));
return;
}
$queue = json_decode(file_get_contents($queueFile), true);
if (empty($queue)) {
@unlink($queueFile);
$json['finished'] = true;
$this->response->setOutput(json_encode($json));
return;
}
$action = isset($this->request->post['action']) ? preg_replace('/[^a-z]/', '', (string)$this->request->post['action']) : 'quarantine';
$batchSize = 200;
$processed = 0;
$dirImageNorm = rtrim(str_replace('\\', '/', DIR_IMAGE), '/') . '/';
while ($processed < $batchSize && !empty($queue)) {
$item = array_shift($queue);
$filePath = $item['path_full'];
$displayPath = $item['path_display'];
$realFilePath = realpath($filePath);
if ($realFilePath && strpos(str_replace('\\', '/', $realFilePath), $dirImageNorm) === 0 && is_file($realFilePath)) {
if ($action === 'delete') {
if (@unlink($realFilePath)) {
$json['log'][] = "" . $this->language->get('text_log_deleted') . " image/catalog/" . htmlspecialchars($displayPath, ENT_QUOTES | ENT_IGNORE, 'UTF-8') . "
";
} else {
$json['log'][] = "" . $this->language->get('text_log_del_err') . " image/catalog/" . htmlspecialchars($displayPath, ENT_QUOTES | ENT_IGNORE, 'UTF-8') . "
";
}
} else {
$cleanRel = str_replace(array('../', '..\\', "\0"), '', (string)$item['path_clean']);
$destPath = DIR_IMAGE . 'catalog_trash/' . ltrim($cleanRel, '/');
$destDir = $this->mb_dirname($destPath);
if (!is_dir($destDir)) {
@mkdir($destDir, 0755, true);
}
if (file_exists($destPath)) {
@unlink($destPath);
}
$rename_ok = false;
if (@rename($realFilePath, $destPath)) {
$rename_ok = true;
} else {
if (@copy($realFilePath, $destPath)) {
@unlink($realFilePath);
$rename_ok = true;
}
}
if ($rename_ok) {
$json['log'][] = "" . $this->language->get('text_log_quarantine') . " image/catalog_trash/" . htmlspecialchars($displayPath, ENT_QUOTES | ENT_IGNORE, 'UTF-8') . "
";
} else {
$json['log'][] = "" . $this->language->get('text_log_q_err') . " image/catalog/" . htmlspecialchars($displayPath, ENT_QUOTES | ENT_IGNORE, 'UTF-8') . "
";
}
}
}
$processed++;
}
file_put_contents($queueFile, json_encode($queue, JSON_UNESCAPED_UNICODE | JSON_PARTIAL_OUTPUT_ON_ERROR));
if (empty($queue)) {
@unlink($queueFile);
$json['finished'] = true;
}
$this->response->setOutput(json_encode($json));
}
public function restore_quarantine() {
$this->loadLanguageSafe();
if (!isset($this->request->get['user_token']) || !isset($this->session->data['user_token']) || $this->request->get['user_token'] !== $this->session->data['user_token']) {
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode(array('error' => $this->language->get('error_permission'))));
return;
}
$json = array('log' => array(), 'success' => true);
if (!$this->user->hasPermission('modify', 'module/img_opti')) {
$json['error'] = $this->language->get('error_permission');
$this->response->setOutput(json_encode($json));
return;
}
if (!$this->checkLicense($this->getToken())) {
$json['error'] = $this->language->get('error_license') . $this->language->get('error_domain') . $_SERVER['HTTP_HOST'] . ')';
$this->response->setOutput(json_encode($json));
return;
}
$trash_dir = realpath(DIR_IMAGE . 'catalog_trash');
$realImageDir = realpath(DIR_IMAGE);
if ($trash_dir && strpos($trash_dir, $realImageDir) === 0 && is_dir($trash_dir)) {
$directory = new RecursiveDirectoryIterator($trash_dir, RecursiveDirectoryIterator::SKIP_DOTS);
$iterator = new RecursiveIteratorIterator($directory);
$trash_dir_norm = str_replace('\\', '/', $trash_dir) . '/';
foreach ($iterator as $file) {
if ($file->isFile()) {
$src = str_replace('\\', '/', $file->getPathname());
$rel = preg_replace('/^' . preg_quote($trash_dir_norm, '/') . '/i', '', $src);
$cleanRel = str_replace(array('../', '..\\', "\0"), '', ltrim($rel, '/'));
$tools = array('duplicates', 'broken_files', 'small_photos', 'sanitizer', 'translit', 'png_jpg', 'smart_cache', 'exif');
$matched_tool = false;
foreach ($tools as $t) {
if (strpos($cleanRel, $t . '/') === 0) {
$exactRel = substr($cleanRel, strlen($t . '/'));
$dest = rtrim(str_replace('\\', '/', DIR_IMAGE), '/') . '/' . $exactRel;
$matched_tool = true;
break;
}
}
if (!$matched_tool) {
$dest = rtrim(str_replace('\\', '/', DIR_IMAGE), '/') . '/catalog/' . $cleanRel;
}
$displayPath = $this->safe_utf8($cleanRel);
$destDir = $this->mb_dirname($dest);
if (!is_dir($destDir)) {
@mkdir($destDir, 0755, true);
}
if (file_exists($dest)) {
@unlink($dest);
}
$rename_ok = false;
if (@rename($src, $dest)) {
$rename_ok = true;
} else {
if (@copy($src, $dest)) {
@unlink($src);
$rename_ok = true;
}
}
if ($rename_ok) {
$json['log'][] = "" . $this->language->get('text_log_restored') . " image/catalog/" . htmlspecialchars($displayPath, ENT_QUOTES | ENT_IGNORE, 'UTF-8') . "
";
} else {
$json['log'][] = "" . $this->language->get('text_log_rest_err') . " image/catalog_trash/" . htmlspecialchars($displayPath, ENT_QUOTES | ENT_IGNORE, 'UTF-8') . "
";
}
}
}
$this->recursiveDelete($trash_dir);
} else {
$json['log'][] = "" . $this->language->get('text_q_empty_err') . "
";
}
$this->response->setOutput(json_encode($json));
}
private function getImageParameters($relativePath, $globalSettings) {
$rules = isset($globalSettings['module_img_opti_folder_rules']) ? (array)$globalSettings['module_img_opti_folder_rules'] : array();
$jpgQ = isset($globalSettings['module_img_opti_jpg_quality']) ? (int)$globalSettings['module_img_opti_jpg_quality'] : 72;
$pngQ = isset($globalSettings['module_img_opti_png_quality']) ? (int)$globalSettings['module_img_opti_png_quality'] : 7;
$webpQ = isset($globalSettings['module_img_opti_webp_quality']) ? (int)$globalSettings['module_img_opti_webp_quality'] : 80;
$maxW = isset($globalSettings['module_img_opti_max_width']) ? (int)$globalSettings['module_img_opti_max_width'] : 1600;
$maxH = isset($globalSettings['module_img_opti_max_height']) ? (int)$globalSettings['module_img_opti_max_height'] : 1600;
$relativePath = str_replace('\\', '/', $relativePath);
$relativePath = ltrim($relativePath, '/');
foreach ($rules as $rule) {
if (empty($rule['folder'])) continue;
$folder = str_replace('\\', '/', $rule['folder']);
$folder = trim($folder, '/');
if ($folder !== '' && strpos($relativePath, $folder) !== false) {
if (isset($rule['jpg_quality']) && $rule['jpg_quality'] !== '') {
$jpgQ = (int)$rule['jpg_quality'];
}
if (isset($rule['webp_quality']) && $rule['webp_quality'] !== '') {
$webpQ = (int)$rule['webp_quality'];
}
if (isset($rule['max_width']) && $rule['max_width'] !== '') {
$maxW = (int)$rule['max_width'];
}
if (isset($rule['max_height']) && $rule['max_height'] !== '') {
$maxH = (int)$rule['max_height'];
}
break;
}
}
return array(
'jpg_quality' => $jpgQ,
'png_quality' => $pngQ,
'webp_quality' => $webpQ,
'max_width' => $maxW,
'max_height' => $maxH
);
}
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 preview_watermark() {
$this->loadLanguageSafe();
if (!isset($this->request->get['user_token']) || !isset($this->session->data['user_token']) || $this->request->get['user_token'] !== $this->session->data['user_token']) {
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode(array('error' => $this->language->get('error_permission'))));
return;
}
$json = array();
if (!$this->user->hasPermission('modify', 'module/img_opti')) {
$json['error'] = $this->language->get('error_permission');
$this->response->setOutput(json_encode($json));
return;
}
$type = isset($this->request->post['module_img_opti_wm_type']) ? $this->request->post['module_img_opti_wm_type'] : '';
$image = isset($this->request->post['module_img_opti_wm_image']) ? $this->request->post['module_img_opti_wm_image'] : '';
// Search for a real product image to act as a realistic preview background
$base_file = '';
$query_prod = $this->db->query("SELECT image FROM `" . DB_PREFIX . "product` WHERE image IS NOT NULL AND image != '' AND image LIKE 'catalog/%' LIMIT 1");
if ($query_prod->num_rows && is_file(DIR_IMAGE . $query_prod->row['image'])) {
$base_file = DIR_IMAGE . $query_prod->row['image'];
} else {
$base_file = DIR_IMAGE . 'no_image.png';
if (!is_file($base_file)) {
$base_file = DIR_IMAGE . 'catalog/no_image.png';
}
}
$temp_preview = DIR_IMAGE . 'cache/temp_wm_preview.png';
$compare_dir = DIR_IMAGE . 'cache/';
if (!is_dir($compare_dir)) {
@mkdir($compare_dir, 0755, true);
}
$base_img = false;
if (is_file($base_file)) {
$ext = strtolower(pathinfo($base_file, PATHINFO_EXTENSION));
if ($ext === 'jpg' || $ext === 'jpeg') {
$base_img = @imagecreatefromjpeg($base_file);
} elseif ($ext === 'png') {
$base_img = @imagecreatefrompng($base_file);
} elseif ($ext === 'webp' && function_exists('imagecreatefromwebp')) {
$base_img = @imagecreatefromwebp($base_file);
}
}
if (!$base_img) {
$base_img = imagecreatetruecolor(500, 500);
$bg = imagecolorallocate($base_img, 240, 240, 240);
imagefill($base_img, 0, 0, $bg);
}
$preview_w = 500;
$preview_h = 500;
$preview_im = imagecreatetruecolor($preview_w, $preview_h);
$white = imagecolorallocate($preview_im, 255, 255, 255);
imagefill($preview_im, 0, 0, $white);
$orig_w = imagesx($base_img);
$orig_h = imagesy($base_img);
$scale = min($preview_w / $orig_w, $preview_h / $orig_h);
$new_w = intval($orig_w * $scale);
$new_h = intval($orig_h * $scale);
$dst_x = intval(($preview_w - $new_w) / 2);
$dst_y = intval(($preview_h - $new_h) / 2);
imagecopyresampled($preview_im, $base_img, $dst_x, $dst_y, 0, 0, $new_w, $new_h, $orig_w, $orig_h);
imagedestroy($base_img);
imagepng($preview_im, $temp_preview);
imagedestroy($preview_im);
$this->config->set('module_img_opti_wm_type', $this->request->post['module_img_opti_wm_type']);
$this->config->set('module_img_opti_wm_image', $this->request->post['module_img_opti_wm_image']);
$this->config->set('module_img_opti_wm_text_val', $this->request->post['module_img_opti_wm_text_val']);
$this->config->set('module_img_opti_wm_position', $this->request->post['module_img_opti_wm_position']);
$this->config->set('module_img_opti_wm_opacity', $this->request->post['module_img_opti_wm_opacity']);
$this->config->set('module_img_opti_wm_angle', isset($this->request->post['module_img_opti_wm_angle']) ? $this->request->post['module_img_opti_wm_angle'] : 0);
$this->config->set('module_img_opti_wm_corner_radius', isset($this->request->post['module_img_opti_wm_corner_radius']) ? $this->request->post['module_img_opti_wm_corner_radius'] : 0);
$this->config->set('module_img_opti_wm_size_type', isset($this->request->post['module_img_opti_wm_size_type']) ? $this->request->post['module_img_opti_wm_size_type'] : 'original');
$this->config->set('module_img_opti_wm_size_percent', isset($this->request->post['module_img_opti_wm_size_percent']) ? $this->request->post['module_img_opti_wm_size_percent'] : 20);
$this->config->set('module_img_opti_wm_text_color', isset($this->request->post['module_img_opti_wm_text_color']) ? $this->request->post['module_img_opti_wm_text_color'] : '#ffffff');
$this->config->set('module_img_opti_wm_text_font', isset($this->request->post['module_img_opti_wm_text_font']) ? $this->request->post['module_img_opti_wm_text_font'] : '');
$this->config->set('module_img_opti_wm_target_product', 1);
$this->config->set('module_img_opti_wm_target_category', 0);
$this->config->set('module_img_opti_wm_target_brand', 0);
$this->config->set('module_img_opti_wm_target_banner', 0);
$this->config->set('module_img_opti_wm_target_blog', 0);
$this->load->model('module/img_opti');
$res = $this->model_module_img_opti->applyDynamicWatermark($temp_preview, '', true);
if ($res) {
$catalog_url = (defined('HTTPS_CATALOG') && HTTPS_CATALOG) ? HTTPS_CATALOG : (defined('HTTP_CATALOG') ? HTTP_CATALOG : '');
$json['success'] = true;
$json['preview_url'] = $catalog_url . 'image/cache/temp_wm_preview.png?t=' . time();
} else {
$json['error'] = $this->language->get('error_wm_preview') ?: 'Failed to generate preview';
}
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode($json));
}
private function getFoldersTree($dir, $base = '') {
$folders = array();
$realDir = realpath($dir);
if (!$realDir || !is_dir($realDir)) return $folders;
$items = array_diff(scandir($realDir), array('.', '..'));
foreach ($items as $item) {
$path = $realDir . DIRECTORY_SEPARATOR . $item;
if (is_dir($path)) {
$relPath = $base ? $base . '/' . $item : $item;
$folders[] = $relPath;
$folders = array_merge($folders, $this->getFoldersTree($path, $relPath));
}
}
return $folders;
}
private function getOptimizedCache() {
$cache_file = DIR_CACHE . 'img_opti_optimized.json';
if (file_exists($cache_file)) {
$content = @file_get_contents($cache_file);
if ($content) {
$decoded = json_decode($content, true);
if (is_array($decoded)) {
return $decoded;
}
}
}
return array();
}
private function saveOptimizedCache($cache) {
$cache_file = DIR_CACHE . 'img_opti_optimized.json';
@file_put_contents($cache_file, json_encode($cache, JSON_UNESCAPED_UNICODE));
}
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)) && substr($absolutePath, 3) === substr($dirImageNorm, 3)) {
$rel = substr($absolutePath, strlen($dirImageNorm));
}
$rel = ltrim($rel, '/');
return $this->safe_utf8($rel);
}
private function translate($key, $default = '') {
if (isset($this->translations[$key])) {
return $this->translations[$key];
}
return $default ? $default : $key;
}
private function loadLanguageSafe() {
$data = array();
if (isset($this->language) && is_object($this->language)) {
if (method_exists($this->language, 'all')) {
$all = $this->language->all();
if (is_array($all)) {
$data = array_merge($data, $all);
}
}
}
$en_paths = array(
DIR_LANGUAGE . 'en-gb/module/img_opti.php',
DIR_LANGUAGE . 'english/module/img_opti.php'
);
foreach ($en_paths as $path) {
if (is_file($path)) {
$_ = array();
require($path);
$data = array_merge($_, $data);
break;
}
}
$lang = '';
if (isset($this->session->data['language'])) {
$lang = $this->session->data['language'];
}
if (!$lang && $this->config->get('config_admin_language')) {
$lang = $this->config->get('config_admin_language');
}
if (!$lang && $this->config->get('config_language')) {
$lang = $this->config->get('config_language');
}
if (!$lang) {
$lang = 'en-gb';
}
$paths = array();
if ($lang && $lang !== 'en-gb' && $lang !== 'english') {
$paths[] = DIR_LANGUAGE . $lang . '/module/img_opti.php';
$paths[] = DIR_LANGUAGE . strtolower($lang) . '/module/img_opti.php';
}
if (isset($this->db)) {
try {
$query_lang = null;
if (is_numeric($lang)) {
$query_lang = $this->db->query("SELECT directory, code FROM `" . DB_PREFIX . "language` WHERE language_id = '" . (int)$lang . "' LIMIT 1");
} else if ($lang) {
$escaped_lang = $this->db->escape($lang);
$query_lang = $this->db->query("SELECT directory, code FROM `" . DB_PREFIX . "language` WHERE code = '" . $escaped_lang . "' OR directory = '" . $escaped_lang . "' LIMIT 1");
}
if ($query_lang && $query_lang->num_rows) {
if (!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';
}
if (!empty($query_lang->row['code'])) {
$lang = $query_lang->row['code'];
}
}
} catch (Exception $e) {}
}
$lang_lower = strtolower((string)$lang);
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 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_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 mb_basename($path) {
$path = str_replace('\\', '/', $path);
$parts = explode('/', $path);
return end($parts);
}
public function get_disk_stats() {
$this->loadLanguageSafe();
if (!isset($this->request->get['user_token']) || $this->request->get['user_token'] !== $this->session->data['user_token']) {
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode(array('error' => 'Permission denied')));
return;
}
session_write_close();
if (!$this->config->get('module_img_opti_disk_status')) {
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode(array('disabled' => true)));
return;
}
$refresh = isset($this->request->get['refresh']) ? (int)$this->request->get['refresh'] : 0;
if (!$refresh) {
$cached = $this->cache->get('img_opti_disk_stats');
if ($cached) {
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode($cached));
return;
}
}
$image_dir = rtrim(str_replace('\\', '/', DIR_IMAGE), '/');
$quota_total = (float)$this->config->get('module_img_opti_disk_quota_total');
$quota_used = (float)$this->config->get('module_img_opti_disk_quota_used');
if ($quota_total > 0 && $quota_used > 0) {
$disk_total = $quota_total * 1024 * 1024 * 1024;
$disk_used = $quota_used * 1024 * 1024 * 1024;
$disk_free = max(0.0, $disk_total - $disk_used);
} else {
$disk_total = (float)@disk_total_space($image_dir);
$disk_free = (float)@disk_free_space($image_dir);
}
$json = array(
'disk_total' => $disk_total,
'disk_free' => $disk_free,
'module_trash' => $this->dirSize($image_dir . '/catalog_trash'),
'oc_cache' => $this->dirSize($image_dir . '/cache'),
'catalog' => $this->dirSize($image_dir . '/catalog'),
);
$this->cache->set('img_opti_disk_stats', $json, 28800); // cache for 8 hours
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode($json));
}
private function dirSize($dir) {
$size = 0;
if (!is_dir($dir)) return 0;
try {
$it = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS)
);
foreach ($it as $file) {
if ($file->isFile()) $size += $file->getSize();
}
} catch (Exception $e) {}
return (float)$size;
}
public function export_settings() {
if (!isset($this->request->get['user_token']) || $this->request->get['user_token'] !== $this->session->data['user_token']) {
die('Permission denied');
}
$this->load->model('setting/setting');
$settings = $this->model_setting_setting->getSetting('module_img_opti');
$export = array(
'_module' => 'img_opti',
'_version' => '1.2.0',
'_exported' => date('Y-m-d H:i:s'),
'settings' => $settings
);
$filename = 'img_opti_settings_' . date('Ymd_His') . '.json';
$this->response->addHeader('Content-Type: application/json');
$this->response->addHeader('Content-Disposition: attachment; filename="' . $filename . '"');
$this->response->setOutput(json_encode($export, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
}
public function import_settings() {
$this->loadLanguageSafe();
if (!isset($this->request->get['user_token']) || $this->request->get['user_token'] !== $this->session->data['user_token']) {
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode(array('error' => $this->language->get('error_permission'))));
return;
}
if (!$this->user->hasPermission('modify', 'module/img_opti')) {
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode(array('error' => $this->language->get('error_permission'))));
return;
}
$json = array();
if (!isset($_FILES['import_json']) || $_FILES['import_json']['error'] !== UPLOAD_ERR_OK) {
$json['error'] = $this->language->get('error_import_file');
} else {
$content = @file_get_contents($_FILES['import_json']['tmp_name']);
$data = json_decode($content, true);
if (!$data || !isset($data['settings']) || !isset($data['_module']) || $data['_module'] !== 'img_opti') {
$json['error'] = $this->language->get('error_import_invalid');
} else {
$this->load->model('setting/setting');
$safe = array();
foreach ($data['settings'] as $key => $value) {
if (strpos($key, 'module_img_opti') === 0) {
$safe[$key] = $value;
}
}
$this->model_setting_setting->editSetting('module_img_opti', $safe);
$this->cache->delete('img_opti_disk_stats');
$json['success'] = true;
}
}
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode($json));
}
}