load->language('module/img_opti'); if (!is_array($lang_data)) { $lang_data = array(); } $data = array_merge($this->loadLanguageSafe(), $lang_data); $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(); } $checkbox_keys = array( 'module_img_opti_status', 'module_img_opti_wm_status', 'module_img_opti_ph_status', 'module_img_opti_support_svg', 'module_img_opti_support_svg_autofix', 'module_img_opti_support_webp', 'module_img_opti_webp_on_fly', 'module_img_opti_lazy_load', 'module_img_opti_auto_fake_jpg_guard', 'module_img_opti_broken_fallback', 'module_img_opti_sz_translit', 'module_img_opti_sz_spaces', 'module_img_opti_sz_special', 'module_img_opti_sz_lowercase_name', 'module_img_opti_sz_lowercase_ext', 'module_img_opti_sz_normalize_ext', 'module_img_opti_disk_status' ); foreach ($checkbox_keys as $chk) { if (!isset($this->request->post[$chk])) { $this->request->post[$chk] = 0; } } $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_svg_autofix' => 1, '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_batch_limit' => 100, '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_grid_limit' => 50, '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, 'module_img_opti_auto_fake_jpg_guard' => 1, 'module_img_opti_fake_jpg_mode' => 'jpg' ); 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'], 'batch_limit' => (int)$data['module_img_opti_cron_batch_limit'] ) ); $data['module_img_opti_cron_tasks_json'] = json_encode($default_cron_tasks, JSON_UNESCAPED_UNICODE); } else { $cron_tasks_arr = json_decode(html_entity_decode($data['module_img_opti_cron_tasks'], ENT_QUOTES, 'UTF-8'), true); if (is_array($cron_tasks_arr)) { foreach ($cron_tasks_arr as &$t) { if (!isset($t['batch_limit'])) { $t['batch_limit'] = 100; } } unset($t); $data['module_img_opti_cron_tasks_json'] = json_encode($cron_tasks_arr, 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; } } } } } $stats = $this->getModuleModel()->getGlobalStats(); $data['stats_files'] = number_format($stats['total_files'], 0, '.', ' '); $data['stats_saved_mb'] = number_format($stats['saved_mb'], 2, '.', ' '); $data['stats_avg_pct'] = number_format($stats['avg_pct'], 1, '.', ' '); $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)); } private function getModuleModel() { if (!isset($this->model_extension_module_img_opti)) { $this->load->model('module/img_opti'); } return $this->model_extension_module_img_opti; } 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']; $json['log'] = array("
" . htmlspecialchars($json['success'], ENT_QUOTES | ENT_IGNORE, 'UTF-8') . "
"); file_put_contents(DIR_CACHE . 'img_opti_opti_scan_summary.json', json_encode($json, JSON_UNESCAPED_UNICODE | JSON_PARTIAL_OUTPUT_ON_ERROR)); @unlink(DIR_CACHE . 'img_opti_process_state.json'); $this->response->setOutput(json_encode($json)); } public function get_cached_opti() { $this->loadLanguageSafe(); $this->response->addHeader('Content-Type: application/json'); $state_file = DIR_CACHE . 'img_opti_process_state.json'; if (file_exists($state_file)) { $state = json_decode(file_get_contents($state_file), true); if ($state) { $this->response->setOutput(json_encode($state)); return; } } $summary_file = DIR_CACHE . 'img_opti_opti_scan_summary.json'; if (file_exists($summary_file)) { $json = json_decode(file_get_contents($summary_file), true); if ($json) { $this->response->setOutput(json_encode($json)); return; } } $this->response->setOutput(json_encode(array('total' => 0, 'success' => ''))); } 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)) { $this->markProcessFinishedState(); $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); $this->markProcessFinishedState(); $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 ($batchOptimized > 0 && $batchOldSize > $batchNewSize) { $bytesSaved = $batchOldSize - $batchNewSize; $this->getModuleModel()->recordGlobalStats($batchOptimized, $bytesSaved, $batchOldSize, 'manual_opti'); } $globalStats = $this->getModuleModel()->getGlobalStats(); $json['stats'] = array( 'files' => number_format($globalStats['total_files'], 0, '.', ' '), 'saved_mb' => number_format($globalStats['saved_mb'], 2, '.', ' '), 'avg_pct' => number_format($globalStats['avg_pct'], 1, '.', ' ') ); if (!file_exists($queueFile) || empty($queue)) { $json['finished'] = true; } $stateFile = DIR_CACHE . 'img_opti_process_state.json'; $state = array('total' => 0, 'processed' => 0, 'optimized' => 0, 'old_bytes' => 0, 'new_bytes' => 0, 'finished' => false, 'log' => array(), 'mode' => $mode, 'copy_folder' => $copyFolder); if (file_exists($stateFile)) { $prev = json_decode(file_get_contents($stateFile), true); if ($prev) $state = array_merge($prev, $state); } $state['mode'] = $mode; $state['copy_folder'] = $copyFolder; $state['processed'] += $processed; $state['optimized'] += $batchOptimized; $state['old_bytes'] += $batchOldSize; $state['new_bytes'] += $batchNewSize; if (!empty($json['log'])) { $state['log'] = array_merge($state['log'], $json['log']); } if (!empty($json['finished'])) { $state['finished'] = true; } $scanSummary = DIR_CACHE . 'img_opti_opti_scan_summary.json'; if (file_exists($scanSummary)) { $scan = json_decode(file_get_contents($scanSummary), true); if ($scan && isset($scan['total'])) { $state['total'] = $scan['total']; } } file_put_contents($stateFile, json_encode($state, JSON_UNESCAPED_UNICODE | JSON_PARTIAL_OUTPUT_ON_ERROR)); $this->response->setOutput(json_encode($json)); } private function markProcessFinishedState() { $stateFile = DIR_CACHE . 'img_opti_process_state.json'; if (file_exists($stateFile)) { $state = json_decode(file_get_contents($stateFile), true); if ($state) { $state['finished'] = true; file_put_contents($stateFile, json_encode($state, JSON_UNESCAPED_UNICODE | JSON_PARTIAL_OUTPUT_ON_ERROR)); } } } 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); } $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; } @unlink(DIR_CACHE . 'img_opti_process_state.json'); @unlink(DIR_CACHE . 'img_opti_opti_scan_summary.json'); @unlink(DIR_CACHE . 'img_opti_queue.json'); $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'); @unlink(DIR_CACHE . 'img_opti_process_state.json'); @unlink(DIR_CACHE . 'img_opti_opti_scan_summary.json'); @unlink(DIR_CACHE . 'img_opti_queue.json'); $this->response->setOutput(json_encode($json)); } public function save_grid_limit() { $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; } $limit = isset($this->request->post['limit']) ? max(1, (int)$this->request->post['limit']) : 50; $query = $this->db->query("SELECT * FROM `" . DB_PREFIX . "setting` WHERE `code` = 'module_img_opti' AND `key` = 'module_img_opti_grid_limit' AND `store_id` = '0'"); if ($query->num_rows) { $this->db->query("UPDATE `" . DB_PREFIX . "setting` SET `value` = '" . (int)$limit . "' WHERE `code` = 'module_img_opti' AND `key` = 'module_img_opti_grid_limit' AND `store_id` = '0'"); } else { $this->db->query("INSERT INTO `" . DB_PREFIX . "setting` SET `code` = 'module_img_opti', `key` = 'module_img_opti_grid_limit', `value` = '" . (int)$limit . "', `serialized` = '0', `store_id` = '0'"); } $json['success'] = true; $json['limit'] = $limit; $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_extension_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 reset_stats() { $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; } $newStats = $this->getModuleModel()->resetGlobalStats(); $this->response->addHeader('Content-Type: application/json'); $this->response->setOutput(json_encode(array( 'success' => true, 'files' => number_format($newStats['total_files'], 0, '.', ' '), 'saved_mb' => number_format($newStats['saved_mb'], 2, '.', ' '), 'avg_pct' => number_format($newStats['avg_pct'], 1, '.', ' ') ))); } 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'); if ($action === 'get_cached_grid') { $method = 'get_cached_grid'; } elseif ($action === 'preview_black_bg_row') { $method = 'preview_black_bg_row'; } else { $method = $action . '_' . $tool; if ($tool === 'broken') { $method = $action . '_broken_db'; } } $allowed_methods = array( 'get_cached_grid', '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', 'fix_html_broken', 'scan_folder_tree', 'fix_folder_tree', 'scan_sanitizer', 'fix_sanitizer', 'scan_cmyk', 'fix_cmyk', 'scan_heavy_files', 'fix_heavy_files', 'scan_svg_security', 'fix_svg_security', 'scan_black_bg', 'fix_black_bg', 'preview_black_bg_row' ); if (in_array($method, $allowed_methods)) { $start = isset($this->request->post['start']) ? max(0, (int)$this->request->post['start']) : 0; $limit = isset($this->request->post['limit']) ? max(0, (int)$this->request->post['limit']) : 0; $page = isset($this->request->post['page']) ? max(1, (int)$this->request->post['page']) : 1; if ($limit > 0 && $start === 0 && $page > 1) { $start = ($page - 1) * $limit; } $this->request->post['start'] = $start; $this->request->post['limit'] = $limit; $json = $this->model_extension_module_img_opti->{$method}($this->request->post); if (isset($json['total']) && $limit > 0) { $json['page'] = floor($start / $limit) + 1; $json['pages'] = max(1, ceil($json['total'] / $limit)); } } 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_extension_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_extension_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); file_put_contents(DIR_CACHE . 'img_opti_trash_scan_summary.json', json_encode($json, JSON_UNESCAPED_UNICODE | JSON_PARTIAL_OUTPUT_ON_ERROR)); $this->response->setOutput(json_encode($json)); } public function get_cached_trash() { $this->loadLanguageSafe(); $this->response->addHeader('Content-Type: application/json'); $summary_file = DIR_CACHE . 'img_opti_trash_scan_summary.json'; if (file_exists($summary_file)) { $json = json_decode(file_get_contents($summary_file), true); if ($json) { $this->response->setOutput(json_encode($json)); return; } } $this->response->setOutput(json_encode(array('total' => 0, 'total_bytes' => 0, 'log' => array()))); } 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); @unlink(DIR_CACHE . 'img_opti_trash_scan_summary.json'); $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'] : ''; $bg_mode = isset($this->request->post['wm_preview_bg']) ? $this->request->post['wm_preview_bg'] : 'product'; $base_file = ''; if ($bg_mode === 'product') { $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'; } } } $compare_dir = DIR_IMAGE . 'cache/'; if (!is_dir($compare_dir)) { @mkdir($compare_dir, 0755, true); } // Clean up old preview files older than 30 seconds foreach (@glob($compare_dir . 'temp_wm_preview_*.png') as $old_file) { if (is_file($old_file) && (time() - filemtime($old_file) > 30)) { @unlink($old_file); } } $preview_hash = md5(microtime(true) . rand(10000, 99999)); $temp_rel_path = 'cache/temp_wm_preview_' . $preview_hash . '.png'; $temp_preview = DIR_IMAGE . $temp_rel_path; if ($bg_mode === 'white') { $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); } elseif ($bg_mode === 'transparent') { $preview_w = 500; $preview_h = 500; $preview_im = imagecreatetruecolor($preview_w, $preview_h); imagealphablending($preview_im, false); imagesavealpha($preview_im, true); $c1 = imagecolorallocate($preview_im, 255, 255, 255); $c2 = imagecolorallocate($preview_im, 226, 232, 240); imagefill($preview_im, 0, 0, $c1); $sqSize = 25; for ($x = 0; $x < $preview_w; $x += $sqSize) { for ($y = 0; $y < $preview_h; $y += $sqSize) { if ((($x / $sqSize) + ($y / $sqSize)) % 2 === 0) { imagefilledrectangle($preview_im, $x, $y, $x + $sqSize - 1, $y + $sqSize - 1, $c2); } } } } else { $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) { $orig_w = imagesx($base_img); $orig_h = imagesy($base_img); $max_side = 500; $scale = min($max_side / max(1, $orig_w), $max_side / max(1, $orig_h)); $preview_w = max(10, intval($orig_w * $scale)); $preview_h = max(10, intval($orig_h * $scale)); $preview_im = imagecreatetruecolor($preview_w, $preview_h); imagecopyresampled($preview_im, $base_img, 0, 0, 0, 0, $preview_w, $preview_h, $orig_w, $orig_h); imagedestroy($base_img); } else { $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); } } 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_size_px', isset($this->request->post['module_img_opti_wm_size_px']) ? $this->request->post['module_img_opti_wm_size_px'] : 200); $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_text_size', isset($this->request->post['module_img_opti_wm_text_size']) ? $this->request->post['module_img_opti_wm_text_size'] : 24); $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_extension_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/' . $temp_rel_path . '?v=' . microtime(true); } 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'); $rawSettings = $this->model_setting_setting->getSetting('module_img_opti'); $allKeys = array( 'module_img_opti_status', 'module_img_opti_engine', '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_convert_webp', 'module_img_opti_support_svg', 'module_img_opti_support_svg_autofix', 'module_img_opti_support_webp', 'module_img_opti_webp_on_fly', 'module_img_opti_lazy_load', 'module_img_opti_auto_fake_jpg_guard', 'module_img_opti_fake_jpg_mode', 'module_img_opti_broken_fallback', 'module_img_opti_fallback_image', 'module_img_opti_cron_tasks', 'module_img_opti_wm_status', 'module_img_opti_wm_rules', 'module_img_opti_wm_max_width', 'module_img_opti_wm_max_height', 'module_img_opti_ph_rules', 'module_img_opti_folder_rules', 'module_img_opti_log_level', 'module_img_opti_min_savings_bytes', 'module_img_opti_min_savings_percent', 'module_img_opti_heavy_limit', 'module_img_opti_heavy_min_size', 'module_img_opti_png_jpg_limit', 'module_img_opti_png_jpg_min_size', 'module_img_opti_sanitizer_limit', 'module_img_opti_grid_limit', 'module_img_opti_disk_quota_total', 'module_img_opti_disk_quota_used', 'module_img_opti_disk_status', 'module_img_opti_truefilemanager_webp' ); $exportSettings = array(); foreach ($allKeys as $k) { if (isset($rawSettings[$k])) { $exportSettings[$k] = $rawSettings[$k]; } else { $exportSettings[$k] = $this->config->get($k); } } if (is_array($rawSettings)) { foreach ($rawSettings as $k => $v) { if (strpos($k, 'module_img_opti') === 0 && !isset($exportSettings[$k])) { $exportSettings[$k] = $v; } } } $export = array( '_module' => 'img_opti', '_version' => '1.2.0', '_exported' => date('Y-m-d H:i:s'), 'settings' => $exportSettings ); $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'); $current = $this->model_setting_setting->getSetting('module_img_opti'); if (!is_array($current)) { $current = array(); } foreach ($data['settings'] as $key => $value) { if (strpos($key, 'module_img_opti') === 0) { $current[$key] = $value; } } $this->model_setting_setting->editSetting('module_img_opti', $current); $this->cache->delete('img_opti_disk_stats'); $json['success'] = true; } } $this->response->addHeader('Content-Type: application/json'); $this->response->setOutput(json_encode($json)); } public function autoRotateImage($filePath, $im = null) { if (!function_exists('exif_read_data')) { return $im; } $ext = strtolower(pathinfo($filePath, PATHINFO_EXTENSION)); if (!in_array($ext, array('jpg', 'jpeg', 'tiff'))) { return $im; } $exif = @exif_read_data($filePath); if (!$exif || !isset($exif['Orientation'])) { return $im; } $orientation = (int)$exif['Orientation']; $angle = 0; switch ($orientation) { case 3: $angle = 180; break; case 6: $angle = -90; break; case 8: $angle = 90; break; } if ($angle !== 0) { if ($im !== null) { $im = @imagerotate($im, $angle, 0); } else { $img = @imagecreatefromjpeg($filePath); if ($img) { $img = @imagerotate($img, $angle, 0); @imagejpeg($img, $filePath, 95); @imagedestroy($img); } } } return $im; } public function convertPngToJpgClean($sourcePngPath, $targetJpgPath, $quality = 100, $bgColorHex = '#ffffff') { $img = @imagecreatefrompng($sourcePngPath); if (!$img) return false; if (function_exists('imagepalettetotruecolor') && !imageistruecolor($img)) { imagepalettetotruecolor($img); } $width = imagesx($img); $height = imagesy($img); $canvas = imagecreatetruecolor($width, $height); $hex = ltrim($bgColorHex, '#'); if (strlen($hex) === 3) { $r = hexdec(substr($hex, 0, 1) . substr($hex, 0, 1)); $g = hexdec(substr($hex, 1, 1) . substr($hex, 1, 1)); $b = hexdec(substr($hex, 2, 1) . substr($hex, 2, 1)); } else { $r = hexdec(substr($hex, 0, 2)); $g = hexdec(substr($hex, 2, 2)); $b = hexdec(substr($hex, 4, 2)); } $bgColor = imagecolorallocate($canvas, $r, $g, $b); imagefill($canvas, 0, 0, $bgColor); imagealphablending($canvas, true); imagecopy($canvas, $img, 0, 0, 0, 0, $width, $height); $success = @imagejpeg($canvas, $targetJpgPath, $quality); imagedestroy($img); imagedestroy($canvas); return $success; } public function convertPngToWebpClean($sourcePngPath, $targetWebpPath, $quality = 80) { if (!function_exists('imagewebp')) return false; $img = @imagecreatefrompng($sourcePngPath); if (!$img) return false; if (function_exists('imagepalettetotruecolor') && !imageistruecolor($img)) { imagepalettetotruecolor($img); } imagealphablending($img, false); imagesavealpha($img, true); $success = @imagewebp($img, $targetWebpPath, $quality); imagedestroy($img); return $success; } public function normalizePngPalette($img) { if ($img && function_exists('imagepalettetotruecolor') && !imageistruecolor($img)) { imagepalettetotruecolor($img); } return $img; } public function normalizeCmykToRgbJpeg($fullPath, $quality = 90) { if (!is_file($fullPath)) return false; if (extension_loaded('imagick')) { try { $img = new \Imagick($fullPath); if ($img->getImageColorspace() == \Imagick::COLORSPACE_CMYK) { $img->transformImageColorspace(\Imagick::COLORSPACE_SRGB); $img->setImageFormat('jpeg'); $img->writeImage($fullPath); $img->clear(); $img->destroy(); return true; } $img->clear(); $img->destroy(); } catch (\Exception $e) {} } $info = @getimagesize($fullPath); if (isset($info['channels']) && $info['channels'] == 4) { $img = @imagecreatefromjpeg($fullPath); if ($img) { $width = imagesx($img); $height = imagesy($img); $rgbCanvas = imagecreatetruecolor($width, $height); for ($x = 0; $x < $width; $x++) { for ($y = 0; $y < $height; $y++) { $pixel = imagecolorat($img, $x, $y); $c = ($pixel >> 16) & 0xFF; $m = ($pixel >> 8) & 0xFF; $y_val = $pixel & 0xFF; $r = (int)(($c * $y_val) / 255); $g = (int)(($m * $y_val) / 255); $b = (int)($y_val); $color = imagecolorallocate($rgbCanvas, min(255, max(0, $r)), min(255, max(0, $g)), min(255, max(0, $b))); imagesetpixel($rgbCanvas, $x, $y, $color); } } @imagejpeg($rgbCanvas, $fullPath, $quality); imagedestroy($img); imagedestroy($rgbCanvas); return true; } } return false; } }