added CSV export

This commit is contained in:
O K
2025-09-26 13:15:18 +03:00
parent 2b14391ea1
commit d372f7ac2d
3 changed files with 165 additions and 83 deletions

View File

@@ -1,13 +1,14 @@
<?php <?php
/** /**
* Product Link Checker Product Data Generate Controller * Product Link Checker Category 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 categories.
* with detailed information for external services.
*/ */
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Serializer\Encoder\CsvEncoder;
class ProductLinkCheckerCategoryModuleFrontController extends ModuleFrontController class ProductLinkCheckerCategoryModuleFrontController extends ModuleFrontController
{ {
@@ -26,15 +27,17 @@ class ProductLinkCheckerCategoryModuleFrontController extends ModuleFrontControl
{ {
parent::initContent(); parent::initContent();
$format = Tools::getValue('format', 'json');
$categoriesData = []; $categoriesData = [];
$collection = new PrestaShopCollection('Category'); $collection = new PrestaShopCollection('Category');
foreach ((array)Tools::getValue('plc_id_shop', Shop::getShops(true, null, true)) as $id_shop) { 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 ((array)Tools::getValue('plc_id_lang', Language::getLanguages(false, false, true)) as $id_lang) {
foreach ($collection as $cat) { foreach ($collection as $cat) {
$category = new Category((int)$cat->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) { if (Tools::getValue('plc_only_active') && !$category->active) {
continue; 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_title') ? $data['meta_title'] = $category->meta_title : null;
Tools::getValue('plc_meta_description') ? $data['meta_description'] = $category->meta_description : null; Tools::getValue('plc_meta_description') ? $data['meta_description'] = $category->meta_description : null;
$categoriesData[] = $data; $categoriesData[] = $data;
} }
} }
} }
if ($format === 'csv') {
$this->sendCsvResponse($categoriesData, 'categories.csv');
} else {
$response = new JsonResponse($categoriesData); $response = new JsonResponse($categoriesData);
$response->send(); $response->send();
exit; 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;
}
} }

View File

@@ -3,11 +3,13 @@
/** /**
* Product Link Checker Product Data Generate Controller * 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. * with detailed information for external services.
*/ */
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Serializer\Encoder\CsvEncoder;
class ProductLinkCheckerProductModuleFrontController extends ModuleFrontController class ProductLinkCheckerProductModuleFrontController extends ModuleFrontController
{ {
@@ -18,7 +20,7 @@ class ProductLinkCheckerProductModuleFrontController extends ModuleFrontControll
{ {
parent::init(); 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')) { if (!Tools::getValue('token') || Tools::getValue('token') !== Configuration::get('PLC_SECURITY_TOKEN')) {
$response = new JsonResponse(['error' => 'Not Authorized'], 403); $response = new JsonResponse(['error' => 'Not Authorized'], 403);
$response->send(); $response->send();
@@ -33,12 +35,15 @@ class ProductLinkCheckerProductModuleFrontController extends ModuleFrontControll
{ {
parent::initContent(); parent::initContent();
$format = Tools::getValue('format', 'json');
$productsData = []; $productsData = [];
$collection = new PrestaShopCollection('Product'); $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_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 ((array)Tools::getValue('plc_id_lang', Language::getLanguages(false, false, true)) as $id_lang) {
foreach ($collection as $p) { foreach ($collection as $p) {
$product = new Product((int)$p->id, false, $id_lang, $id_shop); $product = new Product((int)$p->id, false, $id_lang, $id_shop);
@@ -55,80 +60,105 @@ class ProductLinkCheckerProductModuleFrontController extends ModuleFrontControll
foreach ($combinations as $combination) { foreach ($combinations as $combination) {
$index = $product->id . '_' . $combination['id_product_attribute']; $index = $product->id . '_' . $combination['id_product_attribute'];
if (!isset($productsData[$index])) { if (!isset($productsData[$index])) {
$productsData[$index] = [ $productsData[$index] = $this->getBaseProductData($product, $id_lang, $id_shop, (int)$combination['id_product_attribute']);
'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;
} }
// Store attributes for later processing (both JSON and CSV)
$productsData[$index]['attributes'][] = [ $attributes_list[$index][] = [
'group_name' => $combination['group_name'] ?? null, 'group_name' => $combination['group_name'] ?? null,
'attribute_name' => $combination['attribute_name'] ?? null, 'attribute_name' => $combination['attribute_name'] ?? null,
]; ];
} }
} else { // Handle simple products (without combinations) } else { // Handle simple products (without combinations)
$index = $product->id . '_' . 0; $index = $product->id . '_' . 0;
$productsData[$index] = [ $productsData[$index] = $this->getBaseProductData($product, $id_lang, $id_shop, 0);
'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;
} }
} }
} }
} }
// 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 = new JsonResponse($productsData);
$response->send(); $response->send();
exit; 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;
}
} }

View File

@@ -1,5 +1,6 @@
{* views/templates/admin/configure.tpl *} {* views/templates/admin/configure.tpl *}
<div class="panel"> <div class="panel">
<div class="panel-heading"> <div class="panel-heading">
<i class="icon-link"></i> {l s='Product Link Checker Configuration' mod='productlinkchecker'} <i class="icon-link"></i> {l s='Product Link Checker Configuration' mod='productlinkchecker'}
</div> </div>
@@ -62,6 +63,20 @@
</div> </div>
</div> </div>
{* --- NEW FORMAT SELECTOR --- *}
<div class="form-group">
<label class="col-lg-3 control-label">{l s='Output Format' mod='productlinkchecker'}</label>
<div class="col-lg-9">
<span class="switch prestashop-switch fixed-width-lg">
<input type="radio" name="format" id="format_prod_json" value="json" class="plc-builder-input" data-type="product" checked="checked">
<label for="format_prod_json">JSON</label>
<input type="radio" name="format" id="format_prod_csv" value="csv" class="plc-builder-input" data-type="product">
<label for="format_prod_csv">CSV</label>
<a class="slide-button btn"></a>
</span>
</div>
</div>
<hr> <hr>
<h4>{l s='2. Select Data Fields to Include' mod='productlinkchecker'}</h4> <h4>{l s='2. Select Data Fields to Include' mod='productlinkchecker'}</h4>
<div class="form-group"> <div class="form-group">
@@ -135,6 +150,20 @@
</div> </div>
</div> </div>
{* --- NEW FORMAT SELECTOR --- *}
<div class="form-group">
<label class="col-lg-3 control-label">{l s='Output Format' mod='productlinkchecker'}</label>
<div class="col-lg-9">
<span class="switch prestashop-switch fixed-width-lg">
<input type="radio" name="format" id="format_cat_json" value="json" class="plc-builder-input" data-type="category" checked="checked">
<label for="format_cat_json">JSON</label>
<input type="radio" name="format" id="format_cat_csv" value="csv" class="plc-builder-input" data-type="category">
<label for="format_cat_csv">CSV</label>
<a class="slide-button btn"></a>
</span>
</div>
</div>
<hr> <hr>
<h4>{l s='2. Select Data Fields to Include' mod='productlinkchecker'}</h4> <h4>{l s='2. Select Data Fields to Include' mod='productlinkchecker'}</h4>
<div class="form-group"> <div class="form-group">
@@ -174,14 +203,12 @@
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// Base URLs and token from Smarty
const baseUrls = { const baseUrls = {
product: '{$product_controller_url|escape:'javascript':'UTF-8'}', product: '{$product_controller_url|escape:'javascript':'UTF-8'}',
category: '{$category_controller_url|escape:'javascript':'UTF-8'}' category: '{$category_controller_url|escape:'javascript':'UTF-8'}'
}; };
const token = '{$security_token|escape:'javascript':'UTF-8'}'; const token = '{$security_token|escape:'javascript':'UTF-8'}';
// Function to build the URL based on selected options
const buildUrl = (type) => { const buildUrl = (type) => {
let baseUrl = baseUrls[type]; let baseUrl = baseUrls[type];
let params = new URLSearchParams(); let params = new URLSearchParams();
@@ -195,7 +222,7 @@ document.addEventListener('DOMContentLoaded', function() {
if (input.checked && input.value) { if (input.checked && input.value) {
params.append(input.name, input.value); params.append(input.name, input.value);
} }
} else { // select } else {
if (input.value) { if (input.value) {
params.append(input.name, input.value); params.append(input.name, input.value);
} }
@@ -208,24 +235,20 @@ document.addEventListener('DOMContentLoaded', function() {
document.getElementById('open-url-' + type).href = finalUrl; document.getElementById('open-url-' + type).href = finalUrl;
}; };
// Attach event listeners to all inputs
document.querySelectorAll('.plc-builder-input').forEach(input => { document.querySelectorAll('.plc-builder-input').forEach(input => {
input.addEventListener('change', (event) => { input.addEventListener('change', (event) => {
buildUrl(event.target.dataset.type); buildUrl(event.target.dataset.type);
}); });
}); });
// Initial build for both tabs on page load
buildUrl('product'); buildUrl('product');
buildUrl('category'); buildUrl('category');
}); });
// Simple copy to clipboard function
function copyToClipboard(type) { function copyToClipboard(type) {
const urlInput = document.getElementById('generated-url-' + type); const urlInput = document.getElementById('generated-url-' + type);
urlInput.select(); urlInput.select();
document.execCommand('copy'); document.execCommand('copy');
// Optional: show a small feedback message
showSuccessMessage('{l s="URL copied to clipboard!" mod='productlinkchecker' js=1}'); showSuccessMessage('{l s="URL copied to clipboard!" mod='productlinkchecker' js=1}');
} }
</script> </script>