From d372f7ac2d51b3bd5b155ea09d822407724fb485 Mon Sep 17 00:00:00 2001 From: O K Date: Fri, 26 Sep 2025 13:15:18 +0300 Subject: [PATCH] added CSV export --- controllers/front/category.php | 49 +++++++-- controllers/front/product.php | 160 +++++++++++++++++----------- views/templates/admin/configure.tpl | 39 +++++-- 3 files changed, 165 insertions(+), 83 deletions(-) diff --git a/controllers/front/category.php b/controllers/front/category.php index e9e336d..072eab7 100644 --- a/controllers/front/category.php +++ b/controllers/front/category.php @@ -1,13 +1,14 @@ id, $id_lang, $id_shop); + $category = new Category((int)$cat->id, $id_lang, $id_shop); + if (!Validate::isLoadedObject($category)) { + continue; + } if (Tools::getValue('plc_only_active') && !$category->active) { continue; } @@ -52,14 +55,40 @@ class ProductLinkCheckerCategoryModuleFrontController extends ModuleFrontControl Tools::getValue('plc_meta_title') ? $data['meta_title'] = $category->meta_title : null; Tools::getValue('plc_meta_description') ? $data['meta_description'] = $category->meta_description : null; - - $categoriesData[] = $data; } } } - $response = new JsonResponse($categoriesData); + + if ($format === 'csv') { + $this->sendCsvResponse($categoriesData, 'categories.csv'); + } else { + $response = new JsonResponse($categoriesData); + $response->send(); + exit; + } + } + + /** + * Encodes data as CSV and sends it as a downloadable file. + */ + private function sendCsvResponse(array $data, $filename) + { + if (empty($data)) { + $response = new Response('', 204); // No Content + $response->send(); + exit; + } + + $csvEncoder = new CsvEncoder(); + $csvContent = $csvEncoder->encode($data, 'csv', [ + CsvEncoder::DELIMITER_KEY => ';', // Semicolon for better Excel compatibility + ]); + + $response = new Response($csvContent); + $response->headers->set('Content-Type', 'text/csv'); + $response->headers->set('Content-Disposition', 'attachment; filename="' . $filename . '"'); $response->send(); exit; } -} +} \ No newline at end of file diff --git a/controllers/front/product.php b/controllers/front/product.php index 7435203..52f933a 100644 --- a/controllers/front/product.php +++ b/controllers/front/product.php @@ -3,11 +3,13 @@ /** * Product Link Checker Product Data Generate Controller * - * This controller generates a JSON feed of all products and their attribute combinations + * This controller generates a JSON or CSV feed of all products and their attribute combinations * with detailed information for external services. */ use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Serializer\Encoder\CsvEncoder; class ProductLinkCheckerProductModuleFrontController extends ModuleFrontController { @@ -18,7 +20,7 @@ class ProductLinkCheckerProductModuleFrontController extends ModuleFrontControll { parent::init(); - // Security check: Validate the token, same as the other controller + // Security check if (!Tools::getValue('token') || Tools::getValue('token') !== Configuration::get('PLC_SECURITY_TOKEN')) { $response = new JsonResponse(['error' => 'Not Authorized'], 403); $response->send(); @@ -33,12 +35,15 @@ class ProductLinkCheckerProductModuleFrontController extends ModuleFrontControll { parent::initContent(); + $format = Tools::getValue('format', 'json'); $productsData = []; $collection = new PrestaShopCollection('Product'); + // This temporary array will help flatten attributes for CSV + $attributes_list = []; + foreach ((array)Tools::getValue('plc_id_shop', Shop::getShops(true, null, true)) as $id_shop) { foreach ((array)Tools::getValue('plc_id_lang', Language::getLanguages(false, false, true)) as $id_lang) { - foreach ($collection as $p) { $product = new Product((int)$p->id, false, $id_lang, $id_shop); @@ -55,80 +60,105 @@ class ProductLinkCheckerProductModuleFrontController extends ModuleFrontControll foreach ($combinations as $combination) { $index = $product->id . '_' . $combination['id_product_attribute']; if (!isset($productsData[$index])) { - $productsData[$index] = [ - 'id_lang' => (int)$id_lang, - 'id_shop' => (int)$id_shop, - 'id_product' => (int)$product->id, - 'id_product_attribute' => (int)$combination['id_product_attribute'], - 'active' => (bool)$product->active, - 'link' => $this->context->link->getProductLink( - $product, - null, - null, - null, - (int)$id_lang, - (int)$id_shop, - (int)$combination['id_product_attribute'], - false - ), - ]; - - // Conditionally add data based on URL flags - Tools::getValue('plc_name') ? $productsData[$index]['name'] = $product->name : null; - Tools::getValue('plc_link_rewrite') ? $productsData[$index]['link_rewrite'] = $product->link_rewrite : null; - Tools::getValue('plc_description') ? $productsData[$index]['description'] = $product->description : null; - Tools::getValue('plc_description_short') ? $productsData[$index]['description_short'] = $product->description_short : null; - Tools::getValue('plc_meta_title') ? $productsData[$index]['meta_title'] = $product->meta_title : null; - Tools::getValue('plc_meta_description') ? $productsData[$index]['meta_description'] = $product->meta_description : null; - Tools::getValue('plc_reference') ? $productsData[$index]['reference'] = $combination['reference'] : null; - Tools::getValue('plc_ean13') ? $productsData[$index]['ean13'] = $combination['ean13'] : null; - Tools::getValue('plc_upc') ? $productsData[$index]['upc'] = $combination['upc'] : null; - Tools::getValue('plc_mpn') ? $productsData[$index]['mpn'] = $combination['mpn'] : null; + $productsData[$index] = $this->getBaseProductData($product, $id_lang, $id_shop, (int)$combination['id_product_attribute']); } - - $productsData[$index]['attributes'][] = [ + // Store attributes for later processing (both JSON and CSV) + $attributes_list[$index][] = [ 'group_name' => $combination['group_name'] ?? null, 'attribute_name' => $combination['attribute_name'] ?? null, ]; } } else { // Handle simple products (without combinations) $index = $product->id . '_' . 0; - $productsData[$index] = [ - 'id_lang' => (int)$id_lang, - 'id_shop' => (int)$id_shop, - 'id_product' => (int)$product->id, - 'id_product_attribute' => 0, - 'active' => (bool)$product->active, - 'link' => $this->context->link->getProductLink( - $product, - null, - null, - null, - (int)$id_lang, - (int)$id_shop, - 0, - false - ), - ]; - - // Conditionally add data based on URL flags - Tools::getValue('plc_name') ? $productsData[$index]['name'] = $product->name : null; - Tools::getValue('plc_link_rewrite') ? $productsData[$index]['link_rewrite'] = $product->link_rewrite : null; - Tools::getValue('plc_description') ? $productsData[$index]['description'] = $product->description : null; - Tools::getValue('plc_description_short') ? $productsData[$index]['description_short'] = $product->description_short : null; - Tools::getValue('plc_meta_title') ? $productsData[$index]['meta_title'] = $product->meta_title : null; - Tools::getValue('plc_meta_description') ? $productsData[$index]['meta_description'] = $product->meta_description : null; - Tools::getValue('plc_reference') ? $productsData[$index]['reference'] = $product->reference : null; - Tools::getValue('plc_ean13') ? $productsData[$index]['ean13'] = $product->ean13 : null; - Tools::getValue('plc_upc') ? $productsData[$index]['upc'] = $product->upc : null; - Tools::getValue('plc_mpn') ? $productsData[$index]['mpn'] = $product->mpn : null; + $productsData[$index] = $this->getBaseProductData($product, $id_lang, $id_shop, 0); } } } } - $response = new JsonResponse($productsData); + // Post-process the data based on the requested format + foreach ($productsData as $index => &$data) { + if (isset($attributes_list[$index])) { + if ($format === 'csv') { + // Flatten attributes into a single string for CSV + $flat_attributes = []; + foreach ($attributes_list[$index] as $attr) { + $flat_attributes[] = $attr['group_name'] . ':' . $attr['attribute_name']; + } + $data['attributes'] = implode(' | ', $flat_attributes); + } else { + // Keep the structured array for JSON + $data['attributes'] = $attributes_list[$index]; + } + } elseif ($format === 'csv') { + // Ensure the attributes column exists for simple products in CSV + $data['attributes'] = ''; + } + } + unset($data); + + + if ($format === 'csv') { + $this->sendCsvResponse(array_values($productsData), 'products.csv'); + } else { + $response = new JsonResponse($productsData); + $response->send(); + exit; + } + } + + /** + * Helper to get common product data fields. + */ + private function getBaseProductData(Product $product, $id_lang, $id_shop, $id_product_attribute) + { + $combination = new Combination($id_product_attribute); + $data = [ + 'id_lang' => (int)$id_lang, + 'id_shop' => (int)$id_shop, + 'id_product' => (int)$product->id, + 'id_product_attribute' => $id_product_attribute, + 'active' => (bool)$product->active, + 'link' => $this->context->link->getProductLink($product, null, null, null, (int)$id_lang, (int)$id_shop, $id_product_attribute, false), + ]; + + // Conditionally add data based on URL flags + Tools::getValue('plc_name') ? $data['name'] = $product->name : null; + Tools::getValue('plc_link_rewrite') ? $data['link_rewrite'] = $product->link_rewrite : null; + Tools::getValue('plc_description') ? $data['description'] = $product->description : null; + Tools::getValue('plc_description_short') ? $data['description_short'] = $product->description_short : null; + Tools::getValue('plc_meta_title') ? $data['meta_title'] = $product->meta_title : null; + Tools::getValue('plc_meta_description') ? $data['meta_description'] = $product->meta_description : null; + + // Use combination specific data if it exists, otherwise fallback to product + Tools::getValue('plc_reference') ? $data['reference'] = ($id_product_attribute && !empty($combination->reference)) ? $combination->reference : $product->reference : null; + Tools::getValue('plc_ean13') ? $data['ean13'] = ($id_product_attribute && !empty($combination->ean13)) ? $combination->ean13 : $product->ean13 : null; + Tools::getValue('plc_upc') ? $data['upc'] = ($id_product_attribute && !empty($combination->upc)) ? $combination->upc : $product->upc : null; + Tools::getValue('plc_mpn') ? $data['mpn'] = ($id_product_attribute && !empty($combination->mpn)) ? $combination->mpn : $product->mpn : null; + + return $data; + } + + /** + * Encodes data as CSV and sends it as a downloadable file. + */ + private function sendCsvResponse(array $data, $filename) + { + if (empty($data)) { + $response = new Response('', 204); // No Content + $response->send(); + exit; + } + + $csvEncoder = new CsvEncoder(); + $csvContent = $csvEncoder->encode($data, 'csv', [ + CsvEncoder::DELIMITER_KEY => ';', // Semicolon for better Excel compatibility + ]); + + $response = new Response($csvContent); + $response->headers->set('Content-Type', 'text/csv'); + $response->headers->set('Content-Disposition', 'attachment; filename="' . $filename . '"'); $response->send(); exit; } -} +} \ No newline at end of file diff --git a/views/templates/admin/configure.tpl b/views/templates/admin/configure.tpl index a9752e9..b727f3d 100644 --- a/views/templates/admin/configure.tpl +++ b/views/templates/admin/configure.tpl @@ -1,5 +1,6 @@ {* views/templates/admin/configure.tpl *}
+
{l s='Product Link Checker Configuration' mod='productlinkchecker'}
@@ -62,6 +63,20 @@
+ {* --- NEW FORMAT SELECTOR --- *} +
+ +
+ + + + + + + +
+
+

{l s='2. Select Data Fields to Include' mod='productlinkchecker'}

@@ -135,9 +150,23 @@
+ {* --- NEW FORMAT SELECTOR --- *} +
+ +
+ + + + + + + +
+
+

{l s='2. Select Data Fields to Include' mod='productlinkchecker'}

-
+
{foreach from=$category_fields key=key item=label}
@@ -174,14 +203,12 @@ \ No newline at end of file