From 6bc3ed214bcf30ee4bf9053f71310211a898bb65 Mon Sep 17 00:00:00 2001 From: Benjamin Perche Date: Fri, 11 Jul 2014 09:56:06 +0200 Subject: [PATCH] =?UTF-8?q?Finish=20Import=20/=20Export=20categories=20man?= =?UTF-8?q?agement=20=09modifi=C3=A9:=20=20=20=20=20=20=20=20=20core/lib/T?= =?UTF-8?q?helia/Config/Resources/routing/admin.xml=20=09modifi=C3=A9:=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20core/lib/Thelia/Controller/Admin/Export?= =?UTF-8?q?Controller.php=20=09modifi=C3=A9:=20=20=20=20=20=20=20=20=20cor?= =?UTF-8?q?e/lib/Thelia/Controller/Admin/ImportExportController.php=20=09m?= =?UTF-8?q?odifi=C3=A9:=20=20=20=20=20=20=20=20=20core/lib/Thelia/Core/Fil?= =?UTF-8?q?eFormat/Formatting/Formatter/JsonFormatter.php=20=09modifi?= =?UTF-8?q?=C3=A9:=20=20=20=20=20=20=20=20=20core/lib/Thelia/Core/FileForm?= =?UTF-8?q?at/Formatting/Formatter/XMLFormatter.php=20=09modifi=C3=A9:=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20core/lib/Thelia/Core/FileFormat/Formatt?= =?UTF-8?q?ing/FormatterInterface.php=20=09modifi=C3=A9:=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20core/lib/Thelia/Core/Template/Loop/Export.php=20=09mod?= =?UTF-8?q?ifi=C3=A9:=20=20=20=20=20=20=20=20=20core/lib/Thelia/Core/Templ?= =?UTF-8?q?ate/Loop/Formatter.php=20=09nouveau=20fichier:=20core/lib/Theli?= =?UTF-8?q?a/ImportExport/Both/NewsletterImportExport.php=20=09nouveau=20f?= =?UTF-8?q?ichier:=20core/lib/Thelia/ImportExport/Export/ExportType.php=20?= =?UTF-8?q?=09nouveau=20fichier:=20core/lib/Thelia/ImportExport/Export/Mai?= =?UTF-8?q?lingExport.php=20=09modifi=C3=A9:=20=20=20=20=20=20=20=20=20cor?= =?UTF-8?q?e/lib/Thelia/ImportExport/ExportHandlerInterface.php=20=09modif?= =?UTF-8?q?i=C3=A9:=20=20=20=20=20=20=20=20=20core/lib/Thelia/Model/Export?= =?UTF-8?q?.php=20=09modifi=C3=A9:=20=20=20=20=20=20=20=20=20core/lib/Thel?= =?UTF-8?q?ia/Model/ExportCategory.php=20=09modifi=C3=A9:=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20core/lib/Thelia/Model/ImportCategory.php=20=09modif?= =?UTF-8?q?i=C3=A9:=20=20=20=20=20=20=20=20=20templates/backOffice/default?= =?UTF-8?q?/export-page.html=20=09modifi=C3=A9:=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?templates/backOffice/default/export.html=20=09modifi=C3=A9:=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20templates/backOffice/default/import.htm?= =?UTF-8?q?l=20=09modifi=C3=A9:=20=20=20=20=20=20=20=20=20templates/backOf?= =?UTF-8?q?fice/default/includes/export-form-definition.html?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Thelia/Config/Resources/routing/admin.xml | 14 +- .../Controller/Admin/ExportController.php | 88 ++++++++++-- .../Admin/ImportExportController.php | 26 +++- .../Formatting/Formatter/JsonFormatter.php | 8 ++ .../Formatting/Formatter/XMLFormatter.php | 9 ++ .../Formatting/FormatterInterface.php | 13 ++ core/lib/Thelia/Core/Template/Loop/Export.php | 1 - .../Thelia/Core/Template/Loop/Formatter.php | 25 ++-- .../Both/NewsletterImportExport.php | 58 ++++++++ .../Thelia/ImportExport/Export/ExportType.php | 33 +++++ .../ImportExport/Export/MailingExport.php | 47 +++++++ .../ImportExport/ExportHandlerInterface.php | 17 +++ core/lib/Thelia/Model/Export.php | 5 + core/lib/Thelia/Model/ExportCategory.php | 66 ++++++++- core/lib/Thelia/Model/ImportCategory.php | 63 +++++++++ templates/backOffice/default/export-page.html | 46 ++++++ templates/backOffice/default/export.html | 93 ++++++------- templates/backOffice/default/import.html | 131 +++++++++++------- .../includes/export-form-definition.html | 2 +- 19 files changed, 610 insertions(+), 135 deletions(-) create mode 100644 core/lib/Thelia/ImportExport/Both/NewsletterImportExport.php create mode 100644 core/lib/Thelia/ImportExport/Export/ExportType.php create mode 100644 core/lib/Thelia/ImportExport/Export/MailingExport.php diff --git a/core/lib/Thelia/Config/Resources/routing/admin.xml b/core/lib/Thelia/Config/Resources/routing/admin.xml index 52d4d6ca3..e10e4fbcb 100644 --- a/core/lib/Thelia/Config/Resources/routing/admin.xml +++ b/core/lib/Thelia/Config/Resources/routing/admin.xml @@ -1158,7 +1158,7 @@ Thelia\Controller\Admin\TranslationsController::updateAction - + Thelia\Controller\Admin\ExportController::indexAction @@ -1176,6 +1176,18 @@ \d+ + + Thelia\Controller\Admin\ExportController::changeCategoryPosition + up|down + \d+ + + + + Thelia\Controller\Admin\ExportController::updateCategoryPosition + \d+ + \d+ + + Thelia\Controller\Admin\ImportExportController::export \d+ diff --git a/core/lib/Thelia/Controller/Admin/ExportController.php b/core/lib/Thelia/Controller/Admin/ExportController.php index 69832b9e8..8f20d61a7 100644 --- a/core/lib/Thelia/Controller/Admin/ExportController.php +++ b/core/lib/Thelia/Controller/Admin/ExportController.php @@ -16,6 +16,7 @@ use Thelia\Core\Security\AccessManager; use Thelia\Core\Security\Resource\AdminResources; use Thelia\Core\Template\Loop\ImportExportType; use Thelia\Core\Translation\Translator; +use Thelia\Model\ExportCategoryQuery; use Thelia\Model\ExportQuery; /** @@ -31,15 +32,7 @@ class ExportController extends BaseAdminController return $response; } - $export_order = $this->getRequest()->query->get("export_order"); - - if (!in_array($export_order, ImportExportType::getAllowedOrders())) { - $export_order = ImportExportType::DEFAULT_ORDER; - } - - $this->getParserContext() - ->set("export_order", $export_order) - ; + $this->setOrders(); return $this->render('export'); } @@ -58,9 +51,7 @@ class ExportController extends BaseAdminController $export->downPosition(); } - $this->getParserContext() - ->set("export_order", "manual") - ; + $this->setOrders(null, "manual"); return $this->render('export'); } @@ -75,13 +66,63 @@ class ExportController extends BaseAdminController $export->updatePosition($value); - $this->getParserContext() - ->set("export_order", "manual") - ; + $this->setOrders(null, "manual"); return $this->render('export'); } + public function changeCategoryPosition($action, $id) + { + if (null !== $response = $this->checkAuth([AdminResources::EXPORT], [], [AccessManager::UPDATE])) { + return $response; + } + + $category = $this->getCategory($id); + + if ($action === "up") { + $category->upPosition(); + } elseif ($action === "down") { + $category->downPosition(); + } + + $this->setOrders("manual"); + + return $this->render('export'); + } + + public function updateCategoryPosition($id, $value) + { + if (null !== $response = $this->checkAuth([AdminResources::EXPORT], [], [AccessManager::UPDATE])) { + return $response; + } + + $category = $this->getCategory($id); + + $category->updatePosition($value); + + $this->setOrders("manual"); + + return $this->render('export'); + } + + protected function setOrders($category = null, $export = null) + { + if ($category === null) { + $category = $this->getRequest()->query->get("category_order"); + } + + if ($export === null) { + $export = $this->getRequest()->query->get("export_order"); + } + + $this->getParserContext() + ->set("category_order", $category) + ; + + $this->getParserContext() + ->set("export_order", $export) + ; + } protected function getExport($id) { @@ -99,4 +140,21 @@ class ExportController extends BaseAdminController } return $export; } + + protected function getCategory($id) + { + $category = ExportCategoryQuery::create()->findPk($id); + + if (null === $category) { + throw new \ErrorException( + Translator::getInstance()->trans( + "There is no id \"%id\" in the export categories", + [ + "%id" => $id + ] + ) + ); + } + return $category; + } } diff --git a/core/lib/Thelia/Controller/Admin/ImportExportController.php b/core/lib/Thelia/Controller/Admin/ImportExportController.php index 4226ef0e4..c9ab79e86 100644 --- a/core/lib/Thelia/Controller/Admin/ImportExportController.php +++ b/core/lib/Thelia/Controller/Admin/ImportExportController.php @@ -12,6 +12,7 @@ namespace Thelia\Controller\Admin; use Thelia\Core\HttpFoundation\Response; +use Thelia\Model\ExportQuery; /** * Class ImportExportController @@ -20,23 +21,40 @@ use Thelia\Core\HttpFoundation\Response; */ class ImportExportController extends BaseAdminController { - public function import() + public function import($id) { } - public function export() + public function export($id) { } - public function importView() + public function importView($id) { + if (null === $export = $this->getExport($id)) { + return $this->render("404"); + } + return $this->render("import-page"); } - public function exportView() + public function exportView($id) { + if (null === $export = $this->getExport($id)) { + return $this->render("404"); + } + return $this->render("export-page"); } + + protected function getExport($id) + { + $export = ExportQuery::create() + ->findPk($id) + ; + + return $export; + } } \ No newline at end of file diff --git a/core/lib/Thelia/Core/FileFormat/Formatting/Formatter/JsonFormatter.php b/core/lib/Thelia/Core/FileFormat/Formatting/Formatter/JsonFormatter.php index 06f182a00..04612bfdb 100644 --- a/core/lib/Thelia/Core/FileFormat/Formatting/Formatter/JsonFormatter.php +++ b/core/lib/Thelia/Core/FileFormat/Formatting/Formatter/JsonFormatter.php @@ -13,6 +13,7 @@ namespace Thelia\Core\FileFormat\Formatting\Formatter; use Thelia\Core\FileFormat\Formatting\AbstractFormatter; use Thelia\Core\FileFormat\Formatting\FormatterData; +use Thelia\ImportExport\Export\ExportType; /** * Class JsonFormatter @@ -87,4 +88,11 @@ class JsonFormatter extends AbstractFormatter ); } + public function getExportType() + { + return array( + ExportType::EXPORT_TABLE, + ExportType::EXPORT_UNBOUNDED, + ); + } } \ No newline at end of file diff --git a/core/lib/Thelia/Core/FileFormat/Formatting/Formatter/XMLFormatter.php b/core/lib/Thelia/Core/FileFormat/Formatting/Formatter/XMLFormatter.php index 3ab3f7de4..45046af96 100644 --- a/core/lib/Thelia/Core/FileFormat/Formatting/Formatter/XMLFormatter.php +++ b/core/lib/Thelia/Core/FileFormat/Formatting/Formatter/XMLFormatter.php @@ -14,6 +14,7 @@ namespace Thelia\Core\FileFormat\Formatting\Formatter; use Thelia\Core\FileFormat\Formatter\Exception\BadFormattedStringException; use Thelia\Core\FileFormat\Formatting\AbstractFormatter; use Thelia\Core\FileFormat\Formatting\FormatterData; +use Thelia\ImportExport\Export\ExportType; /** * Class XMLFormatter @@ -141,4 +142,12 @@ class XMLFormatter extends AbstractFormatter $data = new FormatterData($this->getAliases()); return $data->setData($array); } + + public function getExportType() + { + return array( + ExportType::EXPORT_TABLE, + ExportType::EXPORT_UNBOUNDED, + ); + } } \ No newline at end of file diff --git a/core/lib/Thelia/Core/FileFormat/Formatting/FormatterInterface.php b/core/lib/Thelia/Core/FileFormat/Formatting/FormatterInterface.php index c88c36c54..a68daac2d 100644 --- a/core/lib/Thelia/Core/FileFormat/Formatting/FormatterInterface.php +++ b/core/lib/Thelia/Core/FileFormat/Formatting/FormatterInterface.php @@ -36,4 +36,17 @@ interface FormatterInterface * a FormatterData object. */ public function decode($rawData); + + /** + * @return string + * + * return a string that defines the handled format type. + * + * Thelia types are defined in \Thelia\ImportExport\Export\ExportType + * + * examples: + * return ExportType::EXPORT_TABLE; + * return ExportType::EXPORT_UNBOUNDED; + */ + public function getExportType(); } diff --git a/core/lib/Thelia/Core/Template/Loop/Export.php b/core/lib/Thelia/Core/Template/Loop/Export.php index 1815b15d5..1975832e5 100644 --- a/core/lib/Thelia/Core/Template/Loop/Export.php +++ b/core/lib/Thelia/Core/Template/Loop/Export.php @@ -12,7 +12,6 @@ namespace Thelia\Core\Template\Loop; use Thelia\Model\ExportQuery; -use Thelia\Model\Map\ExportTableMap; /** * Class Export diff --git a/core/lib/Thelia/Core/Template/Loop/Formatter.php b/core/lib/Thelia/Core/Template/Loop/Formatter.php index 0c77e4de6..15cac4399 100644 --- a/core/lib/Thelia/Core/Template/Loop/Formatter.php +++ b/core/lib/Thelia/Core/Template/Loop/Formatter.php @@ -17,6 +17,7 @@ use Thelia\Core\Template\Element\LoopResult; use Thelia\Core\Template\Element\LoopResultRow; use Thelia\Core\Template\Loop\Argument\Argument; use Thelia\Core\Template\Loop\Argument\ArgumentCollection; +use Thelia\Model\ExportQuery; use Thelia\Type\EnumType; use Thelia\Type\TypeCollection; @@ -39,19 +40,27 @@ class Formatter extends BaseLoop implements ArraySearchLoopInterface $rawFormatters = array_change_key_case($service->getAll()); - $allowedFormatter = $this->getAllowed_formatter(); + $exportId = $this->getExport(); $formatters = []; - if ($allowedFormatter !== null) { - $allowedFormatter = explode(",", $allowedFormatter); + if ($exportId !== null) { + $export = ExportQuery::create()->findPk($exportId); + if (null !== $export) { + $types = $export->getHandleClassInstance($this->container) + ->getHandledType(); - foreach($allowedFormatter as $formatter) { - $formatter = trim(strtolower($formatter)); + if (is_scalar($types)) { + $types = [$types]; + } - if (isset($rawFormatters[$formatter])) { - $formatters[$formatter] = $rawFormatters[$formatter]; + /** @var \Thelia\Core\FileFormat\Formatting\AbstractFormatter $formatter */ + foreach ($rawFormatters as $key=>$formatter) { + if (in_array($formatter->getExportType(), $types)) { + $formatters[$key] = $formatter; + } } } + } else { $formatters = $rawFormatters; } @@ -118,7 +127,7 @@ class Formatter extends BaseLoop implements ArraySearchLoopInterface protected function getArgDefinitions() { return new ArgumentCollection( - Argument::createAnyTypeArgument("allowed_formatter"), + Argument::createIntTypeArgument("export"), new Argument( "order", new TypeCollection( diff --git a/core/lib/Thelia/ImportExport/Both/NewsletterImportExport.php b/core/lib/Thelia/ImportExport/Both/NewsletterImportExport.php new file mode 100644 index 000000000..ffa220e38 --- /dev/null +++ b/core/lib/Thelia/ImportExport/Both/NewsletterImportExport.php @@ -0,0 +1,58 @@ + + */ +class NewsletterImportExport implements ExportHandlerInterface, ImportHandlerInterface +{ + protected $container; + + /** + * @param ContainerInterface $container + * + * Dependency injection: load the container to be able to get parameters and services + */ + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + /** + * @return \Thelia\Core\FileFormat\Formatting\FormatterData + * + * The method builds + */ + public function buildFormatterData() + { + // TODO: Implement buildFormatterData() method. + } + + /** + * @return \Thelia\Core\FileFormat\Formatting\FormatterData + * + * The method builds + */ + public function importFromFormatterData(FormatterData $data) + { + // TODO: Implement importFromFormatterData() method. + } + +} \ No newline at end of file diff --git a/core/lib/Thelia/ImportExport/Export/ExportType.php b/core/lib/Thelia/ImportExport/Export/ExportType.php new file mode 100644 index 000000000..7b98edd4a --- /dev/null +++ b/core/lib/Thelia/ImportExport/Export/ExportType.php @@ -0,0 +1,33 @@ + + */ +class ExportType +{ + /** + * This type is for unbounded formats, in general serialization formats + * example: XML, json, yaml + */ + const EXPORT_UNBOUNDED = "export.unbounded"; + + /** + * This type is for tabled format ( matrix ), most used by spreadsheet application. + * example: CSV, ODS, XLS + */ + const EXPORT_TABLE = "export.table"; +} \ No newline at end of file diff --git a/core/lib/Thelia/ImportExport/Export/MailingExport.php b/core/lib/Thelia/ImportExport/Export/MailingExport.php new file mode 100644 index 000000000..01e73eb8e --- /dev/null +++ b/core/lib/Thelia/ImportExport/Export/MailingExport.php @@ -0,0 +1,47 @@ + + */ +class MailingExport implements ExportHandlerInterface +{ + protected $container; + + /** + * @param ContainerInterface $container + * + * Dependency injection: load the container to be able to get parameters and services + */ + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + /** + * @return \Thelia\Core\FileFormat\Formatting\FormatterData + * + * The method builds + */ + public function buildFormatterData() + { + $data = new FormatterData(); + } + +} \ No newline at end of file diff --git a/core/lib/Thelia/ImportExport/ExportHandlerInterface.php b/core/lib/Thelia/ImportExport/ExportHandlerInterface.php index 4d637201b..64348ff3b 100644 --- a/core/lib/Thelia/ImportExport/ExportHandlerInterface.php +++ b/core/lib/Thelia/ImportExport/ExportHandlerInterface.php @@ -33,4 +33,21 @@ interface ExportHandlerInterface * The method builds */ public function buildFormatterData(); + + /** + * @return string|array + * + * Define all the type of export/formatters that this can handle + * return a string if it handle a single type ( specific exports ), + * or an array if multiple. + * + * Thelia types are defined in \Thelia\ImportExport\Export\ExportType + * + * example: + * return array( + * ExportType::EXPORT_TABLE, + * ExportType::EXPORT_UNBOUNDED, + * ); + */ + public function getHandledType(); } \ No newline at end of file diff --git a/core/lib/Thelia/Model/Export.php b/core/lib/Thelia/Model/Export.php index ef48cbf45..529aeba54 100644 --- a/core/lib/Thelia/Model/Export.php +++ b/core/lib/Thelia/Model/Export.php @@ -74,6 +74,11 @@ class Export extends BaseExport $this->setPosition($position)->save(); } + /** + * @param ContainerInterface $container + * @return ExportHandlerInterface + * @throws \ErrorException + */ public function getHandleClassInstance(ContainerInterface $container) { $class = $this->getHandleClass(); diff --git a/core/lib/Thelia/Model/ExportCategory.php b/core/lib/Thelia/Model/ExportCategory.php index 49c205e39..2e3726e31 100644 --- a/core/lib/Thelia/Model/ExportCategory.php +++ b/core/lib/Thelia/Model/ExportCategory.php @@ -2,9 +2,73 @@ namespace Thelia\Model; +use Propel\Runtime\ActiveQuery\Criteria; +use Thelia\Model\Base\CategoryQuery; use Thelia\Model\Base\ExportCategory as BaseExportCategory; +use Thelia\Model\Map\ExportCategoryTableMap; class ExportCategory extends BaseExportCategory { + public function upPosition() + { + if (($position = $this->getPosition()) > 1) { -} + $previous = ExportCategoryQuery::create() + ->findOneByPosition($position - 1) + ; + + if (null !== $previous) { + $previous->setPosition($position)->save(); + } + + $this->setPosition($position - 1)->save(); + } + + return $this; + } + + public function downPosition() + { + $max = CategoryQuery::create() + ->orderByPosition(Criteria::DESC) + ->select(ExportCategoryTableMap::POSITION) + ->findOne() + ; + + $count = CategoryQuery::create()->count(); + + if ($count > $max) { + $max = $count; + } + + $position = $this->getPosition(); + + if ($position < $max) { + + $next = ExportCategoryQuery::create() + ->findOneByPosition($position + 1) + ; + + if (null !== $next) { + $next->setPosition($position)->save(); + } + + $this->setPosition($position + 1)->save(); + } + + return $this; + } + + public function updatePosition($position) + { + $reverse = ExportCategoryQuery::create() + ->findOneByPosition($position) + ; + + if (null !== $reverse) { + $reverse->setPosition($this->getPosition())->save(); + } + + $this->setPosition($position)->save(); + } +} \ No newline at end of file diff --git a/core/lib/Thelia/Model/ImportCategory.php b/core/lib/Thelia/Model/ImportCategory.php index 9fac04af6..7eec69306 100644 --- a/core/lib/Thelia/Model/ImportCategory.php +++ b/core/lib/Thelia/Model/ImportCategory.php @@ -2,9 +2,72 @@ namespace Thelia\Model; +use Propel\Runtime\ActiveQuery\Criteria; use Thelia\Model\Base\ImportCategory as BaseImportCategory; +use Thelia\Model\Map\ImportCategoryTableMap; class ImportCategory extends BaseImportCategory { + public function upPosition() + { + if (($position = $this->getPosition()) > 1) { + $previous = ImportCategoryQuery::create() + ->findOneByPosition($position - 1) + ; + + if (null !== $previous) { + $previous->setPosition($position)->save(); + } + + $this->setPosition($position - 1)->save(); + } + + return $this; + } + + public function downPosition() + { + $max = CategoryQuery::create() + ->orderByPosition(Criteria::DESC) + ->select(ImportCategoryTableMap::POSITION) + ->findOne() + ; + + $count = CategoryQuery::create()->count(); + + if ($count > $max) { + $max = $count; + } + + $position = $this->getPosition(); + + if ($position < $max) { + + $next = ImportCategoryQuery::create() + ->findOneByPosition($position + 1) + ; + + if (null !== $next) { + $next->setPosition($position)->save(); + } + + $this->setPosition($position + 1)->save(); + } + + return $this; + } + + public function updatePosition($position) + { + $reverse = ImportCategoryQuery::create() + ->findOneByPosition($position) + ; + + if (null !== $reverse) { + $reverse->setPosition($this->getPosition())->save(); + } + + $this->setPosition($position)->save(); + } } diff --git a/templates/backOffice/default/export-page.html b/templates/backOffice/default/export-page.html index e69de29bb..f1b49c6ef 100644 --- a/templates/backOffice/default/export-page.html +++ b/templates/backOffice/default/export-page.html @@ -0,0 +1,46 @@ +{extends file="admin-layout.tpl"} + +{block name="no-return-functions"} + {$admin_current_location = 'tools'} +{/block} + +{block name="page-title"}{intl l='Export: %name' name=$NAME}{/block} + +{block name="check-resource"}admin.export{/block} +{block name="check-access"}view{/block} + +{block name="main-content"} +{/block} + +{block name="javascript-initialization"} + {javascripts file='assets/js/bootstrap-switch/bootstrap-switch.js'} + + {/javascripts} +{/block} + +{block name="javascript-last-call"} + + + {module_include location='configuration-js'} +{/block} \ No newline at end of file diff --git a/templates/backOffice/default/export.html b/templates/backOffice/default/export.html index 5f307c9fe..beccb4987 100644 --- a/templates/backOffice/default/export.html +++ b/templates/backOffice/default/export.html @@ -10,6 +10,14 @@ {block name="check-access"}view{/block} {block name="main-content"} + {if $category_order != "manual"} + {assign url_category "category_order="|cat:$category_order} + {/if} + {if $export_order != "manual"} + {assign url_export "export_order="|cat:$export_order} + {/if} + +
@@ -24,36 +32,42 @@ {module_include location='tools_top'} - {loop name="export-category" type="export-category"} - {if $LOOP_COUNT % 3} + {loop name="export-category" type="export-category" order=$category_order}
- {/if} - -
-