name = 'addlivephoto'; $this->tab = 'front_office_features'; $this->version = '1.0.0'; $this->author = 'Panariga'; $this->need_instance = 0; $this->bootstrap = true; parent::__construct(); $this->displayName = $this->trans('Add Live Product Photos',[], 'Modules.Addlivephoto.Admin'); $this->description = $this->trans('Allows admin to add live photos of product details like expiry dates directly from their phone. Displays fresh images on the product page.',[], 'Modules.Addlivephoto.Admin'); $this->ps_versions_compliancy = array('min' => '8.0.0', 'max' => _PS_VERSION_); } /** * Module installation process. * @return bool */ public function install() { if ( !parent::install() || !$this->registerHook('displayProductPriceBlock') || !$this->registerHook('actionAdminControllerSetMedia') || !$this->installDb() || !$this->installAdminTab() || !$this->createImageDirectories() ) { return false; } return true; } /** * Module uninstallation process. * @return bool */ public function uninstall() { // Note: For safety, we are not deleting the /var/modules/addlivephoto directory // with user-uploaded images by default. You can add a configuration option for this. return parent::uninstall() && $this->uninstallDb() && $this->uninstallAdminTab(); } /** * Create the database table for storing image information. * @return bool */ protected function installDb() { $sql = 'CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . self::TABLE_NAME . '` ( `id_add_live_photo` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, `id_product` INT(11) UNSIGNED NOT NULL, `image_name` VARCHAR(255) NOT NULL, `date_add` DATETIME NOT NULL, PRIMARY KEY (`id_add_live_photo`), INDEX `id_product_idx` (`id_product`) ) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=utf8;'; return Db::getInstance()->execute($sql); } /** * Drop the database table. * @return bool */ protected function uninstallDb() { // return Db::getInstance()->execute('DROP TABLE IF EXISTS `' . _DB_PREFIX_ . self::TABLE_NAME . '`'); } /** * Install the link to our Admin Controller in the Quick Access menu. * @return bool */ protected function installAdminTab() { $tab = new Tab(); $tab->active = 1; $tab->class_name = 'AdminAddLivePhoto'; $tab->name = array(); foreach (Language::getLanguages(true) as $lang) { $tab->name[$lang['id_lang']] = $this->trans('Live Photo Uploader',[], 'Modules.Addlivephoto.Admin'); } $tab->id_parent = (int) Tab::getIdFromClassName('IMPROVE'); $tab->module = $this->name; return $tab->add(); } /** * Remove the Admin Controller link. * @return bool */ protected function uninstallAdminTab() { $id_tab = (int) Tab::getIdFromClassName('AdminAddLivePhoto'); if ($id_tab) { $tab = new Tab($id_tab); return $tab->delete(); } return true; } /** * Create directories for storing images. * @return bool */ protected function createImageDirectories() { if (!is_dir(self::IMG_DIR_VAR_PATH)) { // Create directory recursively with write permissions if (!mkdir(self::IMG_DIR_VAR_PATH, 0775, true)) { $this->_errors[] = $this->trans('Could not create image directory: ',[], 'Modules.Addlivephoto.Admin') . self::IMG_DIR_VAR_PATH; return false; } } // Add an index.php file for security if (!file_exists(self::IMG_DIR_VAR_PATH . 'index.php')) { @copy(_PS_MODULE_DIR_.$this->name.'/views/index.php', self::IMG_DIR_VAR_PATH . 'index.php'); } return true; } /** * Hook to display content on the product page. * @param array $params * @return string|void */ public function hookDisplayProductPriceBlock($params) { if (!isset($params['type']) || $params['type'] !== 'after_price') { return; } $id_product = (int) Tools::getValue('id_product'); if (!$id_product) { return; } // Fetch images from the last 4 months $sql = new DbQuery(); $sql->select('`image_name`'); $sql->from(self::TABLE_NAME); $sql->where('`id_product` = ' . $id_product); $sql->where('`date_add` >= DATE_SUB(NOW(), INTERVAL 4 MONTH)'); $sql->orderBy('`date_add` DESC'); $results = Db::getInstance()->executeS($sql); if (!$results) { return; } $live_photos = []; foreach ($results as $row) { $image_uri = $this->getProductImageUri($id_product, $row['image_name']); if ($image_uri) { $live_photos[] = [ 'url' => $image_uri, // This alt text is crucial for SEO 'alt' => sprintf( $this->trans('Freshness photo for %s, taken on %s',[], 'Modules.Addlivephoto.Shop'), $this->context->smarty->tpl_vars['product']->value['name'], date('Y-m-d') // You can store the date_add and format it here ), 'title' => $this->trans('Click to see the expiry date photo',[], 'Modules.Addlivephoto.Shop'), ]; } } if (empty($live_photos)) { return; } $this->context->smarty->assign([ 'live_photos' => $live_photos, 'module_name' => $this->name, ]); return $this->display(__FILE__, 'views/templates/hook/displayProductPriceBlock.tpl'); } /** * Hook to add CSS/JS to the admin controller page. */ public function hookActionAdminControllerSetMedia() { // We only want to load these assets on our specific controller page if (Tools::getValue('controller') == 'AdminAddLivePhoto') { $this->context->controller->addJS($this->_path . 'views/js/admin.js'); $this->context->controller->addCSS($this->_path . 'views/css/admin.css'); } } /** * Gets the full server path to a product's image directory, creating it if necessary. * Follows the PrestaShop pattern (e.g., /1/2/3/ for ID 123). * * @param int $id_product * @return string|false The path to the directory or false on failure. */ public function getProductImageServerPath($id_product) { if (!is_numeric($id_product)) { return false; } $path = self::IMG_DIR_VAR_PATH . implode('/', str_split((string)$id_product)) . '/'; if (!is_dir($path)) { if (!mkdir($path, 0775, true)) { return false; } // Add an index.php file for security if (!file_exists($path . 'index.php')) { @copy(_PS_MODULE_DIR_.$this->name.'/views/index.php', $path . 'index.php'); } } return $path; } /** * Gets the public URI for a specific product image. * * @param int $id_product * @param string $image_name * @return string|false The public URI or false if file does not exist. */ public function getProductImageUri($id_product, $image_name) { if (!is_numeric($id_product) || empty($image_name)) { return false; } $path_parts = str_split((string)$id_product); $image_path = implode('/', $path_parts) . '/' . $image_name; $server_path_check = self::IMG_DIR_VAR_PATH . $image_path; // We check if the file actually exists before returning a URI if (!file_exists($server_path_check)) { return false; } return $this->context->link->getBaseLink() . 'modules/addlivephoto/photo/' . $image_path; } /** * Deletes a live photo record and its corresponding file. * @param int $id_product * @param string $image_name * @return bool */ public function deleteProductImage($id_product, $image_name) { if (!is_numeric($id_product) || empty($image_name)) { return false; } // Delete from database $deleted_from_db = Db::getInstance()->delete( self::TABLE_NAME, '`id_product` = ' . (int)$id_product . ' AND `image_name` = \'' . pSQL($image_name) . '\'' ); // Delete file from server $file_path = $this->getProductImageServerPath($id_product) . $image_name; $deleted_from_disk = false; if (file_exists($file_path) && is_writable($file_path)) { $deleted_from_disk = unlink($file_path); } // Return true if both operations were successful or if the file was already gone but DB entry was removed return $deleted_from_db && ($deleted_from_disk || !file_exists($file_path)); } public function isUsingNewTranslationSystem() { return true; } }