325 lines
11 KiB
PHP
325 lines
11 KiB
PHP
<?php
|
|
/**
|
|
* 2007-2023 PrestaShop
|
|
*
|
|
* NOTICE OF LICENSE
|
|
*
|
|
* This source file is subject to the Academic Free License (AFL 3.0)
|
|
* that is bundled with this package in the file LICENSE.txt.
|
|
* It is also available through the world-wide-web at this URL:
|
|
* http://opensource.org/licenses/afl-3.0.php
|
|
* If you did not receive a copy of the license and are unable to
|
|
* obtain it through the world-wide-web, please send an email
|
|
* to license@prestashop.com so we can send you a copy immediately.
|
|
*
|
|
* DISCLAIMER
|
|
*
|
|
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
|
|
* versions in the future. If you wish to customize PrestaShop for your
|
|
* needs please refer to http://www.prestashop.com for more information.
|
|
*
|
|
* @author Panariga
|
|
* @copyright 2025
|
|
* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
|
|
* International Registered Trademark & Property of PrestaShop SA
|
|
*/
|
|
|
|
if (!defined('_PS_VERSION_')) {
|
|
exit;
|
|
}
|
|
|
|
class AddLivePhoto extends Module
|
|
{
|
|
const TABLE_NAME = 'add_live_photo';
|
|
const IMG_DIR_VAR_PATH = _PS_ROOT_DIR_ . '/modules/addlivephoto/photo/';
|
|
const IMG_DIR_MODULE_PATH = _PS_MODULE_DIR_ . 'addlivephoto/photo/'; // Kept for URI generation context
|
|
|
|
public function __construct()
|
|
{
|
|
$this->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();
|
|
}
|
|
|
|
protected function installDb()
|
|
{
|
|
// Added image_type column
|
|
$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,
|
|
`image_type` ENUM("expiry", "packaging") NOT NULL DEFAULT "expiry",
|
|
`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;
|
|
|
|
// Complex Logic:
|
|
// 1. Get 'packaging' photos (Always show, limit to newest 3)
|
|
// 2. Get 'expiry' photos (Show only if newer than 3 months)
|
|
|
|
$sql = "SELECT * FROM `" . _DB_PREFIX_ . self::TABLE_NAME . "`
|
|
WHERE `id_product` = " . $id_product . "
|
|
AND (
|
|
(`image_type` = 'packaging')
|
|
OR
|
|
(`image_type` = 'expiry' AND `date_add` >= DATE_SUB(NOW(), INTERVAL 3 MONTH))
|
|
)
|
|
ORDER BY `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) {
|
|
|
|
// Customize text based on type
|
|
$is_expiry = ($row['image_type'] === 'expiry');
|
|
$date_taken = date('Y-m-d', strtotime($row['date_add']));
|
|
|
|
$alt_text = $is_expiry
|
|
? sprintf($this->trans('Expiry date photo for %s, taken on %s',[], 'Modules.Addlivephoto.Shop'), $this->context->smarty->tpl_vars['product']->value['name'], $date_taken)
|
|
: sprintf($this->trans('Real packaging photo for %s',[], 'Modules.Addlivephoto.Shop'), $this->context->smarty->tpl_vars['product']->value['name']);
|
|
|
|
$live_photos[] = [
|
|
'url' => $image_uri,
|
|
'type' => $row['image_type'], // 'expiry' or 'packaging'
|
|
'date' => $row['date_add'],
|
|
'alt' => $alt_text,
|
|
];
|
|
}
|
|
}
|
|
|
|
if (empty($live_photos)) return;
|
|
|
|
$this->context->smarty->assign([
|
|
'live_photos' => $live_photos,
|
|
]);
|
|
|
|
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;
|
|
}
|
|
} |