HEX
Server: LiteSpeed
System: Linux php-prod-3.spaceapp.ru 5.15.0-151-generic #161-Ubuntu SMP Tue Jul 22 14:25:40 UTC 2025 x86_64
User: sarli3128 (1010)
PHP: 7.4.33
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: //home/retile.ru/public_html/system/library/vendor/isenselabs/watermark/OpenCartImageSymlink.php
<?php

if (!class_exists('OpenCartImageSymlink')) {
    class OpenCartImageSymlink {
        private $group_size = 1000;
        private $group_separator = '-';
        private $group_base = 'cache';
        private $ds = DIRECTORY_SEPARATOR;
        private $mode = 0755;
        private $main_dir = 'main';
        private $additional_dir = 'additional';
        private $store_ids = array('0');
        private $language_ids = array();
        private $registry;

        public function __construct($registry) {
            $this->registry = $registry;

            $store_query = $this->db->query("SELECT store_id FROM `" . DB_PREFIX . "store`");

            foreach ($store_query->rows as $store_row) {
                $this->store_ids[] = $store_row['store_id'];
            }

            $language_query = $this->db->query("SELECT language_id FROM `" . DB_PREFIX . "language`");

            foreach ($language_query->rows as $language_row) {
                $this->language_ids[] = $language_row['language_id'];
            }
        }

        public function __get($key) {
            return $this->registry->get($key);
        }

        public function __set($key, $value) {
            $this->registry->set($key, $value);
        }

        public function getGroupBase() {
            return $this->group_base;
        }

        public function setGroupBase($new_base) {
            $this->group_base = $new_base;
        }

        public function getGroupDir() {
            return $this->setupGroupBase();
        }

        public function deleteProductDir($product_id) {
            if (false !== $product_dir = $this->setupProductDir($product_id)) {
                return $this->deleteDir($product_dir);
            }

            return false;
        }

        /*
            This method is used in the events after addProduct and editProduct. It deletes all existing symlinks and initializes generation of new symlinks with new mtimes. This forces OpenCart's ModelToolImage::resize to create a new image/cache instance.
        */

        public function update($product_id) {
            // Unfortunately UNIX does not allow us to simply touch existing links. Therefore, to preserve the original mtime, we must delete all product-related symlinks
            $this->deleteProductDir($product_id);

            // The product has been created/updated, so we want to create symlinks for its images.
            $main_info = $this->db->query("SELECT image FROM `" . DB_PREFIX . "product` WHERE product_id = '" . (int)$product_id . "'")->row;

            $this->linkImage($product_id, !empty($main_info['image']) ? $main_info['image'] : '', false);

            $additional_infos = $this->db->query("SELECT image FROM `" . DB_PREFIX . "product_image` WHERE product_id = '" . (int)$product_id . "'")->rows;

            foreach ($additional_infos as $additional_info) {
                $this->linkImage($product_id, !empty($additional_info['image']) ? $additional_info['image'] : '', true);
            }
        }

        /*
            This method is called by the update() method, and also individually for every getProduct or getProductImages from the catalog. It prepares the product-specific symlink container directory. Afterwards, it triggers permuteLanguageStore() and returns the result only for the current language and store.
        */

        public function linkImage($product_id, $original, $is_additional = false, $with_prefix = true) {
            // No point in doing anything if $original is not readable or if it does not exist...
            $prefix = $this->getPrefix();
            $original_path = $prefix . $original;

            if (is_file($original_path) && is_readable($original_path) && (false !== $product_dir = $this->setupProductDir($product_id))) {
                // Next, we set up the dirs for the main image or additional images and then create symlinks.
                $dir_by_type = $is_additional ? $this->additional_dir : $this->main_dir;

                if (false !== $target_dir = $this->setupDir($product_dir . $this->ds . $dir_by_type)) {
                    if (false !== $target_path = $this->permuteLanguageStore($original_path, $target_dir, $product_id)) {
                        return $with_prefix ? $target_path : substr($target_path, strlen($prefix));
                    }
                }
            }

            return false;
        }

        /*
            Here is where the actual symlinks are getting created. Mind that they are made on the basis of:

            $target_dir exists and it is writable - ensured by linkImage()
            $original_path exists and it is writable- ensured by linkImage()

            ===

            The symlink filename is a combination of:

            $original_filename;

            filemtime($original_path) - in case anyone touches the original file, or replaces it with another image, this must cause a different symlink;

            We are taking the md5 substring because we are only generating a unique ID based on the original image path and mtime.

            $store_id + $language_id - they are used to uniquely identify a symlinked image with a specific store-language pair. This ensures that older versions of LabelMaker and WaterMark will be able to apply different labels depending on store and language.
        */

        private function makeSymlink($store_id, $language_id, $original_path, $target_dir, $product_id) {
            // At this point we are certain $target_dir exists and that $original_path also exists.
            $target_extension =  '.' . pathinfo($original_path, PATHINFO_EXTENSION);
            $original_filename = pathinfo($original_path, PATHINFO_FILENAME);
            $md5_substr = substr(md5($original_path . filemtime($original_path) . $this->getProductDateModified($product_id)), 0, 4);
            $target_basename = $md5_substr . '-' . $original_filename . '-' . $store_id . '-' . $language_id . $target_extension;

            $target_path = str_replace('\\', '/', $target_dir . $this->ds . $target_basename);

            // If the link does not exist, create it. Otherwise, just return it.
            if (!file_exists($target_path)) {
                if ($this->noSymlinkOrCopy($original_path, $target_path)) {
                    if ($this->config->get('config_error_log')) {
                        $this->log->write("[OpenCartImageSymlink]: Could not create symlink/copy of " . $original_path . " to: " . $target_path);
                    }
                } else {
                    return $target_path;
                }
            } else {
                return $target_path;
            }

            return false;
        }

        /*
            This method returns the date_modified value for the product
        */

        private function getProductDateModified($product_id) {
            return $this->registry->get('db')->query("SELECT date_modified FROM `" . DB_PREFIX . "product` WHERE product_id='" . (int)$product_id. "'")->row['date_modified'];
        }

        /* 
            This method tries to first create a symlink. If it does not work, it falls back to copy.
        */

        private function noSymlinkOrCopy($original_path, $target_path) {
            if (function_exists('symlink')) {
                return !@symlink($original_path, $target_path) && !@copy($original_path, $target_path);
            } else {
                return !@copy($original_path, $target_path);
            }
        }

        /*
            This method iterates through all existing stores and languages and generates a symlinked version of the main image for every store. It also returns the symlinked image for the current store and language.
        */

        private function permuteLanguageStore($original_path, $target_dir, $product_id) {
            $result = array();

            foreach ($this->store_ids as $store_id) {
                foreach ($this->language_ids as $language_id) {
                    $result[$store_id][$language_id] = $this->makeSymlink($store_id, $language_id, $original_path, $target_dir, $product_id);
                }
            }

            $current_language_id = $this->config->has('config_language_id') ? $this->config->get('config_language_id') : $this->language_ids[0];
            $current_store_id = $this->config->has('config_store_id') ? $this->config->get('config_store_id') : $this->store_ids[0];

            // Assuming that config_store_id and config_language_id are always set and take one of the values of $this->store_ids and $this->language_ids.
            return $result[$current_store_id][$current_language_id];
        }

        private function setupDir($dir) {
            clearstatcache(true);

            if (@is_dir($dir)) {
                if (!@is_writable($dir) && !(@chmod($dir, $this->mode) && @is_writable($dir))) {
                    if ($this->config->get('config_error_log')) {
                        $this->log->write("[OpenCartImageSymlink]: Directory " . $dir . " exists, but it is not writable. chmod to " . $this->mode . " also failed.");
                    }

                    return false;
                }
            } else if (false === @mkdir($dir, $this->mode)) {
                if ($this->config->get('config_error_log')) {
                    $this->log->write("[OpenCartImageSymlink]: Could not create directory: " . $dir);
                }

                return false;
            }

            return realpath($dir);
        }

        private function getPrefix() {
            return realpath(DIR_IMAGE) . $this->ds;
        }

        private function setupGroupBase() {
            return $this->setupDir($this->getPrefix() . $this->group_base);
        }

        private function setupGroupDir($product_id) {
            if (false !== $group_base = $this->setupGroupBase()) {
                $group = (int)((int)$product_id / $this->group_size);

                $begin_id = $group * $this->group_size + 1;
                
                $end_id = ($group + 1) * $this->group_size;

                $dir_name = $begin_id . $this->group_separator . $end_id;

                return $this->setupDir($group_base . $this->ds . $dir_name);
            }

            return false;
        }

        private function setupProductDir($product_id) {
            if (false !== $group_dir = $this->setupGroupDir($product_id)) {
                return $this->setupDir($group_dir . $this->ds . $product_id);
            }

            return false;
        }

        private function deleteDir($dir) {
            clearstatcache(true);

            $handle = opendir($dir);

            while (false !== ($entry = readdir($handle))) {
                if (in_array($entry, array('.', '..'))) {
                    continue;
                }

                $item = $dir . $this->ds . $entry;

                if (is_dir($item)) {
                    $this->deleteDir($item);
                } else {
                    @unlink($item);
                }
            }

            closedir($handle);

            if (false === @rmdir($dir)) {
                if ($this->config->get('config_error_log')) {
                    $this->log->write("[OpenCartImageSymlink]: Could not delete directory: " . $dir);
                }

                return false;
            }

            return true;
        }

        
    }
}