improve photos

This commit is contained in:
O K
2025-11-25 11:00:26 +02:00
parent ff2dcdc0ee
commit 8e7141175a
6 changed files with 1004 additions and 719 deletions

View File

@@ -1,30 +1,6 @@
<?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 Your Name <your@email.com>
* @copyright 2007-2023 PrestaShop SA
* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*
* @property \AddLivePhoto $module
* Admin Controller for AddLivePhoto Module
*/
class AdminAddLivePhotoController extends ModuleAdminController
@@ -32,205 +8,211 @@ class AdminAddLivePhotoController extends ModuleAdminController
public function __construct()
{
$this->bootstrap = true;
// The table is not for a list view, but it's good practice to set it.
$this->table = 'product';
$this->display = 'view'; // Force custom view
parent::__construct();
}
/**
* This is the entry point for the controller page.
* It sets up the main template.
*/
public function initContent()
{
parent::initContent();
// Pass the ajax URL to the template
$ajax_url = $this->context->link->getAdminLink(
'AdminAddLivePhoto',
true, // Keep the token
[], // No route params
['ajax' => 1] // Add ajax=1 to the query string
);
// Не викликаємо parent::initContent(), бо нам не потрібен стандартний список
// Але нам потрібен header і footer адмінки
$this->context->smarty->assign([
'ajax_url' => $ajax_url,
'content' => $this->renderView(), // Це вставить наш tpl
]);
// We use a custom template for our camera interface.
$this->setTemplate('uploader.tpl');
// Викликаємо батьківський метод для відображення структури адмінки
parent::initContent();
}
public function renderView()
{
$ajax_url = $this->context->link->getAdminLink(
'AdminAddLivePhoto',
true,
[],
['ajax' => 1]
);
$this->context->smarty->assign([
'ajax_url' => $ajax_url,
'module_dir' => _MODULE_DIR_ . $this->module->name . '/',
]);
return $this->context->smarty->fetch($this->module->getLocalPath() . 'views/templates/admin/uploader.tpl');
}
/**
* This method is automatically called by PrestaShop when an AJAX request is made to this controller.
* We use a 'action' parameter to decide what to do.
*/
public function ajaxProcess()
{
$action = Tools::getValue('action');
switch ($action) {
case 'searchProduct':
$this->ajaxProcessSearchProduct();
break;
case 'uploadImage':
$this->ajaxProcessUploadImage();
break;
case 'deleteImage':
$this->ajaxProcessDeleteImage();
break;
try {
switch ($action) {
case 'searchProduct':
$this->processSearchProduct();
break;
case 'uploadImage':
$this->processUploadImage();
break;
case 'deleteImage':
$this->processDeleteFreshImage();
break;
default:
throw new Exception('Unknown action');
}
} catch (Exception $e) {
$this->jsonResponse(['success' => false, 'message' => $e->getMessage()]);
}
// No further processing needed for AJAX
exit;
}
/**
* Handles searching for a product by EAN13 barcode or ID.
*/
protected function ajaxProcessSearchProduct()
protected function processSearchProduct()
{
$identifier = Tools::getValue('identifier');
$identifier = trim(Tools::getValue('identifier'));
if (empty($identifier)) {
$this->jsonError($this->trans('Identifier cannot be empty.',[], 'Modules.Addlivephoto.Admin'));
throw new Exception($this->trans('Please enter a barcode or ID.', [], 'Modules.Addlivephoto.Admin'));
}
$id_product = 0;
if (is_numeric($identifier)) {
// Check if it's an EAN or a Product ID
$id_product_by_ean = (int)Db::getInstance()->getValue('
SELECT id_product FROM `' . _DB_PREFIX_ . 'product` WHERE ean13 = \'' . pSQL($identifier) . '\'
');
if ($id_product_by_ean) {
$id_product = $id_product_by_ean;
} else {
// Assume it's a product ID if not found by EAN
$id_product = (int)$identifier;
}
// 1. Спробуємо знайти по EAN13
$sql = 'SELECT id_product FROM `' . _DB_PREFIX_ . 'product` WHERE ean13 = "'.pSQL($identifier).'"';
$id_by_ean = Db::getInstance()->getValue($sql);
if ($id_by_ean) {
$id_product = (int)$id_by_ean;
} elseif (is_numeric($identifier)) {
// 2. Якщо це число, пробуємо як ID
$id_product = (int)$identifier;
}
if (!$id_product || !Validate::isLoadedObject($product = new Product($id_product, false, $this->context->language->id))) {
$this->jsonError($this->trans('Product not found.',[], 'Modules.Addlivephoto.Admin'));
$product = new Product($id_product, false, $this->context->language->id);
if (!Validate::isLoadedObject($product)) {
throw new Exception($this->trans('Product not found.', [], 'Modules.Addlivephoto.Admin'));
}
// Get product prices
$retail_price = Product::getPriceStatic($product->id, true, null, 2, null, false, true);
$discounted_price = Product::getPriceStatic($product->id, true, null, 2, null, true, true);
// Отримуємо існуючі фото
$existing_photos = $this->getLivePhotos($product->id);
// Fetch existing live photos for this product
$live_photos = $this->getLivePhotosForProduct($product->id);
$response = [
'id_product' => $product->id,
'name' => $product->name,
'wholesale_price' => $product->wholesale_price,
'retail_price' => $retail_price,
'discounted_price' => ($retail_price !== $discounted_price) ? $discounted_price : null,
'existing_photos' => $live_photos,
];
$this->jsonSuccess($response);
$this->jsonResponse([
'success' => true,
'data' => [
'id_product' => $product->id,
'name' => $product->name,
'reference' => $product->reference,
'ean13' => $product->ean13,
'existing_photos' => $existing_photos
]
]);
}
/**
* Handles the image upload process.
*/
protected function ajaxProcessUploadImage()
protected function processUploadImage()
{
$id_product = (int)Tools::getValue('id_product');
$imageData = Tools::getValue('imageData');
$rawImage = Tools::getValue('imageData'); // base64 string
$imageType = Tools::getValue('image_type'); // expiry або packaging
if (!$id_product || !$imageData) {
$this->jsonError($this->trans('Missing product ID or image data.',[], 'Modules.Addlivephoto.Admin'));
if (!$id_product || !$rawImage) {
throw new Exception('Missing ID or Image Data');
}
// Remove the data URI scheme header
list($type, $imageData) = explode(';', $imageData);
list(, $imageData) = explode(',', $imageData);
$imageData = base64_decode($imageData);
if ($imageData === false) {
$this->jsonError($this->trans('Invalid image data.',[], 'Modules.Addlivephoto.Admin'));
if (!in_array($imageType, ['expiry', 'packaging'])) {
$imageType = 'expiry'; // Fallback
}
$image_name = uniqid() . '.webp';
// Clean Base64
if (preg_match('/^data:image\/(\w+);base64,/', $rawImage, $type)) {
$rawImage = substr($rawImage, strpos($rawImage, ',') + 1);
$type = strtolower($type[1]); // jpg, png, webp
if (!in_array($type, ['jpg', 'jpeg', 'png', 'webp'])) {
throw new Exception('Invalid image type');
}
$rawImage = base64_decode($rawImage);
if ($rawImage === false) {
throw new Exception('Base64 decode failed');
}
} else {
throw new Exception('Did not match data URI with image data');
}
// Generate Filename
$filename = uniqid() . '.webp'; // Save as WebP usually
$path = $this->module->getProductImageServerPath($id_product);
if (!$path || !file_put_contents($path . $image_name, $imageData)) {
$this->jsonError($this->trans('Could not save image file. Check permissions for /var/modules/addlivephoto/',[], 'Modules.Addlivephoto.Admin'));
if (!$path) {
throw new Exception('Could not create directory');
}
// Save to database
$success = Db::getInstance()->insert(AddLivePhoto::TABLE_NAME, [
// Save File
if (!file_put_contents($path . $filename, $rawImage)) {
throw new Exception('Failed to write file to disk');
}
// Save to DB
$res = Db::getInstance()->insert('add_live_photo', [
'id_product' => $id_product,
'image_name' => pSQL($image_name),
'image_name' => pSQL($filename),
'image_type' => pSQL($imageType),
'date_add' => date('Y-m-d H:i:s'),
]);
if (!$success) {
// Clean up the created file if DB insert fails
@unlink($path . $image_name);
$this->jsonError($this->trans('Could not save image information to the database.',[], 'Modules.Addlivephoto.Admin'));
if (!$res) {
@unlink($path . $filename); // Cleanup
throw new Exception('Database insert error');
}
$new_photo_data = [
'name' => $image_name,
'url' => $this->module->getProductImageUri($id_product, $image_name),
'full_url' => $this->module->getProductImageUri($id_product, $image_name),
];
$photoUrl = $this->module->getProductImageUri($id_product, $filename);
$this->jsonSuccess(['message' => $this->trans('Image uploaded successfully!',[], 'Modules.Addlivephoto.Admin'), 'new_photo' => $new_photo_data]);
$this->jsonResponse([
'success' => true,
'message' => 'Saved successfully!',
'photo' => [
'name' => $filename,
'url' => $photoUrl,
'type' => $imageType
]
]);
}
/**
* Handles deleting a specific image.
*/
protected function ajaxProcessDeleteImage()
protected function processDeleteFreshImage()
{
$id_product = (int)Tools::getValue('id_product');
$image_name = Tools::getValue('image_name');
if (!$id_product || !$image_name) {
$this->jsonError($this->trans('Missing product ID or image name.',[], 'Modules.Addlivephoto.Admin'));
}
// Use the method from the main module class
if ($this->module->deleteProductImage($id_product, $image_name)) {
$this->jsonSuccess(['message' => $this->trans('Image deleted successfully.')]);
$this->jsonResponse(['success' => true, 'message' => 'Deleted']);
} else {
$this->jsonError($this->trans('Failed to delete image.',[], 'Modules.Addlivephoto.Admin'));
throw new Exception('Delete failed');
}
}
/**
* Fetches all live photos for a given product ID.
* @param int $id_product
* @return array
*/
private function getLivePhotosForProduct($id_product)
private function getLivePhotos($id_product)
{
$sql = new DbQuery();
$sql->select('`image_name`');
$sql->from(AddLivePhoto::TABLE_NAME);
$sql->where('`id_product` = ' . (int)$id_product);
$sql->orderBy('`date_add` DESC');
$results = Db::getInstance()->executeS($sql);
$sql->select('*');
$sql->from('add_live_photo');
$sql->where('id_product = ' . (int)$id_product);
$sql->orderBy('date_add DESC');
$res = Db::getInstance()->executeS($sql);
$photos = [];
if ($results) {
foreach ($results as $row) {
if ($res) {
foreach($res as $row) {
$photos[] = [
'name' => $row['image_name'],
'url' => $this->module->getProductImageUri($id_product, $row['image_name']),
'type' => isset($row['image_type']) ? $row['image_type'] : 'expiry',
'url' => $this->module->getProductImageUri($id_product, $row['image_name'])
];
}
}
return $photos;
}
/** Helper functions for consistent JSON responses */
private function jsonSuccess($data)
private function jsonResponse($data)
{
header('Content-Type: application/json');
echo json_encode(['success' => true, 'data' => $data]);
echo json_encode($data);
exit;
}
}
}