Files
addlivephoto/addlivephoto.php
2025-11-25 11:00:26 +02:00

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;
}
}