diff --git a/controllers/front/download.php b/controllers/front/download.php new file mode 100644 index 0000000..17ed5fb --- /dev/null +++ b/controllers/front/download.php @@ -0,0 +1,95 @@ +errors[] = $this->module->l('File ID is missing', 'download'); + $this->redirectWithNotifications($this->context->link->getPageLink('404')); + return; + } + + try { + $productFile = Db::getInstance()->executeS(" + SELECT file, title + FROM " . _DB_PREFIX_ . "product_file + WHERE id = " . $idFile . " AND id_lang = " . $this->context->language->id . " AND id_shop = " . $this->context->shop->id + ); + + Db::getInstance()->execute(" + UPDATE " . _DB_PREFIX_ . "product_file + SET nb_download = nb_download + 1 + WHERE id = " . $idFile . " AND id_lang = " . $this->context->language->id . " AND id_shop = " . $this->context->shop->id + ); + + $this->downloadFile($productFile[0]); + } catch (\Exception $e) { + PrestaShopLogger::addLog( + 'Error downloading file: ' . $e->getMessage(), + 3, + null, + 'ProductFile', + $idFile + ); + Tools::redirect('index.php?controller=404'); + } + } + + protected function downloadFile(array $productFile) + { + $filePath = Pixel_product_files::FILE_BASE_DIR . $productFile['file']; + + if (!file_exists($filePath)) { + $this->errors[] = $this->module->l('File does not exist on server', 'download'); + $this->redirectWithNotifications($this->context->link->getPageLink('404')); + return; + } + + // Déterminer le type MIME + $finfo = finfo_open(FILEINFO_MIME_TYPE); + $mimeType = finfo_file($finfo, $filePath); + finfo_close($finfo); + + // Obtenir le nom du fichier + $fileName = $productFile['title'] + ? $this->sanitizeFileName($productFile['title']) + : basename($productFile['file']); + + // Ajouter l'extension si nécessaire + $extension = pathinfo($productFile['file'], PATHINFO_EXTENSION); + if (!preg_match('/\.' . preg_quote($extension, '/') . '$/', $fileName)) { + $fileName .= '.' . $extension; + } + + // Headers pour le téléchargement + header('Content-Type: ' . $mimeType); + header('Content-Disposition: attachment; filename="' . $fileName . '"'); + header('Content-Length: ' . filesize($filePath)); + header('Cache-Control: private, max-age=0, must-revalidate'); + header('Pragma: public'); + + // Nettoyage du buffer de sortie + if (ob_get_level()) { + ob_end_clean(); + } + + // Lecture et envoi du fichier + readfile($filePath); + exit; + } + + protected function sanitizeFileName($fileName) + { + // Remplacer les caractères spéciaux + $fileName = str_replace(['"', "'", '/', '\\', '?', '*', ':', '|', '<', '>'], '-', $fileName); + // Supprimer les espaces multiples + $fileName = preg_replace('/\s+/', '_', $fileName); + + return trim($fileName); + } +} diff --git a/pixel_product_files.php b/pixel_product_files.php index 7f4e4e6..bf30a52 100644 --- a/pixel_product_files.php +++ b/pixel_product_files.php @@ -74,7 +74,7 @@ class Pixel_product_files extends Module implements WidgetInterface public function __construct() { $this->name = 'pixel_product_files'; - $this->version = '1.3.1'; + $this->version = '1.4.0'; $this->author = 'Pixel Open'; $this->tab = 'content_management'; $this->need_instance = 0; @@ -117,7 +117,8 @@ public function install(): bool $this->registerHook('displayBackOfficeHeader') && $this->registerHook('actionAdminProductsControllerSaveBefore') && $this->registerHook('actionBeforeUpdateProductFormHandler') && - $this->registerHook('actionFrontControllerSetMedia'); + $this->registerHook('actionFrontControllerSetMedia') && + $this->registerHook("displayAdminStatsModules"); } /** @@ -380,6 +381,52 @@ protected function saveProductFileData($files): void } } + public function hookDisplayAdminStatsModules() + { + $html = ""; + + $productFiles = Db::getInstance()->executeS(" + SELECT pf.id, pf.title, pl.name, pf.nb_download + FROM " . _DB_PREFIX_ . "product_file AS pf + INNER JOIN " . _DB_PREFIX_ . "product_lang AS pl ON (pf.id_product = pl.id_product AND pl.id_lang = pf.id_lang) + ORDER BY pf.nb_download DESC + "); + + if (count($productFiles) > 0) { + $html = ' + +
| ' . $this->trans("Id", [], "Modules.PixelproductFiles.Admin") . ' | +' . $this->trans("File name", [], "Modules.PixelproductFiles.Admin") . ' | +' . $this->trans("Product", [], "Modules.PixelproductFiles.Admin") . ' | +' . $this->trans("Number of downloads", [], "Modules.PixelproductFiles.Admin") . ' | +
|---|---|---|---|
| ' . $productFile['id'] . ' | +' . $productFile['title'] . ' | +' . $productFile['name'] . ' | +' . $productFile['nb_download'] . ' | +
" . $this->trans("There are no files associated with product", [], "Modules.Pixelproductfiles.Admin") . "
"; + } + + return $html; + } + /**************/ /** USEFULLY **/ /**************/ diff --git a/src/Entity/ProductFile.php b/src/Entity/ProductFile.php index 892d756..86a65af 100644 --- a/src/Entity/ProductFile.php +++ b/src/Entity/ProductFile.php @@ -70,6 +70,13 @@ class ProductFile */ private $position; + /** + * @var int + * + * @ORM\Column(name="nb_download", type="integer", nullable=false) + */ + private $nbDownload; + /** * @return int */ @@ -230,6 +237,16 @@ public function setPosition(?int $position): ProductFile return $this; } + public function getNbDownload(): int + { + return $this->nbDownload; + } + + public function setNbDownload(int $nbDownload): void + { + $this->nbDownload = $nbDownload; + } + /** * @return mixed[] */ diff --git a/upgrade/upgrade-1.4.0.php b/upgrade/upgrade-1.4.0.php new file mode 100644 index 0000000..ed5fc7c --- /dev/null +++ b/upgrade/upgrade-1.4.0.php @@ -0,0 +1,9 @@ +execute(' + ALTER TABLE `' . _DB_PREFIX_ . 'product_file` + ADD `nb_download` INT(10) NULL DEFAULT 0') + ) && $module->registerHook("displayAdminStatsModules"); +} diff --git a/views/templates/admin/files.html.twig b/views/templates/admin/files.html.twig index 1167d7e..4e30ae8 100644 --- a/views/templates/admin/files.html.twig +++ b/views/templates/admin/files.html.twig @@ -47,6 +47,10 @@ +