diff --git a/CURRENT_ARCHITECTURE.md b/CURRENT_ARCHITECTURE.md new file mode 100644 index 00000000..d56620f4 --- /dev/null +++ b/CURRENT_ARCHITECTURE.md @@ -0,0 +1,126 @@ +# Architecture Actuelle du Module Lengow + +## ⚠️ IMPORTANT : État Actuel + +**Le module Lengow fonctionne actuellement avec l'architecture PrestaShop standard** : + +- ✅ **Contrôleurs** : `ModuleAdminController` (dans `controllers/admin/`) +- ✅ **Templates** : Smarty `.tpl` (dans `views/templates/admin/`) +- ✅ **Routes** : URLs PrestaShop standard avec token admin +- ✅ **Compatibilité** : PrestaShop 1.7.8+ à 9.x + +--- + +## ❌ Pourquoi la migration Symfony/Twig complète n'est pas active + +### Tentative de migration Symfony/Twig + +Des fichiers ont été créés pour une migration vers Symfony/Twig : +- Contrôleurs Symfony dans `src/Controller/` +- Templates Twig dans `views/templates/twig/` +- Configuration des routes dans `config/routes.yml` + +**MAIS** : Ces fichiers ne sont **PAS actifs** et ne doivent **PAS être activés** car : + +1. **Conflits d'architecture** : L'ajout de `getRoutingConfigPath()` cause des boucles infinies +2. **Crash de la page des plugins** : PrestaShop ne peut pas gérer les deux systèmes simultanément +3. **Perte du système de tokens** : Les routes Symfony ne gèrent pas automatiquement les tokens de sécurité PrestaShop + +--- + +## 📋 Comment accéder aux pages actuelles (architecture Smarty) + +### Pages du module via le back-office + +Toutes les pages sont accessibles via le menu Lengow dans le back-office PrestaShop. + +**URLs avec token admin** (générées automatiquement par PrestaShop) : +``` +https://votre-domaine.com/admin-folder/?controller=AdminLengowDashboard&token=xxx... +https://votre-domaine.com/admin-folder/?controller=AdminLengowHome&token=xxx... +https://votre-domaine.com/admin-folder/?controller=AdminLengowOrder&token=xxx... +https://votre-domaine.com/admin-folder/?controller=AdminLengowFeed&token=xxx... +``` + +Le token est **obligatoire** pour la sécurité et est généré automatiquement par PrestaShop. + +--- + +## 🎯 Migration Future vers Symfony/Twig + +### Pourquoi migrer ? + +PrestaShop 8+ et 9 recommandent l'utilisation de Symfony/Twig, mais cette migration est **complexe** : + +- **Temps estimé** : 80-120 heures de développement + 20-30 heures de tests +- **Scope** : 9 contrôleurs + 37 templates + routes + AJAX +- **Risque** : Interruption de service pendant la migration + +### Approche recommandée + +1. **Court terme (actuel)** : Conserver l'architecture Smarty qui fonctionne +2. **Moyen terme** : Planifier la migration comme un projet dédié +3. **Long terme** : Migration progressive page par page + +### Ressources disponibles + +Des guides et exemples ont été créés pour faciliter une future migration : + +- **`SYMFONY_TWIG_MIGRATION_GUIDE.md`** : Guide complet de migration (38 000+ caractères) +- **`SYMFONY_MIGRATION_PLAN.md`** : Plan détaillé de migration +- **`src/Controller/AdminOrdersController.php`** : Exemple de contrôleur Symfony (NON ACTIF) +- **`views/templates/twig/admin/orders/`** : Exemples de templates Twig (NON ACTIFS) +- **`config/routes.yml`** : Configuration des routes (NON ACTIVE) + +⚠️ **Ces fichiers sont des EXEMPLES uniquement** - ils ne doivent pas être activés sans une migration complète. + +--- + +## 🔧 Corrections PrestaShop 9 + +Les seules modifications actives pour la compatibilité PrestaShop 9 sont : + +### 1. Extension de compatibilité version dans `lengow.php` +```php +'ps_versions_compliancy' => ['min' => '1.7.8.0', 'max' => '9.99.99'] +``` + +### 2. Méthodes `formatPrice()` dans `LengowList.php` et `LengowProduct.php` + +Remplacement de `Tools::displayPrice()` (supprimée en PS9) par : +```php +private function formatPrice($price, $currency) +{ + $locale = Context::getContext()->getCurrentLocale(); + if ($locale && method_exists($locale, 'formatPrice')) { + return $locale->formatPrice($price, $currency->iso_code); + } + + // Fallback pour compatibilité + $formattedPrice = number_format($price, $currency->decimals, '.', ''); + + if ($currency->format == 1) { + return $currency->sign . ' ' . $formattedPrice; + } else { + return $formattedPrice . ' ' . $currency->sign; + } +} +``` + +Ces corrections assurent la **compatibilité PrestaShop 9 sans casser l'architecture existante**. + +--- + +## 📚 Résumé + +| Élément | État | Emplacement | +|---------|------|-------------| +| Contrôleurs Smarty | ✅ **Actifs** | `controllers/admin/AdminLengow*.php` | +| Templates Smarty | ✅ **Actifs** | `views/templates/admin/*.tpl` | +| Routes PrestaShop | ✅ **Actives** | URLs avec token admin | +| Contrôleurs Symfony | ❌ **Inactifs** | `src/Controller/` (exemples) | +| Templates Twig | ❌ **Inactifs** | `views/templates/twig/` (exemples) | +| Routes Symfony | ❌ **Inactives** | `config/routes.yml` (exemple) | +| Compatibilité PS9 | ✅ **Active** | `formatPrice()` dans LengowList/LengowProduct | + +**Le module fonctionne parfaitement avec l'architecture Smarty actuelle sur PrestaShop 1.7.8+ à 9.x.** diff --git a/MIGRATION_STATUS.md b/MIGRATION_STATUS.md new file mode 100644 index 00000000..a685332d --- /dev/null +++ b/MIGRATION_STATUS.md @@ -0,0 +1,227 @@ +# Migration Symfony/Twig - État des lieux et prochaines étapes + +## Ce qui a été fait ✅ + +### 1. Migration vers Twig dans les contrôleurs legacy +- **9 contrôleurs admin** modifiés dans `controllers/admin/` pour utiliser Twig au lieu de Smarty +- Les contrôleurs utilisent `setTemplate('module:lengow/views/templates/admin/...')` pour charger les templates Twig +- Les données sont préparées directement dans `initContent()` sans appeler les méthodes legacy +- **Compatibilité PrestaShop 9** : version max mise à jour à 9.99.99 +- **Erreurs corrigées** : Plus d'appels à des méthodes protégées, pas de conflits avec exit() + +### 2. Contrôleurs modifiés +| Page | Contrôleur Admin | Template Twig | État | +|------|-----------------|---------------|------| +| Dashboard | AdminLengowDashboardController | dashboard/index.html.twig | ✅ Charge sans erreur | +| Home/Connexion | AdminLengowHomeController | home/index.html.twig | ✅ Charge sans erreur | +| Produits/Feed | AdminLengowFeedController | feed/index.html.twig | ✅ Charge sans erreur | +| Commandes | AdminLengowOrderController | orders/index.html.twig | ✅ Charge sans erreur | +| Paramètres principaux | AdminLengowMainSettingController | main_setting/index.html.twig | ✅ Charge sans erreur | +| Paramètres commandes | AdminLengowOrderSettingController | order_setting/index.html.twig | ✅ Charge sans erreur | +| Toolbox | AdminLengowToolboxController | toolbox/index.html.twig | ✅ Charge sans erreur | +| Mentions légales | AdminLengowLegalsController | legals/index.html.twig | ✅ Charge sans erreur | +| Aide | AdminLengowHelpController | help/index.html.twig | ✅ Charge sans erreur | + +### 3. Approche de migration corrigée +**Utilisation des ModuleAdminController avec Twig** : +- Les contrôleurs restent dans `controllers/admin/` (structure PrestaShop standard) +- Les URLs legacy fonctionnent : `?controller=AdminLengowHome&token=...` +- Les contrôleurs préparent les données directement dans `initContent()` +- Les templates Twig sont chargés via `setTemplate('module:lengow/...')` +- Pas de redirections - rendu direct avec Twig + +### 4. Templates Twig créés +Structure de base créée : +- `_partials/base.html.twig` - Layout de base avec assets CSS/JS +- `_partials/header.html.twig` - Navigation principale (migrée de Smarty) +- `_partials/footer.html.twig` - Footer +- Templates individuels pour chaque page (structure minimale) + +### 5. Contrôleurs Symfony (optionnels) +Les contrôleurs Symfony dans `src/Controller/` peuvent être utilisés pour : +- Routes API personnalisées +- Actions AJAX spécifiques +- Endpoints REST +Ils ne sont pas utilisés pour les pages admin principales. + +## Ce qui reste à faire 📋 + +### 1. Actions AJAX et formulaires à réimplémenter + +#### Actions critiques manquantes (à restaurer) : +**AdminLengowHomeController** : +- `go_to_credentials` - Affichage du formulaire de connexion +- `connect_cms` - Connexion au CMS Lengow +- `go_to_catalog` - Sélection des catalogues +- `link_catalogs` - Liaison des catalogues + +**AdminLengowDashboardController** : +- `remind_me_later` - Report de la notification de mise à jour + +**AdminLengowFeedController, AdminLengowOrderController, etc.** : +- Diverses actions AJAX pour filtres, exports, imports, etc. + +**Solutions possibles** : +1. Créer des méthodes AJAX séparées dans les contrôleurs admin +2. Utiliser les contrôleurs Symfony pour gérer les endpoints AJAX +3. Ajouter des méthodes `processAjax()` dans les contrôleurs admin + +### 2. Variables template manquantes + +Variables de `prepareDisplay()` non assignées : +- `showPluginUpgradeModal` +- `lengowModalAjaxLink` +- `helpCenterLink`, `updateGuideLink`, `changelogLink`, `supportLink` +- `multiShop`, `debugMode` +- `isNewMerchant` +- Et autres variables spécifiques à chaque page + +### 3. Migration complète du contenu des templates + +### 1. Migration complète du contenu des templates +Les templates Twig actuels contiennent des placeholders. Il faut migrer : + +#### Dashboard (`views/templates/admin/lengow_dashboard/`) +- Statistiques et métriques +- Graphiques de performance +- Alertes et notifications +- État du compte marchand + +#### Home/Connexion (`views/templates/admin/lengow_home/`) +- Formulaire de connexion API +- Sélection des catalogues +- Gestion des credentials +- Workflow de configuration initiale +- Templates AJAX : `connection_*.tpl` → `.html.twig` + +#### Feed/Produits (`views/templates/admin/lengow_feed/`) +- Liste des produits exportables +- Filtres et sélection +- Configuration des flux +- Options d'export + +#### Commandes (`views/templates/admin/lengow_order/`) +- Table des commandes Lengow +- Filtres et recherche +- Actions sur commandes (ré-import, renvoi) +- Détails des erreurs + +#### Paramètres (`views/templates/admin/lengow_main_setting/`) +- Formulaires de configuration +- Gestion des logs +- Paramètres globaux +- Désinstallation + +#### Paramètres commandes (`views/templates/admin/lengow_order_setting/`) +- Mapping marketplace/statuts +- Configuration transporteurs +- Règles de gestion des commandes + +#### Toolbox (`views/templates/admin/lengow_toolbox/`) +- Outils de diagnostic +- Logs système +- Tests de connectivité + +#### Legals & Help +- Contenu statique à migrer + +### 2. Migration de la logique Smarty vers Twig +Remplacer les constructions Smarty : +```smarty +{$variable|escape:'htmlall':'UTF-8'} → {{ variable|escape('html') }} +{if $condition}...{/if} → {% if condition %}...{% endif %} +{foreach $items as $item}...{/foreach} → {% for item in items %}...{% endfor %} +{include file='...'} → {% include '@Modules/lengow/...' %} +``` + +### 3. Gestion des assets +- Vérifier que tous les JS sont chargés correctement +- S'assurer que les chemins des assets fonctionnent +- Tester les appels AJAX depuis les nouveaux templates + +### 4. Formulaires Symfony +Pour une intégration complète PrestaShop 9 : +- Créer des FormTypes Symfony pour les formulaires +- Remplacer les formulaires HTML legacy +- Gérer la validation côté serveur avec Symfony + +### 5. Services et injection de dépendances +Améliorer l'architecture : +- Créer des services Symfony pour la logique métier +- Injecter les dépendances dans les contrôleurs +- Utiliser le container de services PrestaShop + +### 6. Tests +- Tester l'installation du module +- Tester la navigation entre pages +- Tester les actions AJAX +- Tester les formulaires +- Tester sur PrestaShop 8 et 9 + +## Approche recommandée pour finaliser + +### Option 1 : Migration progressive (recommandée) +1. Commencer par les pages les plus simples (Legals, Help) +2. Migrer ensuite les pages avec formulaires (Settings) +3. Finir par les pages complexes avec AJAX (Dashboard, Orders) +4. Tester page par page + +### Option 2 : Migration par composant +1. Migrer tous les headers/footers +2. Migrer tous les formulaires +3. Migrer toutes les tables de données +4. Migrer les modales et popups + +## Structure des fichiers après migration complète + +``` +lengow/ +├── config/ +│ └── routes.yml # Routes Symfony ✅ +├── controllers/admin/ # Legacy redirects ✅ +│ └── AdminLengow*.php +├── src/Controller/ # Contrôleurs Symfony ✅ +│ ├── AdminDashboardController.php +│ ├── AdminHomeController.php +│ └── ... +├── views/ +│ ├── templates/admin/ +│ │ ├── _partials/ # Composants réutilisables ✅ +│ │ ├── dashboard/ # À compléter 📋 +│ │ ├── home/ # À compléter 📋 +│ │ ├── feed/ # À compléter 📋 +│ │ └── ... +│ ├── css/ # Assets existants ✅ +│ └── js/ # Assets existants ✅ +└── classes/controllers/ # Business logic (conservée) ✅ + └── Lengow*Controller.php +``` + +## Notes importantes + +### Compatibilité +- Le code actuel fonctionne avec PrestaShop 1.7.8 à 9.99.99 +- Les templates Smarty legacy sont toujours présents et peuvent servir de référence +- L'approche progressive permet de garder le module fonctionnel pendant la migration + +### Dépendances +- PrestaShop 9 utilise Symfony 6.x +- Twig 3.x est inclus dans PrestaShop 9 +- Les annotations `@AdminSecurity` sont utilisées pour les permissions + +### Performance +- Les contrôleurs Symfony sont plus performants que les legacy +- Twig est compilé et mis en cache +- La séparation des responsabilités améliore la maintenabilité + +## Conclusion + +La fondation Symfony/Twig est en place et fonctionnelle. Le module peut maintenant être étendu progressivement en migrant le contenu des templates. L'architecture actuelle permet : + +1. ✅ Routing moderne avec Symfony +2. ✅ Compatibilité PrestaShop 9 +3. ✅ Navigation entre pages fonctionnelle +4. ✅ Réutilisation de la logique métier existante +5. 📋 Templates à enrichir avec le contenu des pages + +La migration peut se faire de manière incrémentale, page par page, tout en maintenant la fonctionnalité du module. diff --git a/README.md b/README.md index 1b304e53..9b06846d 100755 --- a/README.md +++ b/README.md @@ -86,6 +86,29 @@ Once the translations are finished, just run the translation update script in `l The plugin is translated into English, French, Spanish and Italian. +## Architecture + +### Symfony Controllers (PrestaShop 9 Compatible) + +Starting from version 3.9.4, the module uses Symfony controllers and Twig templates for PrestaShop 9 compatibility. + +**Available Routes:** +- `/modules/lengow/dashboard` - Dashboard page +- `/modules/lengow/home` - Connection/Home page +- `/modules/lengow/feed` - Product catalog management +- `/modules/lengow/orders` - Order list +- `/modules/lengow/settings` - Main settings +- `/modules/lengow/order-settings` - Order-specific settings +- `/modules/lengow/toolbox` - Diagnostic tools +- `/modules/lengow/legals` - Legal information +- `/modules/lengow/help` - Help and documentation + +**Controllers Location:** `src/Controller/Admin*.php` +**Templates Location:** `views/templates/admin/*/index.html.twig` +**Routes Definition:** `config/routes.yml` + +The legacy ModuleAdminController classes in `controllers/admin/` now redirect to the Symfony routes. + ## Changelog The changelog and all available commits are located under [CHANGELOG](CHANGELOG). diff --git a/SYMFONY_MIGRATION_PLAN.md b/SYMFONY_MIGRATION_PLAN.md new file mode 100644 index 00000000..2df89151 --- /dev/null +++ b/SYMFONY_MIGRATION_PLAN.md @@ -0,0 +1,211 @@ +# Migration complète vers Symfony/Twig - Plan détaillé + +> **📖 Guide Complet** : Voir [`SYMFONY_TWIG_MIGRATION_GUIDE.md`](./SYMFONY_TWIG_MIGRATION_GUIDE.md) pour un guide d'implémentation détaillé avec exemples de code complets. + +## État actuel (analysé le 2026-01-05) + +### ✅ Ce qui existe déjà +- **10 contrôleurs Symfony** dans `src/Controller/` (squelettes) +- **Routes Symfony** définies dans `config/routes.yml` +- **Corrections API PrestaShop 9** : formatPrice(), compatibilité 9.99.99 +- **Infrastructure Twig de base** : Layout, header, footer créés +- **Guide de migration complet** : `SYMFONY_TWIG_MIGRATION_GUIDE.md` + +### ❌ Ce qui manque / à finaliser +- **37 templates Smarty** (.tpl) à convertir en Twig (.twig) +- **Contrôleurs Symfony incomplets** : ne retournent pas de Response complète +- **9 contrôleurs legacy** AdminLengow* toujours actifs +- **Tests** de compatibilité PS 8+/9 + +### 📚 Ressources Disponibles +- **Guide détaillé** : `SYMFONY_TWIG_MIGRATION_GUIDE.md` - Guide complet avec exemples +- **Exemples de contrôleurs** : `src/Controller/AdminDashboardController.php` +- **Templates de base** : `views/templates/twig/admin/_partials/` +- **Routes** : `config/routes.yml` + +## Plan de migration par phase + +### Phase 1 : Infrastructure Twig ✅ (Prioritaire) +**Objectif** : Créer l'architecture de base Twig + +#### 1.1 Templates de base +``` +views/templates/twig/admin/ +├── _partials/ +│ ├── base.html.twig # Layout principal +│ ├── header.html.twig # Header avec navigation +│ └── footer.html.twig # Footer +├── dashboard/ +│ └── index.html.twig +├── home/ +│ └── index.html.twig +├── feed/ +│ └── index.html.twig +├── orders/ +│ └── index.html.twig +├── settings/ +│ └── index.html.twig +├── order_settings/ +│ └── index.html.twig +├── toolbox/ +│ └── index.html.twig +├── legals/ +│ └── index.html.twig +└── help/ + └── index.html.twig +``` + +#### 1.2 Mapping templates Smarty → Twig +| Template Smarty actuel | Template Twig cible | +|------------------------|---------------------| +| `views/templates/admin/lengow_home/layout.tpl` | `views/templates/twig/admin/home/index.html.twig` | +| `views/templates/admin/lengow_feed/layout.tpl` | `views/templates/twig/admin/feed/index.html.twig` | +| `views/templates/admin/lengow_order/layout.tpl` | `views/templates/twig/admin/orders/index.html.twig` | +| ... | ... | + +### Phase 2 : Contrôleurs Symfony complets +**Objectif** : Finaliser les 10 contrôleurs pour qu'ils retournent des Response Twig + +#### 2.1 Pattern à suivre +```php +public function indexAction(Request $request): Response +{ + // 1. Récupérer les données métier + $lengowController = new \LengowHomeController(); + $data = $lengowController->getData(); // ou équivalent + + // 2. Préparer les variables Twig + $templateData = [ + 'locale' => new \LengowTranslation(), + 'localeIsoCode' => \Tools::substr(\Context::getContext()->language->language_code, 0, 2), + 'multiShop' => \Shop::isFeatureActive(), + 'debugMode' => \LengowConfiguration::debugModeIsActive(), + // ... autres variables + ]; + + // 3. Rendre le template Twig + return $this->render('@Modules/lengow/views/templates/twig/admin/home/index.html.twig', $templateData); +} +``` + +#### 2.2 Contrôleurs à finaliser +- [ ] `AdminHomeController` - Home/Connexion +- [ ] `AdminDashboardController` - Tableau de bord +- [ ] `AdminFeedController` - Catalogue produits +- [ ] `AdminOrdersController` - Liste commandes +- [ ] `AdminOrderController` - Détail commande (déjà avancé) +- [ ] `AdminMainSettingController` - Configuration +- [ ] `AdminOrderSettingController` - Paramètres commandes +- [ ] `AdminToolboxController` - Outils diagnostic +- [ ] `AdminLegalsController` - Mentions légales +- [ ] `AdminHelpController` - Aide + +### Phase 3 : Migration des templates +**Objectif** : Convertir chaque template Smarty en Twig + +#### 3.1 Syntaxe Smarty → Twig +| Smarty | Twig | +|--------|------| +| `{$variable}` | `{{ variable }}` | +| `{if $condition}...{/if}` | `{% if condition %}...{% endif %}` | +| `{foreach $array as $item}...{/foreach}` | `{% for item in array %}...{% endfor %}` | +| `{l s='Text'}` | `{{ 'Text'\|trans({}, 'Modules.Lengow.Admin') }}` | +| `{$variable.property}` | `{{ variable.property }}` | +| `{include file='...'}` | `{% include '...' %}` | + +#### 3.2 Ordre de migration suggéré +1. **Header/Footer/Layout** (commun à toutes les pages) +2. **Dashboard** (page principale, référence) +3. **Home** (connexion, critère MVP) +4. **Feed, Orders, Settings** (pages principales) +5. **Toolbox, Legals, Help** (pages secondaires) + +### Phase 4 : Actions AJAX +**Objectif** : Migrer les endpoints AJAX vers Symfony + +#### 4.1 Pattern AJAX +```php +public function ajaxAction(Request $request): JsonResponse +{ + $action = $request->request->get('action') ?? $request->query->get('action'); + + switch ($action) { + case 'connect_cms': + return $this->connectCmsAction($request); + case 'link_catalogs': + return $this->linkCatalogsAction($request); + // ... autres actions + default: + return new JsonResponse(['error' => 'Unknown action'], 400); + } +} +``` + +#### 4.2 Actions à migrer +- **Home** : `connect_cms`, `link_catalogs`, `go_to_credentials` +- **Dashboard** : `remind_me_later`, `refresh_status` +- **Feed** : export actions, filtres +- **Orders** : actions sur commandes +- **Settings** : sauvegarde configuration + +### Phase 5 : Nettoyage et tests +**Objectif** : Supprimer le code legacy, tester + +#### 5.1 À supprimer +- [ ] `controllers/admin/AdminLengow*.php` (9 fichiers) +- [ ] `views/templates/admin/lengow_*/` (37 fichiers .tpl) +- [ ] Références Smarty dans le code + +#### 5.2 Tests +- [ ] Accès à chaque page via route Symfony +- [ ] Navigation entre pages +- [ ] Actions AJAX fonctionnelles +- [ ] Compatibilité PrestaShop 8.x +- [ ] Compatibilité PrestaShop 9.x +- [ ] Pas de régression PS 1.7.8+ + +## Prochaines étapes immédiates + +### Étape 1 : Base Twig (urgent) +1. Créer `base.html.twig` +2. Créer `header.html.twig` et `footer.html.twig` +3. Créer templates squelettes pour les 9 pages + +### Étape 2 : Contrôleur Dashboard complet +1. Finaliser `AdminDashboardController::indexAction()` +2. Créer template Twig Dashboard complet +3. Tester l'accès via route Symfony + +### Étape 3 : Réplication +1. Répliquer le pattern Dashboard pour les 8 autres pages +2. Adapter le contenu spécifique de chaque page + +## Notes techniques + +### Compatibilité PrestaShop +- **PS 1.7.6+** : Twig disponible, mais Smarty toujours utilisé pour modules +- **PS 8.x** : Support complet Twig + Symfony pour modules +- **PS 9.x** : Twig/Symfony devient le standard + +### Limitations connues +- `ModuleAdminController` ne supporte PAS Twig nativement +- Il FAUT utiliser `FrameworkBundleAdminController` (Symfony) +- Routes doivent être déclarées dans `config/routes.yml` +- Templates Twig doivent utiliser namespace `@Modules/lengow/...` + +### Avantages post-migration +- ✅ Code moderne et maintenable +- ✅ Compatible PS 8+ et 9 nativement +- ✅ Meilleure séparation logique/présentation +- ✅ Syntaxe Twig plus claire que Smarty +- ✅ Routing Symfony plus flexible + +## Estimation +- **Temps total** : 3-5 jours développement + 2 jours tests +- **Complexité** : Élevée (refonte complète architecture frontend) +- **Risque** : Moyen (bien cadré, PrestaShop supporte Twig/Symfony) + +--- + +**Dernière mise à jour** : 2026-01-05 +**Statut** : Migration en cours - Phase 1 infrastructure Twig diff --git a/SYMFONY_TWIG_MIGRATION_GUIDE.md b/SYMFONY_TWIG_MIGRATION_GUIDE.md new file mode 100644 index 00000000..e106171e --- /dev/null +++ b/SYMFONY_TWIG_MIGRATION_GUIDE.md @@ -0,0 +1,1198 @@ +# Guide de Migration Symfony/Twig pour Module Lengow PrestaShop + +## 📋 Vue d'ensemble + +Ce guide fournit une approche complète pour migrer le module Lengow de l'architecture Smarty (`.tpl`) vers Symfony/Twig pour PrestaShop 8+ et 9. + +**Compatibilité** : +- ✅ PrestaShop 1.7.6+ (support Twig partiel avec coexistence Smarty) +- ✅ PrestaShop 8.x (support Twig/Symfony complet) +- ✅ PrestaShop 9.x (Twig/Symfony standard) + +--- + +## 🏗️ Architecture Actuelle vs Nouvelle Architecture + +### Architecture Actuelle (Smarty) +``` +controllers/admin/AdminLengow*.php (ModuleAdminController) + ↓ délègue à +classes/controllers/Lengow*Controller.php (logique métier) + ↓ rend +views/templates/admin/**/*.tpl (templates Smarty) +``` + +### Nouvelle Architecture (Symfony/Twig) +``` +src/Controller/Admin*.php (FrameworkBundleAdminController) + ↓ utilise directement +classes/models/* (modèles de données) + ↓ rend +views/templates/twig/admin/**/*.html.twig (templates Twig) +``` + +--- + +## 📂 Structure des Fichiers + +### Contrôleurs Symfony +Emplacement : `src/Controller/` + +**Convention de nommage** : +- `Admin{Page}Controller.php` (ex: `AdminDashboardController.php`) +- Namespace : `PrestaShop\Module\Lengow\Controller` + +### Templates Twig +Emplacement : `views/templates/twig/admin/` + +**Structure recommandée** : +``` +views/templates/twig/admin/ +├── _partials/ +│ ├── base.html.twig # Layout principal +│ ├── header.html.twig # En-tête avec navigation +│ └── footer.html.twig # Pied de page +├── dashboard/ +│ └── index.html.twig +├── home/ +│ └── index.html.twig +├── feed/ +│ ├── index.html.twig +│ ├── product_list.html.twig +│ └── ... +└── ... +``` + +### Routes Symfony +Emplacement : `config/routes.yml` + +--- + +## 🔧 Migration Étape par Étape + +### Étape 1 : Créer un Contrôleur Symfony + +#### Exemple Complet : Dashboard Controller + +**Fichier** : `src/Controller/AdminDashboardController.php` + +```php +version, '>')) { + $pluginIsUpToDate = false; + } + + // 3. Traiter les actions (si nécessaire) + $action = $request->query->get('action'); + if ($action === 'refresh_status') { + \LengowSync::getStatusAccount(true); + return $this->redirectToRoute('lengow_admin_dashboard'); + } + + // 4. Générer les URLs pour les actions + $refreshStatusUrl = $this->generateUrl('lengow_admin_dashboard', ['action' => 'refresh_status']); + + // 5. Préparer les variables pour le template + $templateVars = [ + // Services Lengow + 'locale' => $locale, + 'lengow_link' => $lengowLink, + + // Informations du module + 'lengowPathUri' => $module->getPathUri(), + 'lengowUrl' => \LengowConfiguration::getLengowUrl(), + + // Données métier + 'merchantStatus' => $merchantStatus, + 'pluginData' => $pluginData, + 'pluginIsUpToDate' => $pluginIsUpToDate, + 'total_pending_order' => \LengowOrder::countOrderToBeSent(), + + // Configuration UI + 'displayToolbar' => 1, + 'current_controller' => 'LengowDashboardController', + + // URLs d'actions + 'refresh_status' => $refreshStatusUrl, + ]; + + // 6. Rendre le template Twig + return $this->render('@Modules/lengow/views/templates/twig/admin/dashboard/index.html.twig', $templateVars); + } + + /** + * Handle AJAX action: remind me later for plugin update + * + * @AdminSecurity("is_granted('update', 'AdminLengowDashboard')") + * + * @param Request $request + * @return JsonResponse + */ + public function remindMeLaterAction(Request $request): JsonResponse + { + $timestamp = time() + (7 * 86400); // 7 days + \LengowConfiguration::updateGlobalValue( + \LengowConfiguration::LAST_UPDATE_PLUGIN_MODAL, + $timestamp + ); + + return new JsonResponse(['success' => true]); + } + + /** + * Handle AJAX action: get dashboard statistics + * + * @AdminSecurity("is_granted('read', 'AdminLengowDashboard')") + * + * @param Request $request + * @return JsonResponse + */ + public function getStatisticsAction(Request $request): JsonResponse + { + $statistics = [ + 'pending_orders' => \LengowOrder::countOrderToBeSent(), + 'errors' => \LengowMain::getLastImportErrors(), + 'last_import' => \LengowImport::getLastImport(), + ]; + + return new JsonResponse($statistics); + } +} +``` + +#### Points Clés du Contrôleur Symfony + +1. **Hérite de `FrameworkBundleAdminController`** : Donne accès aux services Symfony +2. **Annotations `@AdminSecurity`** : Gère les permissions PrestaShop +3. **Type hints stricts** : `Request`, `Response`, `JsonResponse` +4. **Méthodes d'action** : Suffixe `Action` (ex: `indexAction`, `remindMeLaterAction`) +5. **Rendu Twig** : `$this->render('@Modules/lengow/...')` +6. **Génération d'URLs** : `$this->generateUrl('route_name', ['param' => 'value'])` +7. **Redirections** : `$this->redirectToRoute('route_name')` + +--- + +### Étape 2 : Définir les Routes Symfony + +**Fichier** : `config/routes.yml` + +```yaml +# Dashboard routes +lengow_admin_dashboard: + path: /lengow/dashboard + methods: [GET, POST] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminDashboardController::indexAction' + _legacy_controller: AdminLengowDashboard + _legacy_link: AdminLengowDashboard + +lengow_admin_dashboard_remind: + path: /lengow/dashboard/remind-me-later + methods: [POST] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminDashboardController::remindMeLaterAction' + +lengow_admin_dashboard_stats: + path: /lengow/dashboard/statistics + methods: [GET] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminDashboardController::getStatisticsAction' + +# Home/Connection routes +lengow_admin_home: + path: /lengow/home + methods: [GET, POST] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminHomeController::indexAction' + _legacy_controller: AdminLengowHome + _legacy_link: AdminLengowHome + +lengow_admin_home_auth: + path: /lengow/home/authenticate + methods: [POST] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminHomeController::authenticateAction' + +# Feed/Catalog routes +lengow_admin_feed: + path: /lengow/feed + methods: [GET] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminFeedController::indexAction' + _legacy_controller: AdminLengowFeed + _legacy_link: AdminLengowFeed + +lengow_admin_feed_export: + path: /lengow/feed/export + methods: [POST] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminFeedController::exportAction' + +# Orders routes +lengow_admin_order: + path: /lengow/orders + methods: [GET] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrderController::indexAction' + _legacy_controller: AdminLengowOrder + _legacy_link: AdminLengowOrder + +lengow_admin_order_import: + path: /lengow/orders/import + methods: [POST] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrderController::importAction' + +# Settings routes +lengow_admin_main_setting: + path: /lengow/settings + methods: [GET, POST] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminMainSettingController::indexAction' + _legacy_controller: AdminLengowMainSetting + _legacy_link: AdminLengowMainSetting + +lengow_admin_order_setting: + path: /lengow/settings/orders + methods: [GET, POST] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrderSettingController::indexAction' + _legacy_controller: AdminLengowOrderSetting + _legacy_link: AdminLengowOrderSetting + +# Toolbox route +lengow_admin_toolbox: + path: /lengow/toolbox + methods: [GET, POST] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminToolboxController::indexAction' + _legacy_controller: AdminLengowToolbox + _legacy_link: AdminLengowToolbox + +# Legals route +lengow_admin_legals: + path: /lengow/legals + methods: [GET] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminLegalsController::indexAction' + _legacy_controller: AdminLengowLegals + _legacy_link: AdminLengowLegals + +# Help route +lengow_admin_help: + path: /lengow/help + methods: [GET] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminHelpController::indexAction' + _legacy_controller: AdminLengowHelp + _legacy_link: AdminLengowHelp +``` + +#### Convention de Nommage des Routes + +- **Route principale** : `lengow_admin_{page}` (ex: `lengow_admin_dashboard`) +- **Actions AJAX** : `lengow_admin_{page}_{action}` (ex: `lengow_admin_dashboard_remind`) +- **Paramètres legacy** : `_legacy_controller` et `_legacy_link` pour compatibilité avec les liens existants + +--- + +### Étape 3 : Créer les Templates Twig + +#### Template de Base (Layout Principal) + +**Fichier** : `views/templates/twig/admin/_partials/base.html.twig` + +```twig + + + + + + {% block title %}Lengow{% endblock %} - PrestaShop + + {# Styles Lengow #} + + + + {% block stylesheets %}{% endblock %} + + + {# Header avec navigation #} + {% include '@Modules/lengow/views/templates/twig/admin/_partials/header.html.twig' %} + + {# Contenu principal #} +
+ {% block content %}{% endblock %} +
+ + {# Footer #} + {% include '@Modules/lengow/views/templates/twig/admin/_partials/footer.html.twig' %} + + {# Scripts Lengow #} + + + + {% block javascripts %}{% endblock %} + + +``` + +#### Template Header + +**Fichier** : `views/templates/twig/admin/_partials/header.html.twig` + +```twig +
+
+ + + + + {% if pluginIsUpToDate == false %} +
+ {{ locale.t('dashboard.plugin_update_available', {'version': pluginData.version}) }} +
+ {% endif %} +
+
+``` + +#### Template de Page : Dashboard + +**Fichier** : `views/templates/twig/admin/dashboard/index.html.twig` + +```twig +{% extends '@Modules/lengow/views/templates/twig/admin/_partials/base.html.twig' %} + +{% block title %}{{ locale.t('menu.dashboard') }}{% endblock %} + +{% block content %} +
+

{{ locale.t('menu.dashboard') }}

+ + {# Account Status Section #} +
+

{{ locale.t('dashboard.account_status') }}

+ + {% if merchantStatus %} +
+

{{ merchantStatus.message }}

+ + {% if merchantStatus.type == 'success' %} +
+

{{ locale.t('dashboard.account_id') }}: {{ merchantStatus.account_id }}

+

{{ locale.t('dashboard.catalog_ids') }}: {{ merchantStatus.catalog_ids|join(', ') }}

+
+ {% endif %} +
+ + + {{ locale.t('dashboard.refresh_status') }} + + {% else %} +

{{ locale.t('dashboard.no_account_data') }}

+ {% endif %} +
+ + {# Statistics Section #} +
+

{{ locale.t('dashboard.statistics') }}

+ +
+
+

{{ locale.t('dashboard.pending_orders') }}

+

{{ total_pending_order }}

+
+ +
+

{{ locale.t('dashboard.plugin_version') }}

+

+ {{ pluginData.version|default('N/A') }} + {% if pluginIsUpToDate == false %} + {{ locale.t('dashboard.update_available') }} + {% endif %} +

+
+
+
+ + {# Plugin Update Modal (si nécessaire) #} + {% if pluginIsUpToDate == false %} +
+ +
+ {% endif %} +
+{% endblock %} + +{% block javascripts %} + +{% endblock %} +``` + +--- + +## 🔄 Conversion Smarty → Twig + +### Syntaxe Comparative + +| Smarty | Twig | Description | +|--------|------|-------------| +| `{$variable}` | `{{ variable }}` | Afficher une variable | +| `{if $condition}...{/if}` | `{% if condition %}...{% endif %}` | Condition | +| `{foreach $items as $item}...{/foreach}` | `{% for item in items %}...{% endfor %}` | Boucle | +| `{include file='header.tpl'}` | `{% include 'header.html.twig' %}` | Inclusion | +| `{$locale->t('key')}` | `{{ locale.t('key') }}` | Traduction | +| `{$array\|@count}` | `{{ array\|length }}` | Longueur tableau | +| `{$price\|number_format:2}` | `{{ price\|number_format(2) }}` | Formatage nombre | +| `{$text\|escape:'html'}` | `{{ text\|e }}` ou `{{ text\|escape }}` | Échappement HTML | + +### Exemples de Conversion + +#### Exemple 1 : Liste de Produits + +**Smarty (avant)** : +```smarty +{if $products && count($products) > 0} + + + + + + + + + + {foreach $products as $product} + + + + + + {/foreach} + +
{$locale->t('product.name')}{$locale->t('product.price')}{$locale->t('product.stock')}
{$product.name|escape:'html'}{$product.price|number_format:2} €{$product.stock}
+{else} +

{$locale->t('product.no_products')}

+{/if} +``` + +**Twig (après)** : +```twig +{% if products and products|length > 0 %} + + + + + + + + + + {% for product in products %} + + + + + + {% endfor %} + +
{{ locale.t('product.name') }}{{ locale.t('product.price') }}{{ locale.t('product.stock') }}
{{ product.name|e }}{{ product.price|number_format(2) }} €{{ product.stock }}
+{% else %} +

{{ locale.t('product.no_products') }}

+{% endif %} +``` + +#### Exemple 2 : Formulaire avec Actions + +**Smarty (avant)** : +```smarty +
+
+ + +
+ + {if $errors} +
+ {foreach $errors as $error} +

{$error|escape:'html'}

+ {/foreach} +
+ {/if} + + +
+``` + +**Twig (après)** : +```twig +
+
+ + +
+ + {% if errors %} +
+ {% for error in errors %} +

{{ error|e }}

+ {% endfor %} +
+ {% endif %} + + +
+``` + +--- + +## 🎯 Gestion des Actions AJAX + +### Dans le Contrôleur Symfony + +```php +/** + * AJAX action example: export products + * + * @AdminSecurity("is_granted('update', 'AdminLengowFeed')") + * + * @param Request $request + * @return JsonResponse + */ +public function exportProductsAction(Request $request): JsonResponse +{ + try { + // Récupérer les paramètres + $format = $request->request->get('format', 'csv'); + $limit = (int) $request->request->get('limit', 0); + + // Exécuter l'export + $result = \LengowExport::exec([ + 'format' => $format, + 'limit' => $limit, + ]); + + if ($result) { + return new JsonResponse([ + 'success' => true, + 'message' => 'Export completed successfully', + 'file_url' => $result['file_url'], + ]); + } + + return new JsonResponse([ + 'success' => false, + 'error' => 'Export failed', + ], 400); + + } catch (\Exception $e) { + return new JsonResponse([ + 'success' => false, + 'error' => $e->getMessage(), + ], 500); + } +} +``` + +### Appel AJAX depuis Twig/JavaScript + +```twig +{% block javascripts %} + +{% endblock %} +``` + +--- + +## 📝 Checklist de Migration par Page + +### ✅ Dashboard +- [ ] Créer `src/Controller/AdminDashboardController.php` +- [ ] Ajouter routes dans `config/routes.yml` +- [ ] Créer `views/templates/twig/admin/dashboard/index.html.twig` +- [ ] Tester affichage et actions AJAX +- [ ] Supprimer `controllers/admin/AdminLengowDashboardController.php` (legacy) +- [ ] Supprimer `views/templates/admin/dashboard/*.tpl` + +### ⏳ Home/Connection +- [ ] Créer `src/Controller/AdminHomeController.php` +- [ ] Ajouter routes dans `config/routes.yml` +- [ ] Créer `views/templates/twig/admin/home/index.html.twig` +- [ ] Gérer formulaire d'authentification +- [ ] Tester connexion/déconnexion +- [ ] Supprimer fichiers legacy + +### ⏳ Feed/Products +- [ ] Créer `src/Controller/AdminFeedController.php` +- [ ] Ajouter routes (liste, export, etc.) +- [ ] Créer templates Twig (index, product_list, export_form) +- [ ] Migrer logique d'export +- [ ] Tester export CSV/XML +- [ ] Supprimer fichiers legacy + +### ⏳ Orders +- [ ] Créer `src/Controller/AdminOrderController.php` +- [ ] Ajouter routes (liste, import, synchronisation) +- [ ] Créer templates Twig +- [ ] Migrer logique d'import +- [ ] Tester import et synchronisation +- [ ] Supprimer fichiers legacy + +### ⏳ Settings (Main) +- [ ] Créer `src/Controller/AdminMainSettingController.php` +- [ ] Ajouter routes +- [ ] Créer templates Twig avec formulaires +- [ ] Gérer sauvegarde configuration +- [ ] Tester tous les paramètres +- [ ] Supprimer fichiers legacy + +### ⏳ Settings (Orders) +- [ ] Créer `src/Controller/AdminOrderSettingController.php` +- [ ] Ajouter routes +- [ ] Créer templates Twig +- [ ] Gérer règles de commandes +- [ ] Tester configuration +- [ ] Supprimer fichiers legacy + +### ⏳ Toolbox +- [ ] Créer `src/Controller/AdminToolboxController.php` +- [ ] Ajouter routes +- [ ] Créer templates Twig +- [ ] Migrer outils de diagnostic +- [ ] Tester fonctionnalités +- [ ] Supprimer fichiers legacy + +### ⏳ Legals +- [ ] Créer `src/Controller/AdminLegalsController.php` +- [ ] Ajouter routes +- [ ] Créer template Twig +- [ ] Afficher mentions légales +- [ ] Supprimer fichiers legacy + +### ⏳ Help +- [ ] Créer `src/Controller/AdminHelpController.php` +- [ ] Ajouter routes +- [ ] Créer template Twig +- [ ] Afficher aide/documentation +- [ ] Supprimer fichiers legacy + +--- + +## 🧪 Tests et Validation + +### Tests Manuels par Page + +1. **Navigation** : Vérifier que tous les liens de menu fonctionnent +2. **Affichage** : Vérifier que toutes les données s'affichent correctement +3. **Formulaires** : Tester la soumission et la validation +4. **Actions AJAX** : Tester toutes les actions asynchrones +5. **Traductions** : Vérifier que les traductions s'affichent +6. **CSS/Layout** : Vérifier l'apparence sur différentes résolutions +7. **Permissions** : Tester avec différents niveaux d'accès + +### Tests de Compatibilité + +- [ ] PrestaShop 1.7.8.x +- [ ] PrestaShop 8.0.x +- [ ] PrestaShop 8.1.x +- [ ] PrestaShop 9.0.x + +### Tests de Régression + +Comparer avec l'ancienne version Smarty : +- [ ] Toutes les fonctionnalités sont présentes +- [ ] Les actions produisent les mêmes résultats +- [ ] Les messages d'erreur sont cohérents +- [ ] Les performances sont similaires ou meilleures + +--- + +## 🚀 Déploiement et Rollback + +### Stratégie de Déploiement Progressif + +1. **Phase 1** : Déployer Dashboard et Home (pages principales) +2. **Phase 2** : Déployer Feed et Orders (fonctionnalités métier) +3. **Phase 3** : Déployer Settings et utilitaires +4. **Phase 4** : Nettoyage complet (supprimer tous les fichiers legacy) + +### Plan de Rollback + +Si un problème survient : + +1. **Rollback partiel** : Restaurer uniquement la page problématique + ```bash + git checkout HEAD~1 -- src/Controller/AdminDashboardController.php + git checkout HEAD~1 -- views/templates/twig/admin/dashboard/ + ``` + +2. **Rollback complet** : Revenir à la version Smarty + ```bash + git revert + ``` + +3. **Coexistence temporaire** : Garder les deux versions actives + - Routes Symfony pour nouvelles pages + - Contrôleurs legacy pour pages non migrées + +--- + +## 📚 Ressources + +### Documentation PrestaShop +- [PrestaShop Devdocs](https://devdocs.prestashop-project.org/) +- [Symfony Controllers in PrestaShop](https://devdocs.prestashop-project.org/8/modules/concepts/controllers/admin-controllers/override-decorate-controller/) + +### Documentation Twig +- [Twig Documentation](https://twig.symfony.com/doc/3.x/) +- [Twig Filters](https://twig.symfony.com/doc/3.x/filters/index.html) +- [Twig Functions](https://twig.symfony.com/doc/3.x/functions/index.html) + +### Documentation Symfony +- [Symfony Routing](https://symfony.com/doc/current/routing.html) +- [Symfony Controllers](https://symfony.com/doc/current/controller.html) +- [Symfony Forms](https://symfony.com/doc/current/forms.html) + +--- + +## 💡 Bonnes Pratiques + +### Contrôleurs + +1. **Une action = une responsabilité** : Séparer les actions complexes +2. **Validation stricte** : Toujours valider les entrées utilisateur +3. **Gestion d'erreurs** : Utiliser try/catch pour les opérations risquées +4. **Logs** : Logger les actions importantes +5. **Permissions** : Toujours utiliser `@AdminSecurity` + +### Templates Twig + +1. **Échappement** : Toujours échapper les variables (`|e`) +2. **Réutilisation** : Créer des includes pour les composants réutilisables +3. **Lisibilité** : Indenter correctement et commenter le code +4. **Performance** : Éviter la logique complexe dans les templates +5. **Assets** : Utiliser `lengowPathUri` pour les chemins relatifs + +### Organisation du Code + +1. **DRY** : Ne pas dupliquer le code +2. **SOLID** : Respecter les principes SOLID +3. **Naming** : Noms explicites et cohérents +4. **Documentation** : Commenter les méthodes complexes +5. **Git** : Commits atomiques et descriptifs + +--- + +## ⚠️ Pièges Courants + +### Erreur 1 : Routes non trouvées +**Symptôme** : 404 sur les nouvelles URLs +**Solution** : Vérifier que `config/routes.yml` est bien chargé et que le cache est vidé + +### Erreur 2 : Templates non trouvés +**Symptôme** : `Unable to load template` +**Solution** : Vérifier le chemin complet : `@Modules/lengow/views/templates/twig/...` + +### Erreur 3 : Variables undefined dans Twig +**Symptôme** : Erreur sur variable manquante +**Solution** : S'assurer que toutes les variables sont passées au template depuis le contrôleur + +### Erreur 4 : Permissions refusées +**Symptôme** : 403 Forbidden +**Solution** : Vérifier l'annotation `@AdminSecurity` et les permissions utilisateur + +### Erreur 5 : AJAX ne fonctionne pas +**Symptôme** : Requête AJAX échoue +**Solution** : Vérifier la route, la méthode HTTP et le format de réponse (JsonResponse) + +--- + +## 🎓 Exemple Complet : Page Feed/Products + +Voici un exemple complet d'une page migrée de bout en bout. + +### Contrôleur Symfony + +**Fichier** : `src/Controller/AdminFeedController.php` + +```php +query->get('limit', 100) + ); + + // Get export formats + $formats = \LengowFeed::getAvailableFormats(); + + return $this->render('@Modules/lengow/views/templates/twig/admin/feed/index.html.twig', [ + 'locale' => $locale, + 'lengowPathUri' => $module->getPathUri(), + 'products' => $products, + 'formats' => $formats, + 'current_controller' => 'LengowFeedController', + 'export_url' => $this->generateUrl('lengow_admin_feed_export'), + ]); + } + + /** + * Export products + * + * @AdminSecurity("is_granted('update', 'AdminLengowFeed')") + */ + public function exportAction(Request $request): JsonResponse + { + try { + $format = $request->request->get('format', 'csv'); + $limit = (int) $request->request->get('limit', 0); + + $result = \LengowExport::exec([ + 'format' => $format, + 'limit' => $limit, + ]); + + if ($result) { + return new JsonResponse([ + 'success' => true, + 'file_url' => $result['file_url'], + 'exported_count' => $result['count'], + ]); + } + + return new JsonResponse([ + 'success' => false, + 'error' => 'Export failed', + ], 400); + + } catch (\Exception $e) { + return new JsonResponse([ + 'success' => false, + 'error' => $e->getMessage(), + ], 500); + } + } +} +``` + +### Template Twig + +**Fichier** : `views/templates/twig/admin/feed/index.html.twig` + +```twig +{% extends '@Modules/lengow/views/templates/twig/admin/_partials/base.html.twig' %} + +{% block title %}{{ locale.t('menu.products') }}{% endblock %} + +{% block content %} +
+

{{ locale.t('menu.products') }}

+ + {# Export Form #} +
+

{{ locale.t('feed.export_products') }}

+ +
+
+ + +
+ +
+ + + {{ locale.t('feed.limit_help') }} +
+ + +
+ + +
+ + {# Product List #} +
+

{{ locale.t('feed.exportable_products') }}

+ + {% if products|length > 0 %} + + + + + + + + + + + {% for product in products %} + + + + + + + {% endfor %} + +
{{ locale.t('product.id') }}{{ locale.t('product.name') }}{{ locale.t('product.price') }}{{ locale.t('product.stock') }}
{{ product.id }}{{ product.name|e }}{{ product.price|number_format(2) }} €{{ product.stock }}
+ {% else %} +

{{ locale.t('feed.no_products') }}

+ {% endif %} +
+
+{% endblock %} + +{% block javascripts %} + +{% endblock %} +``` + +--- + +## ✅ Conclusion + +Ce guide fournit une base solide pour migrer le module Lengow vers Symfony/Twig. L'approche recommandée est : + +1. **Commencer par une page simple** (Dashboard ou Legals) +2. **Valider le fonctionnement complet** avant de passer à la suivante +3. **Migrer progressivement** page par page +4. **Tester à chaque étape** sur différentes versions de PrestaShop +5. **Documenter les problèmes** rencontrés et solutions trouvées + +**Estimation de temps par page** : +- Page simple (Dashboard, Legals, Help) : 4-6 heures +- Page moyenne (Home, Orders) : 8-12 heures +- Page complexe (Feed, Settings) : 12-20 heures + +**Total estimé** : 80-120 heures de développement + 20-30 heures de tests + +--- + +**Dernière mise à jour** : 2026-01-05 +**Version du guide** : 1.0 +**Auteur** : GitHub Copilot pour Lengow diff --git a/classes/models/LengowList.php b/classes/models/LengowList.php index a7e617ae..6e206174 100755 --- a/classes/models/LengowList.php +++ b/classes/models/LengowList.php @@ -313,9 +313,10 @@ public function displayRow($item) break; case 'price': if (isset($item['currency'])) { - $value = Tools::displayPrice($item[$key], $this->getCurrencyByCode($item['currency'])); + $currency = $this->getCurrencyByCode($item['currency']); + $value = $this->formatPrice($item[$key], $currency); } else { - $value = Tools::displayPrice($item[$key]); + $value = $this->formatPrice($item[$key]); } break; case 'switch_product': @@ -718,4 +719,41 @@ public function getTotal() { return $this->total; } + + /** + * Format price for display (PrestaShop 9 compatibility) + * + * @param float $price Price to format + * @param Currency|null $currency Currency object (optional) + * + * @return string Formatted price + */ + protected function formatPrice($price, $currency = null) + { + if ($currency === null) { + $currency = $this->context->currency; + } + + // Use PrestaShop's locale system if available (PS 1.7.6+) + if (method_exists($this->context, 'getCurrentLocale')) { + $locale = $this->context->getCurrentLocale(); + if ($locale !== null) { + return $locale->formatPrice( + $price, + $currency->iso_code + ); + } + } + + // Fallback: manual formatting using currency properties + $blank = ($currency->format % 2 != 0) ? ' ' : ''; + $decimals = (int)$currency->decimals; + $priceFormatted = number_format($price, $decimals, '.', ''); + + if ($currency->format % 2 == 0) { + return $currency->sign . $blank . $priceFormatted; + } else { + return $priceFormatted . $blank . $currency->sign; + } + } } diff --git a/classes/models/LengowProduct.php b/classes/models/LengowProduct.php index cbb32649..5ed8ce4e 100755 --- a/classes/models/LengowProduct.php +++ b/classes/models/LengowProduct.php @@ -321,7 +321,7 @@ public function makeAttributes() foreach ($combinations as $c) { $attributeId = $c['id_product_attribute']; $priceToConvert = Tools::convertPrice($c['price'], $this->context->currency); - $price = Tools::displayPrice($priceToConvert, $this->context->currency); + $price = $this->formatPrice($priceToConvert, $this->context->currency); if (array_key_exists($attributeId, $combArray)) { $combArray[$attributeId]['attributes'][$c['group_name']] = [ $c['group_name'], @@ -1077,4 +1077,41 @@ public static function getMaxImageType() } throw new LengowException(LengowMain::setLogMessage('log.export.error_cant_find_image_size')); } + + /** + * Format price for display (PrestaShop 9 compatibility) + * + * @param float $price Price to format + * @param Currency|null $currency Currency object (optional) + * + * @return string Formatted price + */ + protected function formatPrice($price, $currency = null) + { + if ($currency === null) { + $currency = $this->context->currency; + } + + // Use PrestaShop's locale system if available (PS 1.7.6+) + if (method_exists($this->context, 'getCurrentLocale')) { + $locale = $this->context->getCurrentLocale(); + if ($locale !== null) { + return $locale->formatPrice( + $price, + $currency->iso_code + ); + } + } + + // Fallback: manual formatting using currency properties + $blank = ($currency->format % 2 != 0) ? ' ' : ''; + $decimals = (int)$currency->decimals; + $priceFormatted = number_format($price, $decimals, '.', ''); + + if ($currency->format % 2 == 0) { + return $currency->sign . $blank . $priceFormatted; + } else { + return $priceFormatted . $blank . $currency->sign; + } + } } diff --git a/config/routes.yml b/config/routes.yml deleted file mode 100644 index 2b39e009..00000000 --- a/config/routes.yml +++ /dev/null @@ -1,36 +0,0 @@ -admin_orders_view: - path: /sell/orders/{orderId}/view - methods: [ GET, POST ] - defaults: - _disable_module_prefix: true - _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrderController::viewAction' - _legacy_controller: AdminOrders - _legacy_link: AdminOrders:vieworder - _legacy_parameters: - id_order: orderId - options: - expose: true - requirements: - orderId: \d+ - -admin_orders_update_shipping: - path: /sell/orders/{orderId}/shipping - methods: [ POST ] - defaults: - _disable_module_prefix: true - _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrderController::updateShippingAction' - _legacy_controller: AdminOrders - requirements: - orderId: \d+ - -lengow_save_refund_reason: - path: /admin/lengow/refund-reason/save - methods: [ POST ] - defaults: - _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrderController::saveRefundReason' - -lengow_save_refund_mode: - path: /admin/lengow/refund-mode/save - methods: [ POST ] - defaults: - _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrderController::saveRefundMode' diff --git a/config/routes.yml.example b/config/routes.yml.example new file mode 100644 index 00000000..06870e9f --- /dev/null +++ b/config/routes.yml.example @@ -0,0 +1,180 @@ +admin_orders_view: + path: /sell/orders/{orderId}/view + methods: [ GET, POST ] + defaults: + _disable_module_prefix: true + _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrderController::viewAction' + _legacy_controller: AdminOrders + _legacy_link: AdminOrders:vieworder + _legacy_parameters: + id_order: orderId + options: + expose: true + requirements: + orderId: \d+ + +admin_orders_update_shipping: + path: /sell/orders/{orderId}/shipping + methods: [ POST ] + defaults: + _disable_module_prefix: true + _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrderController::updateShippingAction' + _legacy_controller: AdminOrders + requirements: + orderId: \d+ + +lengow_save_refund_reason: + path: /admin/lengow/refund-reason/save + methods: [ POST ] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrderController::saveRefundReason' + +lengow_save_refund_mode: + path: /admin/lengow/refund-mode/save + methods: [ POST ] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrderController::saveRefundMode' + +# Lengow Dashboard +lengow_admin_dashboard: + path: /modules/lengow/dashboard + methods: [ GET ] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminDashboardController::indexAction' + _legacy_controller: AdminLengowDashboard + +lengow_admin_dashboard_remind: + path: /modules/lengow/dashboard/remind-me-later + methods: [ POST ] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminDashboardController::remindMeLaterAction' + +# Lengow Home/Connection +lengow_admin_home: + path: /modules/lengow/home + methods: [ GET ] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminHomeController::indexAction' + _legacy_controller: AdminLengowHome + +lengow_admin_home_ajax: + path: /modules/lengow/home/ajax + methods: [ POST, GET ] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminHomeController::ajaxAction' + +# Lengow Feed/Product +lengow_admin_feed: + path: /modules/lengow/feed + methods: [ GET, POST ] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminFeedController::indexAction' + _legacy_controller: AdminLengowFeed + +lengow_admin_feed_ajax: + path: /modules/lengow/feed/ajax + methods: [ POST, GET ] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminFeedController::ajaxAction' + +# Lengow Orders +lengow_admin_order_index: + path: /modules/lengow/orders + methods: [ GET ] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrdersController::indexAction' + _legacy_controller: AdminLengowOrder + +lengow_admin_order_load_table: + path: /modules/lengow/orders/load-table + methods: [ POST, GET ] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrdersController::loadTableAction' + +lengow_admin_order_reimport: + path: /modules/lengow/orders/re-import + methods: [ POST ] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrdersController::reImportAction' + +lengow_admin_order_resend: + path: /modules/lengow/orders/re-send + methods: [ POST ] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrdersController::reSendAction' + +lengow_admin_order_import_all: + path: /modules/lengow/orders/import-all + methods: [ POST ] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrdersController::importAllAction' + +lengow_admin_order_synchronize: + path: /modules/lengow/orders/synchronize + methods: [ POST, GET ] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrdersController::synchronizeAction' + +lengow_admin_order_cancel_reimport: + path: /modules/lengow/orders/cancel-re-import + methods: [ POST, GET ] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrdersController::cancelReImportAction' + +lengow_admin_order_save_shipping_method: + path: /modules/lengow/orders/save-shipping-method + methods: [ POST ] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrdersController::saveShippingMethodAction' + +lengow_admin_order_force_resend: + path: /modules/lengow/orders/force-resend + methods: [ POST, GET ] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrdersController::forceResendAction' + +# Lengow Main Settings +lengow_admin_main_setting: + path: /modules/lengow/settings + methods: [ GET, POST ] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminMainSettingController::indexAction' + _legacy_controller: AdminLengowMainSetting + +# Lengow Order Settings +lengow_admin_order_setting: + path: /modules/lengow/order-settings + methods: [ GET, POST ] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrderSettingController::indexAction' + _legacy_controller: AdminLengowOrderSetting + +lengow_admin_order_setting_ajax: + path: /modules/lengow/order-settings/ajax + methods: [ POST, GET ] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrderSettingController::ajaxAction' + +# Lengow Toolbox +lengow_admin_toolbox: + path: /modules/lengow/toolbox + methods: [ GET ] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminToolboxController::indexAction' + _legacy_controller: AdminLengowToolbox + +# Lengow Legals +lengow_admin_legals: + path: /modules/lengow/legals + methods: [ GET ] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminLegalsController::indexAction' + _legacy_controller: AdminLengowLegals + +# Lengow Help +lengow_admin_help: + path: /modules/lengow/help + methods: [ GET ] + defaults: + _controller: 'PrestaShop\Module\Lengow\Controller\AdminHelpController::indexAction' + _legacy_controller: AdminLengowHelp diff --git a/lengow.php b/lengow.php index 29e63326..07f60e3a 100755 --- a/lengow.php +++ b/lengow.php @@ -50,7 +50,7 @@ public function __construct() $this->module_key = '__LENGOW_PRESTASHOP_PRODUCT_KEY__'; $this->ps_versions_compliancy = [ 'min' => '1.7.8', - 'max' => '8.99.99', + 'max' => '9.99.99', ]; $this->bootstrap = true; diff --git a/src/Controller/AdminDashboardController.php b/src/Controller/AdminDashboardController.php new file mode 100644 index 00000000..dcd0460c --- /dev/null +++ b/src/Controller/AdminDashboardController.php @@ -0,0 +1,98 @@ + + * @copyright 2017 Lengow SAS + * @license http://www.apache.org/licenses/LICENSE-2.0 + */ + +namespace PrestaShop\Module\Lengow\Controller; + +use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\JsonResponse; +use PrestaShopBundle\Security\Annotation\AdminSecurity; + +if (!defined('_PS_VERSION_')) { + exit; +} + +/** + * Lengow Dashboard Controller for PrestaShop 9 + */ +class AdminDashboardController extends FrameworkBundleAdminController +{ + /** + * Dashboard page + * + * @AdminSecurity("is_granted('read', 'AdminLengowDashboard')") + * + * @param Request $request + * @return Response + */ + public function indexAction(Request $request): Response + { + $locale = new \LengowTranslation(); + $lengowLink = new \LengowLink(); + $module = \Module::getInstanceByName('lengow'); + + // Get merchant status and plugin information + $merchantStatus = \LengowSync::getStatusAccount(); + $pluginData = \LengowSync::getPluginData(); + $pluginIsUpToDate = \LengowSync::isPluginUpToDate(); + $currentController = 'LengowDashboardController'; + + // Handle refresh status action + $action = $request->query->get('action'); + if ($action === 'refresh_status') { + \LengowSync::getStatusAccount(true); + return $this->redirectToRoute('lengow_admin_dashboard'); + } + + $refreshStatusUrl = $this->generateUrl('lengow_admin_dashboard', ['action' => 'refresh_status']); + + return $this->render('@Modules/lengow/views/templates/admin/dashboard/index.html.twig', [ + 'locale' => $locale, + 'lengowPathUri' => $module->getPathUri(), + 'lengowUrl' => \LengowConfiguration::getLengowUrl(), + 'lengow_link' => $lengowLink, + 'merchantStatus' => $merchantStatus, + 'pluginData' => $pluginData, + 'pluginIsUpToDate' => $pluginIsUpToDate, + 'displayToolbar' => 1, + 'current_controller' => $currentController, + 'total_pending_order' => \LengowOrder::countOrderToBeSent(), + 'refresh_status' => $refreshStatusUrl, + ]); + } + + /** + * Handle remind me later action for plugin update modal + * + * @AdminSecurity("is_granted('update', 'AdminLengowDashboard')") + * + * @param Request $request + * @return JsonResponse + */ + public function remindMeLaterAction(Request $request): JsonResponse + { + $timestamp = time() + (7 * 86400); + \LengowConfiguration::updateGlobalValue(\LengowConfiguration::LAST_UPDATE_PLUGIN_MODAL, $timestamp); + + return new JsonResponse(['success' => true]); + } +} diff --git a/src/Controller/AdminFeedController.php b/src/Controller/AdminFeedController.php new file mode 100644 index 00000000..5723d00b --- /dev/null +++ b/src/Controller/AdminFeedController.php @@ -0,0 +1,99 @@ + + * @copyright 2017 Lengow SAS + * @license http://www.apache.org/licenses/LICENSE-2.0 + */ + +namespace PrestaShop\Module\Lengow\Controller; + +use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\JsonResponse; +use PrestaShopBundle\Security\Annotation\AdminSecurity; + +if (!defined('_PS_VERSION_')) { + exit; +} + +/** + * Lengow Feed/Product Controller for PrestaShop 9 + */ +class AdminFeedController extends FrameworkBundleAdminController +{ + /** + * Feed/Product page + * + * @AdminSecurity("is_granted('read', 'AdminLengowFeed')") + * + * @param Request $request + * @return Response + */ + public function indexAction(Request $request): Response + { + $locale = new \LengowTranslation(); + $lengowLink = new \LengowLink(); + $module = \Module::getInstanceByName('lengow'); + $currentController = 'LengowFeedController'; + + // Use legacy controller for complex display logic + $lengowController = new \LengowFeedController(); + $lengowController->prepareDisplay(); + + return $this->render('@Modules/lengow/views/templates/admin/feed/index.html.twig', [ + 'locale' => $locale, + 'lengowPathUri' => $module->getPathUri(), + 'lengowUrl' => \LengowConfiguration::getLengowUrl(), + 'lengow_link' => $lengowLink, + 'displayToolbar' => 1, + 'current_controller' => $currentController, + 'total_pending_order' => \LengowOrder::countOrderToBeSent(), + 'merchantStatus' => \LengowSync::getStatusAccount(), + 'pluginData' => \LengowSync::getPluginData(), + 'pluginIsUpToDate' => \LengowSync::isPluginUpToDate(), + ]); + } + + /** + * Handle AJAX actions for feed page + * + * @AdminSecurity("is_granted('update', 'AdminLengowFeed')") + * + * @param Request $request + * @return JsonResponse + */ + public function ajaxAction(Request $request): JsonResponse + { + // Delegate to legacy controller for AJAX handling + $lengowController = new \LengowFeedController(); + + ob_start(); + $lengowController->postProcess(); + $output = ob_get_clean(); + + if (!empty($output)) { + $data = json_decode($output, true); + if (json_last_error() === JSON_ERROR_NONE) { + return new JsonResponse($data); + } + return new JsonResponse(['output' => $output]); + } + + return new JsonResponse(['success' => true]); + } +} diff --git a/src/Controller/AdminHelpController.php b/src/Controller/AdminHelpController.php new file mode 100644 index 00000000..7fe385ed --- /dev/null +++ b/src/Controller/AdminHelpController.php @@ -0,0 +1,63 @@ + + * @copyright 2017 Lengow SAS + * @license http://www.apache.org/licenses/LICENSE-2.0 + */ + +namespace PrestaShop\Module\Lengow\Controller; + +use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use PrestaShopBundle\Security\Annotation\AdminSecurity; + +if (!defined('_PS_VERSION_')) { + exit; +} + +/** + * Lengow Help Controller for PrestaShop 9 + */ +class AdminHelpController extends FrameworkBundleAdminController +{ + /** + * Help page + * + * @AdminSecurity("is_granted('read', 'AdminLengowHelp')") + * + * @param Request $request + * @return Response + */ + public function indexAction(Request $request): Response + { + $locale = new \LengowTranslation(); + $lengowLink = new \LengowLink(); + $module = \Module::getInstanceByName('lengow'); + $currentController = 'LengowHelpController'; + + return $this->render('@Modules/lengow/views/templates/admin/help/index.html.twig', [ + 'locale' => $locale, + 'lengowPathUri' => $module->getPathUri(), + 'lengowUrl' => \LengowConfiguration::getLengowUrl(), + 'lengow_link' => $lengowLink, + 'displayToolbar' => 1, + 'current_controller' => $currentController, + 'total_pending_order' => \LengowOrder::countOrderToBeSent(), + ]); + } +} diff --git a/src/Controller/AdminHomeController.php b/src/Controller/AdminHomeController.php new file mode 100644 index 00000000..cc65fc26 --- /dev/null +++ b/src/Controller/AdminHomeController.php @@ -0,0 +1,283 @@ + + * @copyright 2017 Lengow SAS + * @license http://www.apache.org/licenses/LICENSE-2.0 + */ + +namespace PrestaShop\Module\Lengow\Controller; + +use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\JsonResponse; +use PrestaShopBundle\Security\Annotation\AdminSecurity; + +if (!defined('_PS_VERSION_')) { + exit; +} + +/** + * Lengow Home/Connection Controller for PrestaShop 9 + */ +class AdminHomeController extends FrameworkBundleAdminController +{ + /** + * Home/Connection page + * + * @AdminSecurity("is_granted('read', 'AdminLengowHome')") + * + * @param Request $request + * @return Response + */ + public function indexAction(Request $request): Response + { + $locale = new \LengowTranslation(); + $lengowLink = new \LengowLink(); + $module = \Module::getInstanceByName('lengow'); + $isNewMerchant = \LengowConfiguration::isNewMerchant(); + + // If not a new merchant, redirect to dashboard + if (!$isNewMerchant) { + return $this->redirectToRoute('lengow_admin_dashboard'); + } + + $lengowAjaxLink = $this->generateUrl('lengow_admin_home_ajax'); + + return $this->render('@Modules/lengow/views/templates/admin/home/index.html.twig', [ + 'locale' => $locale, + 'lengowPathUri' => $module->getPathUri(), + 'lengow_ajax_link' => $lengowAjaxLink, + 'displayToolbar' => 0, + ]); + } + + /** + * Handle AJAX actions for home/connection page + * + * @AdminSecurity("is_granted('update', 'AdminLengowHome')") + * + * @param Request $request + * @return JsonResponse + */ + public function ajaxAction(Request $request): JsonResponse + { + $action = $request->request->get('action', $request->query->get('action')); + $module = \Module::getInstanceByName('lengow'); + + switch ($action) { + case 'go_to_credentials': + $displayContent = $module->display( + _PS_MODULE_LENGOW_DIR_, + 'views/templates/admin/home/connection_cms.html.twig' + ); + return new JsonResponse([ + 'content' => preg_replace('/\r|\n/', '', $displayContent) + ]); + + case 'connect_cms': + $accessToken = $request->request->get('accessToken', ''); + $secret = $request->request->get('secret', ''); + + $credentialsValid = $this->checkApiCredentials($accessToken, $secret); + $cmsConnected = false; + $hasCatalogToLink = false; + + if ($credentialsValid) { + $cmsConnected = $this->connectCms(); + if ($cmsConnected) { + $hasCatalogToLink = $this->hasCatalogToLink(); + } + } + + $content = $this->renderView('@Modules/lengow/views/templates/admin/home/connection_cms_result.html.twig', [ + 'credentialsValid' => $credentialsValid, + 'cmsConnected' => $cmsConnected, + 'hasCatalogToLink' => $hasCatalogToLink, + ]); + + return new JsonResponse([ + 'success' => $cmsConnected, + 'content' => preg_replace('/\r|\n/', '', $content), + ]); + + case 'go_to_catalog': + $retry = $request->request->get('retry', 'false') !== 'false'; + + if ($retry) { + \LengowConfiguration::resetCatalogIds(); + } + + $content = $this->renderView('@Modules/lengow/views/templates/admin/home/connection_catalog.html.twig', [ + 'shopCollection' => \LengowShop::getActiveShops(), + 'catalogList' => $this->getCatalogList(), + ]); + + return new JsonResponse([ + 'content' => preg_replace('/\r|\n/', '', $content) + ]); + + case 'link_catalogs': + $catalogSelected = $request->request->get('catalogSelected', []); + $catalogsLinked = true; + + if (!empty($catalogSelected)) { + $catalogsLinked = $this->saveCatalogsLinked($catalogSelected); + } + + $displayConnectionResult = $module->display( + _PS_MODULE_LENGOW_DIR_, + 'views/templates/admin/home/connection_catalog_failed.html.twig' + ); + + return new JsonResponse([ + 'success' => $catalogsLinked, + 'content' => preg_replace('/\r|\n/', '', $displayConnectionResult), + ]); + + default: + return new JsonResponse(['error' => 'Unknown action'], 400); + } + } + + /** + * Check API credentials and save them in Database + * + * @param string $accessToken access token for api + * @param string $secret secret for api + * + * @return bool + */ + private function checkApiCredentials(string $accessToken, string $secret): bool + { + $accountId = \LengowConnector::getAccountIdByCredentials($accessToken, $secret); + if ($accountId) { + return \LengowConfiguration::setAccessIds([ + \LengowConfiguration::ACCOUNT_ID => $accountId, + \LengowConfiguration::ACCESS_TOKEN => $accessToken, + \LengowConfiguration::SECRET => $secret, + ]); + } + + return false; + } + + /** + * Connect cms with Lengow + * + * @return bool + */ + private function connectCms(): bool + { + $cmsToken = \LengowMain::getToken(); + $cmsConnected = \LengowSync::syncCatalog(true); + + if (!$cmsConnected) { + $syncData = json_encode(\LengowSync::getSyncData()); + $result = \LengowConnector::queryApi(\LengowConnector::POST, \LengowConnector::API_CMS, [], $syncData); + + if (isset($result->common_account)) { + $cmsConnected = true; + $messageKey = 'log.connection.cms_creation_success'; + } else { + $messageKey = 'log.connection.cms_creation_failed'; + } + } else { + $messageKey = 'log.connection.cms_already_exist'; + } + + \LengowMain::log( + \LengowLog::CODE_CONNECTION, + \LengowMain::setLogMessage($messageKey, ['cms_token' => $cmsToken]) + ); + + if (!$cmsConnected) { + \LengowConfiguration::resetAccessIds(); + \LengowConfiguration::resetAuthorizationToken(); + } + + return $cmsConnected; + } + + /** + * Check if account has catalog to link + * + * @return bool + */ + private function hasCatalogToLink(): bool + { + $activeShops = \LengowShop::getActiveShops(true); + if (empty($activeShops)) { + return \LengowCatalog::hasCatalogNotLinked(); + } + + return false; + } + + /** + * Get all catalogs available in Lengow + * + * @return array + */ + private function getCatalogList(): array + { + $activeShops = \LengowShop::getActiveShops(true); + if (empty($activeShops)) { + return \LengowCatalog::getCatalogList(); + } + + return []; + } + + /** + * Save catalogs linked in database and send data to Lengow with call API + * + * @param array $catalogSelected + * + * @return bool + */ + private function saveCatalogsLinked(array $catalogSelected): bool + { + $catalogsByShops = []; + foreach ($catalogSelected as $catalog) { + $catalogsByShops[$catalog['shopId']] = $catalog['catalogId']; + } + + if (!empty($catalogsByShops)) { + foreach ($catalogsByShops as $idShop => $catalogIds) { + \LengowConfiguration::setCatalogIds($catalogIds, $idShop); + \LengowConfiguration::setActiveShop($idShop); + } + + \LengowConfiguration::updateGlobalValue(\LengowConfiguration::LAST_UPDATE_SETTING, time()); + + $catalogsLinked = \LengowCatalog::linkCatalogs($catalogsByShops); + $messageKey = $catalogsLinked + ? 'log.connection.link_catalog_success' + : 'log.connection.link_catalog_failed'; + + \LengowMain::log( + \LengowLog::CODE_CONNECTION, + \LengowMain::setLogMessage($messageKey) + ); + + return $catalogsLinked; + } + + return false; + } +} diff --git a/src/Controller/AdminLegalsController.php b/src/Controller/AdminLegalsController.php new file mode 100644 index 00000000..1f909f7f --- /dev/null +++ b/src/Controller/AdminLegalsController.php @@ -0,0 +1,63 @@ + + * @copyright 2017 Lengow SAS + * @license http://www.apache.org/licenses/LICENSE-2.0 + */ + +namespace PrestaShop\Module\Lengow\Controller; + +use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use PrestaShopBundle\Security\Annotation\AdminSecurity; + +if (!defined('_PS_VERSION_')) { + exit; +} + +/** + * Lengow Legals Controller for PrestaShop 9 + */ +class AdminLegalsController extends FrameworkBundleAdminController +{ + /** + * Legals page + * + * @AdminSecurity("is_granted('read', 'AdminLengowLegals')") + * + * @param Request $request + * @return Response + */ + public function indexAction(Request $request): Response + { + $locale = new \LengowTranslation(); + $lengowLink = new \LengowLink(); + $module = \Module::getInstanceByName('lengow'); + $currentController = 'LengowLegalsController'; + + return $this->render('@Modules/lengow/views/templates/admin/legals/index.html.twig', [ + 'locale' => $locale, + 'lengowPathUri' => $module->getPathUri(), + 'lengowUrl' => \LengowConfiguration::getLengowUrl(), + 'lengow_link' => $lengowLink, + 'displayToolbar' => 0, + 'current_controller' => $currentController, + 'total_pending_order' => \LengowOrder::countOrderToBeSent(), + ]); + } +} diff --git a/src/Controller/AdminMainSettingController.php b/src/Controller/AdminMainSettingController.php new file mode 100644 index 00000000..196abd4c --- /dev/null +++ b/src/Controller/AdminMainSettingController.php @@ -0,0 +1,77 @@ + + * @copyright 2017 Lengow SAS + * @license http://www.apache.org/licenses/LICENSE-2.0 + */ + +namespace PrestaShop\Module\Lengow\Controller; + +use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\JsonResponse; +use PrestaShopBundle\Security\Annotation\AdminSecurity; + +if (!defined('_PS_VERSION_')) { + exit; +} + +/** + * Lengow Main Setting Controller for PrestaShop 9 + */ +class AdminMainSettingController extends FrameworkBundleAdminController +{ + /** + * Main Settings page + * + * @AdminSecurity("is_granted('read', 'AdminLengowMainSetting')") + * + * @param Request $request + * @return Response + */ + public function indexAction(Request $request): Response + { + $locale = new \LengowTranslation(); + $lengowLink = new \LengowLink(); + $module = \Module::getInstanceByName('lengow'); + $currentController = 'LengowMainSettingController'; + + // Process form submission + if ($request->isMethod('POST')) { + $lengowController = new \LengowMainSettingController(); + $lengowController->postProcess(); + } + + // Prepare display data using legacy controller + $lengowController = new \LengowMainSettingController(); + $lengowController->prepareDisplay(); + + return $this->render('@Modules/lengow/views/templates/admin/main_setting/index.html.twig', [ + 'locale' => $locale, + 'lengowPathUri' => $module->getPathUri(), + 'lengowUrl' => \LengowConfiguration::getLengowUrl(), + 'lengow_link' => $lengowLink, + 'displayToolbar' => 1, + 'current_controller' => $currentController, + 'total_pending_order' => \LengowOrder::countOrderToBeSent(), + 'merchantStatus' => \LengowSync::getStatusAccount(), + 'pluginData' => \LengowSync::getPluginData(), + 'pluginIsUpToDate' => \LengowSync::isPluginUpToDate(), + ]); + } +} diff --git a/src/Controller/AdminOrderController.php b/src/Controller/AdminOrderController.php deleted file mode 100644 index f5759a19..00000000 --- a/src/Controller/AdminOrderController.php +++ /dev/null @@ -1,632 +0,0 @@ - - * @copyright 2017 Lengow SAS - * @license http://www.apache.org/licenses/LICENSE-2.0 - */ - -namespace PrestaShop\Module\Lengow\Controller; - -use PrestaShop\PrestaShop\Core\Domain\CartRule\Exception\InvalidCartRuleDiscountValueException; -use PrestaShop\PrestaShop\Core\Domain\Order\Command\UpdateOrderShippingDetailsCommand; -use PrestaShop\PrestaShop\Core\Domain\Order\Exception\CannotEditDeliveredOrderProductException; -use PrestaShop\PrestaShop\Core\Domain\Order\Exception\CannotFindProductInOrderException; -use PrestaShop\PrestaShop\Core\Domain\Order\Exception\DuplicateProductInOrderException; -use PrestaShop\PrestaShop\Core\Domain\Order\Exception\DuplicateProductInOrderInvoiceException; -use PrestaShop\PrestaShop\Core\Domain\Order\Exception\InvalidAmountException; -use PrestaShop\PrestaShop\Core\Domain\Order\Exception\InvalidCancelProductException; -use PrestaShop\PrestaShop\Core\Domain\Order\Exception\InvalidOrderStateException; -use PrestaShop\PrestaShop\Core\Domain\Order\Exception\InvalidProductQuantityException; -use PrestaShop\PrestaShop\Core\Domain\Order\Exception\NegativePaymentAmountException; -use PrestaShop\PrestaShop\Core\Domain\Order\Exception\OrderConstraintException; -use PrestaShop\PrestaShop\Core\Domain\Order\Exception\OrderEmailSendException; -use PrestaShop\PrestaShop\Core\Domain\Order\Exception\OrderException; -use PrestaShop\PrestaShop\Core\Domain\Order\Exception\OrderNotFoundException; -use PrestaShop\PrestaShop\Core\Domain\Order\Exception\TransistEmailSendingException; -use PrestaShop\PrestaShop\Core\Domain\Order\Query\GetOrderForViewing; -use PrestaShop\PrestaShop\Core\Domain\Order\QueryResult\OrderForViewing; -use PrestaShop\PrestaShop\Core\Domain\Product\Exception\ProductOutOfStockException; -use PrestaShop\PrestaShop\Core\Domain\ValueObject\QuerySorting; -use PrestaShop\PrestaShop\Core\Order\OrderSiblingProviderInterface; -use PrestaShopBundle\Controller\Admin\Sell\Order\ActionsBarButtonsCollection; -use PrestaShopBundle\Controller\Admin\Sell\Order\OrderController; -use PrestaShopBundle\Exception\InvalidModuleException; -use PrestaShopBundle\Form\Admin\Sell\Customer\PrivateNoteType; -use PrestaShopBundle\Form\Admin\Sell\Order\AddOrderCartRuleType; -use PrestaShopBundle\Form\Admin\Sell\Order\AddProductRowType; -use PrestaShopBundle\Form\Admin\Sell\Order\ChangeOrderAddressType; -use PrestaShopBundle\Form\Admin\Sell\Order\ChangeOrderCurrencyType; -use PrestaShopBundle\Form\Admin\Sell\Order\EditProductRowType; -use PrestaShopBundle\Form\Admin\Sell\Order\InternalNoteType; -use PrestaShopBundle\Form\Admin\Sell\Order\OrderMessageType; -use PrestaShopBundle\Form\Admin\Sell\Order\OrderPaymentType; -use PrestaShopBundle\Form\Admin\Sell\Order\UpdateOrderShippingType; -use PrestaShopBundle\Form\Admin\Sell\Order\UpdateOrderStatusType; -use PrestaShopBundle\Security\Annotation\AdminSecurity; -use Symfony\Component\Form\Extension\Core\Type\ChoiceType; -use Symfony\Component\Form\Extension\Core\Type\TextType; -use Symfony\Component\HttpFoundation\JsonResponse; -use Symfony\Component\HttpFoundation\RedirectResponse; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; - -if (!defined('_PS_VERSION_')) { - exit; -} - -class AdminOrderController extends OrderController -{ - /** - * @AdminSecurity("is_granted('read', request.get('_legacy_controller'))") - * - * @param int $orderId - * @param Request $request - * - * @return Response - */ - public function viewAction(int $orderId, Request $request): Response - { - try { - if (!$this->isFromLengow($orderId)) { - return parent::viewAction($orderId, $request); - } - /** @var OrderForViewing $orderForViewing */ - $orderForViewing = $this->getQueryBus()->handle(new GetOrderForViewing($orderId, QuerySorting::DESC)); - } catch (OrderException $e) { - $this->addFlash('error', $this->getErrorMessageForException($e, $this->getErrorMessages($e))); - - return $this->redirectToRoute('admin_orders_index'); - } - $locale = new \LengowTranslation(); - $formFactory = $this->get('form.factory'); - $updateOrderStatusForm = $formFactory->createNamed( - 'update_order_status', - UpdateOrderStatusType::class, - [ - 'new_order_status_id' => $orderForViewing->getHistory()->getCurrentOrderStatusId(), - ] - ); - $updateOrderStatusActionBarForm = $formFactory->createNamed( - 'update_order_status_action_bar', - UpdateOrderStatusType::class, - [ - 'new_order_status_id' => $orderForViewing->getHistory()->getCurrentOrderStatusId(), - ] - ); - - $addOrderCartRuleForm = $this->createForm(AddOrderCartRuleType::class, [], [ - 'order_id' => $orderId, - ]); - $addOrderPaymentForm = $this->createForm(OrderPaymentType::class, [ - 'id_currency' => $orderForViewing->getCurrencyId(), - ], [ - 'id_order' => $orderId, - ]); - - $orderMessageForm = $this->createForm(OrderMessageType::class, [ - 'lang_id' => $orderForViewing->getCustomer()->getLanguageId(), - ], [ - 'action' => $this->generateUrl('admin_orders_send_message', ['orderId' => $orderId]), - ]); - $orderMessageForm->handleRequest($request); - - $changeOrderCurrencyForm = $this->createForm(ChangeOrderCurrencyType::class, [], [ - 'current_currency_id' => $orderForViewing->getCurrencyId(), - ]); - - $changeOrderAddressForm = null; - $privateNoteForm = null; - - if (null !== $orderForViewing->getCustomer() && $orderForViewing->getCustomer()->getId() !== 0) { - $changeOrderAddressForm = $this->createForm(ChangeOrderAddressType::class, [], [ - 'customer_id' => $orderForViewing->getCustomer()->getId(), - ]); - - $privateNoteForm = $this->createForm(PrivateNoteType::class, [ - 'note' => $orderForViewing->getCustomer()->getPrivateNote(), - ]); - } - - $updateOrderShippingForm = $this->createForm(UpdateOrderShippingType::class, [ - 'new_carrier_id' => $orderForViewing->getCarrierId(), - ], [ - 'order_id' => $orderId, - ]); - $isActiveReturnCarrier = false; - $isActiveReturnTrackingNumber = false; - if ($this->isFromLengow($orderId)) { - $isActiveReturnTrackingNumber = $this->isActiveReturnTrackingNumber($orderId); - $isActiveReturnCarrier = $this->isActiveReturnTrackingCarrier($orderId); - } - - if ($isActiveReturnTrackingNumber) { - $returnTrackingNumber = $this->getReturnTrackingNumber($orderId); - $updateOrderShippingForm->add(\LengowAction::ARG_RETURN_TRACKING_NUMBER, TextType::class, [ - 'required' => false, - 'data' => $returnTrackingNumber, - ]); - } - - if ($isActiveReturnCarrier) { - $returnCarrier = $this->getReturnCarrier($orderId); - $updateOrderShippingForm->add(\LengowAction::ARG_RETURN_CARRIER, ChoiceType::class, [ - 'required' => false, - 'data' => $returnCarrier, - 'choices' => \LengowCarrier::getCarriersChoices( - $orderForViewing->getCustomer()->getLanguageId() - ), - ]); - } - $currencyDataProvider = $this->container->get('prestashop.adapter.data_provider.currency'); - $orderCurrency = $currencyDataProvider->getCurrencyById($orderForViewing->getCurrencyId()); - - $addProductRowForm = $this->createForm(AddProductRowType::class, [], [ - 'order_id' => $orderId, - 'currency_id' => $orderForViewing->getCurrencyId(), - 'symbol' => $orderCurrency->symbol, - ]); - $editProductRowForm = $this->createForm(EditProductRowType::class, [], [ - 'order_id' => $orderId, - 'symbol' => $orderCurrency->symbol, - ]); - - $internalNoteForm = $this->createForm(InternalNoteType::class, [ - 'note' => $orderForViewing->getNote(), - ]); - - $formBuilder = $this->get('prestashop.core.form.identifiable_object.builder.cancel_product_form_builder'); - $backOfficeOrderButtons = new ActionsBarButtonsCollection(); - - try { - $this->dispatchHook( - 'actionGetAdminOrderButtons', - [ - 'controller' => $this, - 'id_order' => $orderId, - 'actions_bar_buttons_collection' => $backOfficeOrderButtons, - ] - ); - - $cancelProductForm = $formBuilder->getFormFor($orderId); - if ($this->isFromLengow($orderId)) { - $lengowOrder = new \LengowOrder($orderId); - $marketplace = $lengowOrder->getMarketplace(); - $refundReasons = $marketplace->getRefundReasons(); - $refundMode = $marketplace->getRefundModes(); - $refundSelectedDatas = $lengowOrder->getRefundDataFromLengowOrder($orderId, $marketplace->name); - } - } catch (\Exception $e) { - $this->addFlash('error', $this->getErrorMessageForException($e, $this->getErrorMessages($e))); - - return $this->redirectToRoute('admin_orders_index'); - } - - $this->handleOutOfStockProduct($orderForViewing); - - $merchandiseReturnEnabled = (bool) $this->configuration->get('PS_ORDER_RETURN'); - - /** @var OrderSiblingProviderInterface $orderSiblingProvider */ - $orderSiblingProvider = $this->get('prestashop.adapter.order.order_sibling_provider'); - - $paginationNum = (int) $this->configuration->get('PS_ORDER_PRODUCTS_NB_PER_PAGE', self::DEFAULT_PRODUCTS_NUMBER); - $paginationNumOptions = self::PRODUCTS_PAGINATION_OPTIONS; - if (!in_array($paginationNum, $paginationNumOptions)) { - $paginationNumOptions[] = $paginationNum; - } - sort($paginationNumOptions); - $metatitle = sprintf( - '%s %s %s', - $this->trans('Orders', 'Admin.Orderscustomers.Feature'), - $this->configuration->get('PS_NAVIGATION_PIPE', '>'), - $this->trans( - 'Order %reference% from %firstname% %lastname%', - 'Admin.Orderscustomers.Feature', - [ - '%reference%' => $orderForViewing->getReference(), - '%firstname%' => $orderForViewing->getCustomer()->getFirstName(), - '%lastname%' => $orderForViewing->getCustomer()->getLastName(), - ] - ) - ); - - return $this->render('@PrestaShop/Admin/Sell/Order/Order/view.html.twig', [ - 'showContentHeader' => true, - 'enableSidebar' => true, - 'orderCurrency' => $orderCurrency, - 'meta_title' => $metatitle, - 'help_link' => $this->generateSidebarLink($request->attributes->get('_legacy_controller')), - 'orderForViewing' => $orderForViewing, - 'addOrderCartRuleForm' => $addOrderCartRuleForm->createView(), - 'updateOrderStatusForm' => $updateOrderStatusForm->createView(), - 'updateOrderStatusActionBarForm' => $updateOrderStatusActionBarForm->createView(), - 'addOrderPaymentForm' => $addOrderPaymentForm->createView(), - 'changeOrderCurrencyForm' => $changeOrderCurrencyForm->createView(), - 'privateNoteForm' => $privateNoteForm ? $privateNoteForm->createView() : null, - 'updateOrderShippingForm' => $updateOrderShippingForm->createView(), - 'cancelProductForm' => $cancelProductForm->createView(), - 'invoiceManagementIsEnabled' => $orderForViewing->isInvoiceManagementIsEnabled(), - 'changeOrderAddressForm' => $changeOrderAddressForm ? $changeOrderAddressForm->createView() : null, - 'orderMessageForm' => $orderMessageForm->createView(), - 'addProductRowForm' => $addProductRowForm->createView(), - 'editProductRowForm' => $editProductRowForm->createView(), - 'backOfficeOrderButtons' => $backOfficeOrderButtons, - 'merchandiseReturnEnabled' => $merchandiseReturnEnabled, - 'priceSpecification' => $this->getContextLocale()->getPriceSpecification($orderCurrency->iso_code)->toArray(), - 'previousOrderId' => $orderSiblingProvider->getPreviousOrderId($orderId), - 'nextOrderId' => $orderSiblingProvider->getNextOrderId($orderId), - 'paginationNum' => $paginationNum, - 'paginationNumOptions' => $paginationNumOptions, - 'isAvailableQuantityDisplayed' => $this->configuration->getBoolean('PS_STOCK_MANAGEMENT'), - 'internalNoteForm' => $internalNoteForm->createView(), - 'returnTrackingNumber' => $this->getReturnTrackingNumber($orderId), - 'returnCarrier' => $this->getReturnCarrier($orderId), - 'isActiveReturnTrackingNumber' => $isActiveReturnTrackingNumber, - 'isActiveReturnCarrier' => $isActiveReturnCarrier, - 'returnTrackingNumberLabel' => $locale->t('order.screen.return_tracking_number_label'), - 'returnCarrierLabel' => $locale->t('order.screen.return_carrier_label'), - 'returnCarrierName' => $this->getReturnCarrierName($orderId), - 'refundReasons' => $refundReasons ?? [], - 'refundModes' => $refundMode ?? [], - 'refundReasonSelected' => $refundSelectedDatas['refund_reason'] ?? '', - 'refundModeSelected' => $refundSelectedDatas['refund_mode'] ?? '', - ]); - } - - /** - * @AdminSecurity( - * "is_granted('update', request.get('_legacy_controller'))", - * redirectRoute="admin_orders_view", - * redirectQueryParamsToKeep={"orderId"}, - * message="You do not have permission to edit this." - * ) - * - * @param int $orderId - * @param Request $request - * - * @return RedirectResponse - */ - public function updateShippingAction(int $orderId, Request $request): RedirectResponse - { - $form = $this->createForm(UpdateOrderShippingType::class, [], [ - 'order_id' => $orderId, - ]); - - if ($this->isFromLengow($orderId)) { - if ($this->isActiveReturnTrackingNumber($orderId)) { - $form->add(\LengowAction::ARG_RETURN_TRACKING_NUMBER, TextType::class, [ - 'required' => false, - ]); - } - if ($this->isActiveReturnTrackingCarrier($orderId)) { - $order = new \LengowOrder($orderId); - $form->add(\LengowAction::ARG_RETURN_CARRIER, ChoiceType::class, [ - 'required' => false, - 'choices' => \LengowCarrier::getCarriersChoices( - $order->id_lang - ), - ]); - } - } - - $form->handleRequest($request); - - if ($form->isSubmitted() && $form->isValid()) { - $data = $form->getData(); - - try { - if (!empty($data[\LengowAction::ARG_RETURN_TRACKING_NUMBER])) { - \LengowOrderDetail::updateOrderReturnTrackingNumber( - $data[\LengowAction::ARG_RETURN_TRACKING_NUMBER], - $orderId - ); - } - if (!empty($data[\LengowAction::ARG_RETURN_CARRIER])) { - \LengowOrderDetail::updateOrderReturnCarrier( - (int) $data[\LengowAction::ARG_RETURN_CARRIER], - $orderId - ); - } - $this->getCommandBus()->handle( - new UpdateOrderShippingDetailsCommand( - $orderId, - (int) $data['current_order_carrier_id'], - (int) $data['new_carrier_id'], - $data['tracking_number'] - ) - ); - - $this->addFlash('success', $this->trans('Successful update.', 'Admin.Notifications.Success')); - } catch (TransistEmailSendingException $e) { - $this->addFlash( - 'error', - $this->trans( - 'An error occurred while sending an email to the customer.', - 'Admin.Orderscustomers.Notification' - ) - ); - } catch (\Exception $e) { - $this->addFlash('error', $this->getErrorMessageForException($e, $this->getErrorMessages($e))); - } - } else { - // exit ('form not valid'); - } - - return $this->redirectToRoute('admin_orders_view', [ - 'orderId' => $orderId, - ]); - } - - /** - * @param OrderForViewing $orderForViewing - */ - private function handleOutOfStockProduct(OrderForViewing $orderForViewing) - { - $isStockManagementEnabled = $this->configuration->getBoolean('PS_STOCK_MANAGEMENT'); - if (!$isStockManagementEnabled || $orderForViewing->isDelivered() || $orderForViewing->isShipped()) { - return; - } - - foreach ($orderForViewing->getProducts()->getProducts() as $product) { - if ($product->getAvailableQuantity() <= 0) { - $this->addFlash( - 'warning', - $this->trans('This product is out of stock:', 'Admin.Orderscustomers.Notification') . ' ' . $product->getName() - ); - } - } - } - - /** - * @param \Exception $e - * - * @return array - */ - private function getErrorMessages(\Exception $e) - { - $refundableQuantity = 0; - if ($e instanceof InvalidCancelProductException) { - $refundableQuantity = $e->getRefundableQuantity(); - } - $orderInvoiceNumber = '#unknown'; - if ($e instanceof DuplicateProductInOrderInvoiceException) { - $orderInvoiceNumber = $e->getOrderInvoiceNumber(); - } - - return [ - CannotEditDeliveredOrderProductException::class => $this->trans('You cannot edit the cart once the order delivered.', 'Admin.Orderscustomers.Notification'), - OrderNotFoundException::class => $e instanceof OrderNotFoundException ? - $this->trans( - 'Order #%d cannot be loaded.', - 'Admin.Orderscustomers.Notification', - ['#%d' => $e->getOrderId()->getValue()] - ) : '', - OrderEmailSendException::class => $this->trans( - 'An error occurred while sending the e-mail to the customer.', - 'Admin.Orderscustomers.Notification' - ), - OrderException::class => $this->trans( - $e->getMessage(), - 'Admin.Orderscustomers.Notification' - ), - InvalidAmountException::class => $this->trans( - 'Only numbers and decimal points (".") are allowed in the amount fields, e.g. 10.50 or 1050.', - 'Admin.Orderscustomers.Notification' - ), - InvalidCartRuleDiscountValueException::class => [ - InvalidCartRuleDiscountValueException::INVALID_MIN_PERCENT => $this->trans( - 'Percent value must be greater than 0.', - 'Admin.Orderscustomers.Notification' - ), - InvalidCartRuleDiscountValueException::INVALID_MAX_PERCENT => $this->trans( - 'Percent value cannot exceed 100.', - 'Admin.Orderscustomers.Notification' - ), - InvalidCartRuleDiscountValueException::INVALID_MIN_AMOUNT => $this->trans( - 'Amount value must be greater than 0.', - 'Admin.Orderscustomers.Notification' - ), - InvalidCartRuleDiscountValueException::INVALID_MAX_AMOUNT => $this->trans( - 'Discount value cannot exceed the total price of this order.', - 'Admin.Orderscustomers.Notification' - ), - InvalidCartRuleDiscountValueException::INVALID_FREE_SHIPPING => $this->trans( - 'Shipping discount value cannot exceed the total price of this order.', - 'Admin.Orderscustomers.Notification' - ), - ], - InvalidCancelProductException::class => [ - InvalidCancelProductException::INVALID_QUANTITY => $this->trans( - 'Positive product quantity is required.', - 'Admin.Notifications.Error' - ), - InvalidCancelProductException::QUANTITY_TOO_HIGH => $this->trans( - 'Please enter a maximum quantity of [1].', - 'Admin.Orderscustomers.Notification', - ['[1]' => $refundableQuantity] - ), - InvalidCancelProductException::NO_REFUNDS => $this->trans( - 'Please select at least one product.', - 'Admin.Orderscustomers.Notification' - ), - InvalidCancelProductException::INVALID_AMOUNT => $this->trans( - 'Please enter a positive amount.', - 'Admin.Orderscustomers.Notification' - ), - InvalidCancelProductException::NO_GENERATION => $this->trans( - 'Please generate at least one credit slip or voucher.', - 'Admin.Orderscustomers.Notification' - ), - ], - InvalidModuleException::class => $this->trans( - 'You must choose a payment module to create the order.', - 'Admin.Orderscustomers.Notification' - ), - ProductOutOfStockException::class => $this->trans( - 'There are not enough products in stock.', - 'Admin.Catalog.Notification' - ), - NegativePaymentAmountException::class => $this->trans( - 'Invalid value: the payment must be a positive amount.', - 'Admin.Notifications.Error' - ), - InvalidOrderStateException::class => [ - InvalidOrderStateException::ALREADY_PAID => $this->trans( - 'Invalid action: this order has already been paid.', - 'Admin.Notifications.Error' - ), - InvalidOrderStateException::DELIVERY_NOT_FOUND => $this->trans( - 'Invalid action: this order has not been delivered.', - 'Admin.Notifications.Error' - ), - InvalidOrderStateException::UNEXPECTED_DELIVERY => $this->trans( - 'Invalid action: this order has already been delivered.', - 'Admin.Notifications.Error' - ), - InvalidOrderStateException::NOT_PAID => $this->trans( - 'Invalid action: this order has not been paid.', - 'Admin.Notifications.Error' - ), - InvalidOrderStateException::INVALID_ID => $this->trans( - 'You must choose an order status to create the order.', - 'Admin.Orderscustomers.Notification' - ), - ], - - OrderConstraintException::class => [ - OrderConstraintException::INVALID_CUSTOMER_MESSAGE => $this->trans( - 'The order message given is invalid.', - 'Admin.Orderscustomers.Notification' - ), - ], - InvalidProductQuantityException::class => $this->trans( - 'Positive product quantity is required.', - 'Admin.Notifications.Error' - ), - DuplicateProductInOrderException::class => $this->trans( - 'This product is already in your order, please edit the quantity instead.', - 'Admin.Notifications.Error' - ), - DuplicateProductInOrderInvoiceException::class => $this->trans( - 'This product is already in the invoice [1], please edit the quantity instead.', - 'Admin.Notifications.Error', - ['[1]' => $orderInvoiceNumber] - ), - CannotFindProductInOrderException::class => $this->trans( - 'You cannot edit the price of a product that no longer exists in your catalog.', - 'Admin.Notifications.Error' - ), - ]; - } - - /** - * @return bool - */ - private function isActiveReturnTrackingNumber(int $orderId): bool - { - $lengowOrder = new \LengowOrder($orderId); - if ($lengowOrder->getMarketplace()) { - return $lengowOrder->getMarketplace()->hasReturnTrackingNumber(); - } - - return false; - } - - /** - * @return bool - */ - private function isActiveReturnTrackingCarrier(int $orderId): bool - { - $lengowOrder = new \LengowOrder($orderId); - if ($lengowOrder->getMarketplace()) { - return $lengowOrder->getMarketplace()->hasReturnTrackingCarrier(); - } - - return false; - } - - /** - * @param int $orderId - * - * @return string - */ - private function getReturnTrackingNumber(int $orderId): string - { - return \LengowOrderDetail::getOrderReturnTrackingNumber($orderId); - } - - /** - * @param int $orderId - * - * @return string - */ - private function getReturnCarrier(int $orderId): string - { - return \LengowOrderDetail::getOrderReturnCarrier($orderId); - } - - /** - * @param int $orderId - * - * @return string - */ - private function getReturnCarrierName(int $orderId): string - { - return \LengowOrderDetail::getOrderReturnCarrierName($orderId); - } - - /** - * @param int $orderId - */ - private function isFromLengow(int $orderId): bool - { - return \LengowOrder::isFromLengow($orderId); - } - - public function saveRefundReason(Request $request) - { - $data = json_decode($request->getContent(), true); - $orderId = (int) $data['orderId']; - $reason = $data['reason'] ?? ''; - - if (empty($orderId) || empty($reason)) { - return new JsonResponse(['success' => false, 'message' => 'Données manquantes']); - } - - \Db::getInstance()->update('lengow_orders', [ - 'refund_reason' => pSQL($reason), - ], 'id_order = ' . (int) $orderId); - - return new JsonResponse(['success' => true]); - } - - public function saveRefundMode(Request $request) - { - $data = json_decode($request->getContent(), true); - $orderId = (int) $data['orderId']; - $reason = $data['mode'] ?? ''; - - if (empty($orderId) || empty($reason)) { - return new JsonResponse(['success' => false, 'message' => 'Données manquantes']); - } - - \Db::getInstance()->update('lengow_orders', [ - 'refund_mode' => pSQL($reason), - ], 'id_order = ' . (int) $orderId); - - return new JsonResponse(['success' => true]); - } -} diff --git a/src/Controller/AdminOrderSettingController.php b/src/Controller/AdminOrderSettingController.php new file mode 100644 index 00000000..af38cb9e --- /dev/null +++ b/src/Controller/AdminOrderSettingController.php @@ -0,0 +1,105 @@ + + * @copyright 2017 Lengow SAS + * @license http://www.apache.org/licenses/LICENSE-2.0 + */ + +namespace PrestaShop\Module\Lengow\Controller; + +use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\JsonResponse; +use PrestaShopBundle\Security\Annotation\AdminSecurity; + +if (!defined('_PS_VERSION_')) { + exit; +} + +/** + * Lengow Order Settings Controller for PrestaShop 9 + */ +class AdminOrderSettingController extends FrameworkBundleAdminController +{ + /** + * Order Settings page + * + * @AdminSecurity("is_granted('read', 'AdminLengowOrderSetting')") + * + * @param Request $request + * @return Response + */ + public function indexAction(Request $request): Response + { + $locale = new \LengowTranslation(); + $lengowLink = new \LengowLink(); + $module = \Module::getInstanceByName('lengow'); + $currentController = 'LengowOrderSettingController'; + + // Process form submission + if ($request->isMethod('POST')) { + $lengowController = new \LengowOrderSettingController(); + $lengowController->postProcess(); + } + + // Prepare display data using legacy controller + $lengowController = new \LengowOrderSettingController(); + $lengowController->prepareDisplay(); + + return $this->render('@Modules/lengow/views/templates/admin/order_setting/index.html.twig', [ + 'locale' => $locale, + 'lengowPathUri' => $module->getPathUri(), + 'lengowUrl' => \LengowConfiguration::getLengowUrl(), + 'lengow_link' => $lengowLink, + 'displayToolbar' => 1, + 'current_controller' => $currentController, + 'total_pending_order' => \LengowOrder::countOrderToBeSent(), + 'merchantStatus' => \LengowSync::getStatusAccount(), + 'pluginData' => \LengowSync::getPluginData(), + 'pluginIsUpToDate' => \LengowSync::isPluginUpToDate(), + ]); + } + + /** + * Handle AJAX actions for order settings page + * + * @AdminSecurity("is_granted('update', 'AdminLengowOrderSetting')") + * + * @param Request $request + * @return JsonResponse + */ + public function ajaxAction(Request $request): JsonResponse + { + // Delegate to legacy controller for AJAX handling + $lengowController = new \LengowOrderSettingController(); + + ob_start(); + $lengowController->postProcess(); + $output = ob_get_clean(); + + if (!empty($output)) { + $data = json_decode($output, true); + if (json_last_error() === JSON_ERROR_NONE) { + return new JsonResponse($data); + } + return new JsonResponse(['output' => $output]); + } + + return new JsonResponse(['success' => true]); + } +} diff --git a/src/Controller/AdminOrdersController.php b/src/Controller/AdminOrdersController.php new file mode 100644 index 00000000..d1b9e8fe --- /dev/null +++ b/src/Controller/AdminOrdersController.php @@ -0,0 +1,485 @@ + + * @copyright 2017 Lengow SAS + * @license http://www.apache.org/licenses/LICENSE-2.0 + */ + +namespace PrestaShop\Module\Lengow\Controller; + +use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\JsonResponse; +use PrestaShopBundle\Security\Annotation\AdminSecurity; +use Shop; +use Tools; +use Module; + +if (!defined('_PS_VERSION_')) { + exit; +} + +/** + * Lengow Orders List Controller for PrestaShop 8+/9 + * + * This controller handles the Lengow orders list page with complete + * migration from Smarty to Twig templates for PrestaShop 8+/9 compatibility. + */ +class AdminOrdersController extends FrameworkBundleAdminController +{ + /** + * Display orders list page + * + * @AdminSecurity("is_granted('read', 'AdminLengowOrder')") + * + * @param Request $request + * @return Response + */ + public function indexAction(Request $request): Response + { + $locale = new \LengowTranslation(); + $lengowLink = new \LengowLink(); + $module = Module::getInstanceByName('lengow'); + + // Get order controller instance + $lengowOrderController = new \LengowOrderController(); + + // Get last import information + $lastImport = \LengowMain::getLastImport(); + $orderCollection = [ + 'last_import_date' => $lastImport['timestamp'] !== 'none' + ? \LengowMain::getDateInCorrectFormat($lastImport['timestamp']) + : '', + 'last_import_type' => $lastImport['type'], + 'link' => \LengowMain::getCronUrl(), + ]; + + // Get report mail settings + $reportMailEnabled = (bool) \LengowConfiguration::getGlobalValue(\LengowConfiguration::REPORT_MAIL_ENABLED); + $reportMailAddress = \LengowConfiguration::getReportEmailAddress(); + + // Get number of imported orders + $sql = 'SELECT COUNT(*) as `total` FROM `' . _DB_PREFIX_ . 'lengow_orders`'; + try { + $totalOrders = \Db::getInstance()->executeS($sql); + $nbOrderImported = (int) $totalOrders[0]['total']; + } catch (\PrestaShopDatabaseException $e) { + $nbOrderImported = 0; + } + + // Get warning messages + $warningMessages = []; + if (\LengowConfiguration::debugModeIsActive()) { + $warningMessages[] = $locale->t( + 'order.screen.debug_warning_message', + ['url' => $lengowLink->getAbsoluteAdminLink('AdminLengowMainSetting')] + ); + } + if (\LengowCarrier::hasDefaultCarrierNotMatched()) { + $warningMessages[] = $locale->t( + 'order.screen.no_carrier_warning_message', + ['url' => $lengowLink->getAbsoluteAdminLink('AdminLengowOrderSetting')] + ); + } + $warningMessage = !empty($warningMessages) ? join('
', $warningMessages) : false; + + // Build orders table + $lengowTable = $lengowOrderController->buildTable(); + + // Get plugin information + $pluginData = \LengowSync::getPluginData(); + $pluginIsUpToDate = true; + if ($pluginData && version_compare($pluginData['version'], $module->version, '>')) { + $pluginIsUpToDate = false; + } + + // Get system information + $multiShop = Shop::isFeatureActive(); + $debugMode = \LengowConfiguration::debugModeIsActive(); + + return $this->render('@Modules/lengow/views/templates/twig/admin/orders/index.html.twig', [ + // Translation and links + 'locale' => $locale, + 'lengowPathUri' => $module->getPathUri(), + 'lengowUrl' => \LengowConfiguration::getLengowUrl(), + 'lengow_link' => $lengowLink, + + // Order data + 'orderCollection' => $orderCollection, + 'reportMailEnabled' => $reportMailEnabled, + 'report_mail_address' => $reportMailAddress, + 'nb_order_imported' => $nbOrderImported, + 'warning_message' => $warningMessage, + 'lengow_table' => $lengowTable, + + // Plugin information + 'pluginData' => $pluginData, + 'pluginIsUpToDate' => $pluginIsUpToDate, + + // System information + 'multiShop' => $multiShop, + 'debugMode' => $debugMode, + 'showCarrierNotification' => \LengowCarrier::hasDefaultCarrierNotMatched(), + + // Toolbar + 'displayToolbar' => 1, + 'current_controller' => 'LengowOrderController', + 'total_pending_order' => \LengowOrder::countOrderToBeSent(), + 'merchantStatus' => \LengowSync::getStatusAccount(), + ]); + } + + /** + * Load orders table via AJAX + * + * @AdminSecurity("is_granted('read', 'AdminLengowOrder')") + * + * @param Request $request + * @return JsonResponse + */ + public function loadTableAction(Request $request): JsonResponse + { + $lengowOrderController = new \LengowOrderController(); + $orderTable = $lengowOrderController->buildTable(); + + $data = []; + $data['order_table'] = preg_replace('/\r|\n/', '', $orderTable); + + return new JsonResponse($data); + } + + /** + * Re-import order via AJAX + * + * @AdminSecurity("is_granted('update', 'AdminLengowOrder')") + * + * @param Request $request + * @return JsonResponse + */ + public function reImportAction(Request $request): JsonResponse + { + $idOrderLengow = (int) $request->get('id', 0); + + if (!$idOrderLengow) { + return new JsonResponse(['error' => 'Invalid order ID'], 400); + } + + \LengowOrder::reImportOrder($idOrderLengow); + + $lengowOrderController = new \LengowOrderController(); + $list = $lengowOrderController->loadTable(); + $row = $list->getRow(' id = ' . $idOrderLengow); + $html = $list->displayRow($row); + $html = preg_replace('/\r|\n/', '', $html); + + $data = [ + 'id_order_lengow' => $idOrderLengow, + 'html' => $html, + ]; + + return new JsonResponse($data); + } + + /** + * Re-send order via AJAX + * + * @AdminSecurity("is_granted('update', 'AdminLengowOrder')") + * + * @param Request $request + * @return JsonResponse + */ + public function reSendAction(Request $request): JsonResponse + { + $idOrderLengow = (int) $request->get('id', 0); + + if (!$idOrderLengow) { + return new JsonResponse(['error' => 'Invalid order ID'], 400); + } + + \LengowOrder::reSendOrder($idOrderLengow); + + $lengowOrderController = new \LengowOrderController(); + $list = $lengowOrderController->loadTable(); + $row = $list->getRow(' id = ' . $idOrderLengow); + $html = $list->displayRow($row); + $html = preg_replace('/\r|\n/', '', $html); + + $data = [ + 'id_order_lengow' => $idOrderLengow, + 'html' => $html, + ]; + + return new JsonResponse($data); + } + + /** + * Import all orders via AJAX + * + * @AdminSecurity("is_granted('update', 'AdminLengowOrder')") + * + * @param Request $request + * @return JsonResponse + */ + public function importAllAction(Request $request): JsonResponse + { + $locale = new \LengowTranslation(); + $module = Module::getInstanceByName('lengow'); + + // Run import + if (Shop::getContextShopID()) { + $import = new \LengowImport([ + \LengowImport::PARAM_SHOP_ID => Shop::getContextShopID(), + \LengowImport::PARAM_LOG_OUTPUT => false, + ]); + } else { + $import = new \LengowImport([\LengowImport::PARAM_LOG_OUTPUT => false]); + } + $return = $import->exec(); + + // Get import messages + $lengowOrderController = new \LengowOrderController(); + $message = $lengowOrderController->loadMessage($return); + + // Get updated data + $lastImport = \LengowMain::getLastImport(); + $orderCollection = [ + 'last_import_date' => $lastImport['timestamp'] !== 'none' + ? \LengowMain::getDateInCorrectFormat($lastImport['timestamp']) + : '', + 'last_import_type' => $lastImport['type'], + 'link' => \LengowMain::getCronUrl(), + ]; + + // Get warning messages + $warningMessages = []; + if (\LengowConfiguration::debugModeIsActive()) { + $lengowLink = new \LengowLink(); + $warningMessages[] = $locale->t( + 'order.screen.debug_warning_message', + ['url' => $lengowLink->getAbsoluteAdminLink('AdminLengowMainSetting')] + ); + } + if (\LengowCarrier::hasDefaultCarrierNotMatched()) { + $lengowLink = new \LengowLink(); + $warningMessages[] = $locale->t( + 'order.screen.no_carrier_warning_message', + ['url' => $lengowLink->getAbsoluteAdminLink('AdminLengowOrderSetting')] + ); + } + $warningMessage = !empty($warningMessages) ? join('
', $warningMessages) : false; + + // Render partials + $this->get('twig')->addGlobal('orderCollection', $orderCollection); + $this->get('twig')->addGlobal('locale', $locale); + $this->get('twig')->addGlobal('reportMailEnabled', (bool) \LengowConfiguration::getGlobalValue(\LengowConfiguration::REPORT_MAIL_ENABLED)); + $this->get('twig')->addGlobal('report_mail_address', \LengowConfiguration::getReportEmailAddress()); + $this->get('twig')->addGlobal('warning_message', $warningMessage); + $this->get('twig')->addGlobal('lengow_link', new \LengowLink()); + + $displayWarningMessage = $this->get('twig')->render('@Modules/lengow/views/templates/twig/admin/orders/_partials/warning_message.html.twig'); + $displayLastImportation = $this->get('twig')->render('@Modules/lengow/views/templates/twig/admin/orders/_partials/last_importation.html.twig'); + + // Get order table + $orderTable = $lengowOrderController->buildTable(); + $list = $lengowOrderController->loadTable(); + + if ($list->getTotal() > 0) { + $displayListOrder = $orderTable; + } else { + $this->get('twig')->addGlobal('nb_order_imported', 0); + $displayListOrder = $this->get('twig')->render('@Modules/lengow/views/templates/twig/admin/orders/_partials/no_order.html.twig'); + } + + $data = [ + 'message' => '
' . join('
', $message) . '
', + 'warning_message' => preg_replace('/\r|\n/', '', $displayWarningMessage), + 'last_importation' => preg_replace('/\r|\n/', '', $displayLastImportation), + 'import_orders' => $locale->t('order.screen.button_update_orders'), + 'list_order' => preg_replace('/\r|\n/', '', $displayListOrder), + 'show_carrier_notification' => \LengowCarrier::hasDefaultCarrierNotMatched(), + ]; + + return new JsonResponse($data); + } + + /** + * Synchronize order with Lengow + * + * @AdminSecurity("is_granted('update', 'AdminLengowOrder')") + * + * @param Request $request + * @return Response + */ + public function synchronizeAction(Request $request): Response + { + $idOrder = (int) $request->get('id_order', 0); + + if (!$idOrder) { + $this->addFlash('error', 'Invalid order ID'); + return $this->redirectToRoute('lengow_admin_order_index'); + } + + $lengowOrder = new \LengowOrder($idOrder); + $synchro = $lengowOrder->synchronizeOrder(); + + if ($synchro) { + $synchroMessage = \LengowMain::setLogMessage( + 'log.import.order_synchronized_with_lengow', + ['order_id' => $idOrder] + ); + } else { + $synchroMessage = \LengowMain::setLogMessage( + 'log.import.order_not_synchronized_with_lengow', + ['order_id' => $idOrder] + ); + } + + \LengowMain::log(\LengowLog::CODE_IMPORT, $synchroMessage, false, $lengowOrder->lengowMarketplaceSku); + + // Redirect to PrestaShop order view + return $this->redirect($this->getOrderAdminLink($idOrder)); + } + + /** + * Cancel and re-import order + * + * @AdminSecurity("is_granted('update', 'AdminLengowOrder')") + * + * @param Request $request + * @return Response + */ + public function cancelReImportAction(Request $request): Response + { + $idOrder = (int) $request->get('id_order', 0); + + if (!$idOrder) { + $this->addFlash('error', 'Invalid order ID'); + return $this->redirectToRoute('lengow_admin_order_index'); + } + + $lengowOrder = new \LengowOrder($idOrder); + $newIdOrder = $lengowOrder->cancelAndreImportOrder(); + + if (!$newIdOrder) { + $newIdOrder = $idOrder; + } + + return $this->redirect($this->getOrderAdminLink($newIdOrder)); + } + + /** + * Save shipping method via AJAX + * + * @AdminSecurity("is_granted('update', 'AdminLengowOrder')") + * + * @param Request $request + * @return JsonResponse + */ + public function saveShippingMethodAction(Request $request): JsonResponse + { + $idOrder = (int) $request->get('id_order', 0); + $shippingMethod = $request->get('method', ''); + + $response = ['success' => false, 'message' => '']; + + if (!$idOrder || !$shippingMethod) { + $response['message'] = 'Missing parameters'; + return new JsonResponse($response, 400); + } + + try { + $result = \Db::getInstance()->update( + 'lengow_orders', + ['method' => pSQL($shippingMethod)], + 'id_order = ' . $idOrder + ); + + if ($result) { + $response = [ + 'success' => true, + 'message' => 'Delivery method successfully updated', + ]; + \LengowMain::log( + \LengowLog::CODE_IMPORT, + 'Updated shipping method : ' . $shippingMethod, + false, + $idOrder + ); + } else { + $response['message'] = 'No changes or errors'; + } + } catch (\Exception $e) { + $response['message'] = 'Error: ' . $e->getMessage(); + return new JsonResponse($response, 500); + } + + return new JsonResponse($response); + } + + /** + * Force resend action + * + * @AdminSecurity("is_granted('update', 'AdminLengowOrder')") + * + * @param Request $request + * @return Response + */ + public function forceResendAction(Request $request): Response + { + $idOrder = (int) $request->get('id_order', 0); + $actionType = $request->get('action_type', \LengowAction::TYPE_SHIP); + + if (!$idOrder) { + $this->addFlash('error', 'Invalid order ID'); + return $this->redirectToRoute('lengow_admin_order_index'); + } + + $lengowOrder = new \LengowOrder($idOrder); + $lengowOrder->callAction($actionType); + + return $this->redirect($this->getOrderAdminLink($idOrder)); + } + + /** + * Get order admin link (PrestaShop order view) + * + * @param int $idOrder + * @return string + */ + private function getOrderAdminLink(int $idOrder): string + { + $link = new \LengowLink(); + try { + if (version_compare(_PS_VERSION_, '1.7.7', '<')) { + $href = $link->getAbsoluteAdminLink('AdminOrders') + . '&vieworder&id_order=' . $idOrder; + } else { + $sfParams = ['orderId' => $idOrder]; + $href = \Link::getUrlSmarty([ + 'entity' => 'sf', + 'route' => 'admin_orders_view', + 'sf-params' => $sfParams, + ]); + } + } catch (\PrestaShopException $e) { + $href = '#'; + } + + return $href; + } +} diff --git a/src/Controller/AdminToolboxController.php b/src/Controller/AdminToolboxController.php new file mode 100644 index 00000000..b86c6364 --- /dev/null +++ b/src/Controller/AdminToolboxController.php @@ -0,0 +1,63 @@ + + * @copyright 2017 Lengow SAS + * @license http://www.apache.org/licenses/LICENSE-2.0 + */ + +namespace PrestaShop\Module\Lengow\Controller; + +use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use PrestaShopBundle\Security\Annotation\AdminSecurity; + +if (!defined('_PS_VERSION_')) { + exit; +} + +/** + * Lengow Toolbox Controller for PrestaShop 9 + */ +class AdminToolboxController extends FrameworkBundleAdminController +{ + /** + * Toolbox page + * + * @AdminSecurity("is_granted('read', 'AdminLengowToolbox')") + * + * @param Request $request + * @return Response + */ + public function indexAction(Request $request): Response + { + $locale = new \LengowTranslation(); + $lengowLink = new \LengowLink(); + $module = \Module::getInstanceByName('lengow'); + $currentController = 'LengowToolboxController'; + + return $this->render('@Modules/lengow/views/templates/admin/toolbox/index.html.twig', [ + 'locale' => $locale, + 'lengowPathUri' => $module->getPathUri(), + 'lengowUrl' => \LengowConfiguration::getLengowUrl(), + 'lengow_link' => $lengowLink, + 'displayToolbar' => 0, + 'current_controller' => $currentController, + 'total_pending_order' => \LengowOrder::countOrderToBeSent(), + ]); + } +} diff --git a/views/templates/admin/_partials/base.html.twig b/views/templates/admin/_partials/base.html.twig new file mode 100644 index 00000000..0796d6a3 --- /dev/null +++ b/views/templates/admin/_partials/base.html.twig @@ -0,0 +1,68 @@ +{# + # Copyright 2017 Lengow SAS. + # + # Licensed under the Apache License, Version 2.0 (the "License"); you may + # not use this file except in compliance with the License. You may obtain + # a copy of the License at + # + # http://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, software + # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + # License for the specific language governing permissions and limitations + # under the License. + # + # @author Team Connector + # @copyright 2017 Lengow SAS + # @license http://www.apache.org/licenses/LICENSE-2.0 + #} + + + + + + + {% block title %}Lengow{% endblock %} + + + + + + + + + + + + + {% block stylesheets %}{% endblock %} + + + + {% if displayToolbar is defined and displayToolbar == 1 %} + {% include '@Modules/lengow/views/templates/admin/_partials/header.html.twig' %} + {% endif %} + +
+ {% block content %}{% endblock %} +
+ + {% if displayToolbar is defined and displayToolbar == 1 %} + {% include '@Modules/lengow/views/templates/admin/_partials/footer.html.twig' %} + {% endif %} + + + + + + + + + + + {% block javascripts %}{% endblock %} + + diff --git a/views/templates/admin/_partials/footer.html.twig b/views/templates/admin/_partials/footer.html.twig new file mode 100644 index 00000000..b331ddfa --- /dev/null +++ b/views/templates/admin/_partials/footer.html.twig @@ -0,0 +1,6 @@ +{# + # Copyright 2017 Lengow SAS. + # Footer for Lengow pages + #} + +{# Footer content if needed in the future #} diff --git a/views/templates/admin/_partials/header.html.twig b/views/templates/admin/_partials/header.html.twig new file mode 100644 index 00000000..6356751e --- /dev/null +++ b/views/templates/admin/_partials/header.html.twig @@ -0,0 +1,68 @@ +{# + # Copyright 2017 Lengow SAS. + # Navigation header for Lengow pages + #} + +
+ +
diff --git a/views/templates/admin/dashboard/index.html.twig b/views/templates/admin/dashboard/index.html.twig new file mode 100644 index 00000000..b9bd6388 --- /dev/null +++ b/views/templates/admin/dashboard/index.html.twig @@ -0,0 +1,19 @@ +{% extends '@Modules/lengow/views/templates/admin/_partials/base.html.twig' %} + +{% block title %}Lengow - Dashboard{% endblock %} + +{% block content %} + +
+ {# Dashboard content will be loaded here - For now using a placeholder #} + {# The full dashboard content from the legacy templates will be migrated progressively #} +
+

{{ locale.t('dashboard.title')|default('Dashboard') }}

+

Dashboard content - To be migrated from legacy template

+
+
+{% endblock %} + +{% block javascripts %} + +{% endblock %} diff --git a/views/templates/admin/feed/index.html.twig b/views/templates/admin/feed/index.html.twig new file mode 100644 index 00000000..c5ee88ae --- /dev/null +++ b/views/templates/admin/feed/index.html.twig @@ -0,0 +1,18 @@ +{% extends '@Modules/lengow/views/templates/admin/_partials/base.html.twig' %} + +{% block title %}Lengow - Products{% endblock %} + +{% block content %} + +
+
+

{{ locale.t('feed.title')|default('Products') }}

+ {# Feed/Product content will be migrated from legacy templates #} +

Feed content - To be migrated from legacy template

+
+
+{% endblock %} + +{% block javascripts %} + +{% endblock %} diff --git a/views/templates/admin/help/index.html.twig b/views/templates/admin/help/index.html.twig new file mode 100644 index 00000000..a6eba059 --- /dev/null +++ b/views/templates/admin/help/index.html.twig @@ -0,0 +1,18 @@ +{% extends '@Modules/lengow/views/templates/admin/_partials/base.html.twig' %} + +{% block title %}Lengow - Help{% endblock %} + +{% block content %} + +
+
+

{{ locale.t('help.title')|default('Help') }}

+ {# Help content will be migrated from legacy templates #} +

Help content - To be migrated from legacy template

+
+
+{% endblock %} + +{% block javascripts %} + +{% endblock %} diff --git a/views/templates/admin/home/index.html.twig b/views/templates/admin/home/index.html.twig new file mode 100644 index 00000000..07f53aa8 --- /dev/null +++ b/views/templates/admin/home/index.html.twig @@ -0,0 +1,25 @@ +{% extends '@Modules/lengow/views/templates/admin/_partials/base.html.twig' %} + +{% block title %}Lengow - Home{% endblock %} + +{% block content %} + +
+
+
+ +
+
+ {# Connection content will be loaded via AJAX #} +

Connection page - Content loaded via AJAX

+
+
+
+ +{% endblock %} + +{% block javascripts %} + +{% endblock %} diff --git a/views/templates/admin/legals/index.html.twig b/views/templates/admin/legals/index.html.twig new file mode 100644 index 00000000..f35c72bd --- /dev/null +++ b/views/templates/admin/legals/index.html.twig @@ -0,0 +1,14 @@ +{% extends '@Modules/lengow/views/templates/admin/_partials/base.html.twig' %} + +{% block title %}Lengow - Legals{% endblock %} + +{% block content %} + +
+
+

{{ locale.t('legals.title')|default('Legal Information') }}

+ {# Legals content will be migrated from legacy templates #} +

Legals content - To be migrated from legacy template

+
+
+{% endblock %} diff --git a/views/templates/admin/main_setting/index.html.twig b/views/templates/admin/main_setting/index.html.twig new file mode 100644 index 00000000..d5237d0f --- /dev/null +++ b/views/templates/admin/main_setting/index.html.twig @@ -0,0 +1,18 @@ +{% extends '@Modules/lengow/views/templates/admin/_partials/base.html.twig' %} + +{% block title %}Lengow - Settings{% endblock %} + +{% block content %} + +
+
+

{{ locale.t('settings.title')|default('Settings') }}

+ {# Settings content will be migrated from legacy templates #} +

Settings content - To be migrated from legacy template

+
+
+{% endblock %} + +{% block javascripts %} + +{% endblock %} diff --git a/views/templates/admin/order_setting/index.html.twig b/views/templates/admin/order_setting/index.html.twig new file mode 100644 index 00000000..d3a434ac --- /dev/null +++ b/views/templates/admin/order_setting/index.html.twig @@ -0,0 +1,18 @@ +{% extends '@Modules/lengow/views/templates/admin/_partials/base.html.twig' %} + +{% block title %}Lengow - Order Settings{% endblock %} + +{% block content %} + +
+
+

{{ locale.t('order_setting.title')|default('Order Settings') }}

+ {# Order Settings content will be migrated from legacy templates #} +

Order Settings content - To be migrated from legacy template

+
+
+{% endblock %} + +{% block javascripts %} + +{% endblock %} diff --git a/views/templates/admin/orders/index.html.twig b/views/templates/admin/orders/index.html.twig new file mode 100644 index 00000000..b62319f2 --- /dev/null +++ b/views/templates/admin/orders/index.html.twig @@ -0,0 +1,18 @@ +{% extends '@Modules/lengow/views/templates/admin/_partials/base.html.twig' %} + +{% block title %}Lengow - Orders{% endblock %} + +{% block content %} + +
+
+

{{ locale.t('order.title')|default('Orders') }}

+ {# Orders content will be migrated from legacy templates #} +

Orders content - To be migrated from legacy template

+
+
+{% endblock %} + +{% block javascripts %} + +{% endblock %} diff --git a/views/templates/admin/toolbox/index.html.twig b/views/templates/admin/toolbox/index.html.twig new file mode 100644 index 00000000..30b61335 --- /dev/null +++ b/views/templates/admin/toolbox/index.html.twig @@ -0,0 +1,18 @@ +{% extends '@Modules/lengow/views/templates/admin/_partials/base.html.twig' %} + +{% block title %}Lengow - Toolbox{% endblock %} + +{% block content %} + +
+
+

{{ locale.t('toolbox.title')|default('Toolbox') }}

+ {# Toolbox content will be migrated from legacy templates #} +

Toolbox content - To be migrated from legacy template

+
+
+{% endblock %} + +{% block javascripts %} + +{% endblock %} diff --git a/views/templates/twig/admin/_partials/base.html.twig b/views/templates/twig/admin/_partials/base.html.twig new file mode 100644 index 00000000..14e894d2 --- /dev/null +++ b/views/templates/twig/admin/_partials/base.html.twig @@ -0,0 +1,41 @@ + + + + + + {% block title %}Lengow - PrestaShop{% endblock %} + + {# PrestaShop admin styles #} + + + {# Lengow module styles #} + + + + {% block stylesheets %}{% endblock %} + + + {% block header %} + {% include '@Modules/lengow/views/templates/twig/admin/_partials/header.html.twig' %} + {% endblock %} + +
+
+ {% block content %}{% endblock %} +
+
+ + {% block footer %} + {% include '@Modules/lengow/views/templates/twig/admin/_partials/footer.html.twig' %} + {% endblock %} + + {# PrestaShop admin scripts #} + + + + {# Lengow module scripts #} + + + {% block javascripts %}{% endblock %} + + diff --git a/views/templates/twig/admin/_partials/footer.html.twig b/views/templates/twig/admin/_partials/footer.html.twig new file mode 100644 index 00000000..85e1d429 --- /dev/null +++ b/views/templates/twig/admin/_partials/footer.html.twig @@ -0,0 +1,16 @@ +{# Lengow Footer #} + diff --git a/views/templates/twig/admin/_partials/header.html.twig b/views/templates/twig/admin/_partials/header.html.twig new file mode 100644 index 00000000..70c91fcc --- /dev/null +++ b/views/templates/twig/admin/_partials/header.html.twig @@ -0,0 +1,60 @@ +{# Lengow Header with Navigation #} + diff --git a/views/templates/twig/admin/dashboard/index.html.twig b/views/templates/twig/admin/dashboard/index.html.twig new file mode 100644 index 00000000..ca71eee3 --- /dev/null +++ b/views/templates/twig/admin/dashboard/index.html.twig @@ -0,0 +1,61 @@ +{% extends '@Modules/lengow/views/templates/twig/admin/_partials/base.html.twig' %} + +{% block title %}{{ 'Dashboard'|trans({}, 'Modules.Lengow.Admin') }} - Lengow{% endblock %} + +{% block content %} +
+
+

{{ 'Dashboard'|trans({}, 'Modules.Lengow.Admin') }}

+
+ + {% if not pluginIsUpToDate %} +
+ {{ 'Update available'|trans({}, 'Modules.Lengow.Admin') }} + {{ 'A new version of the Lengow module is available'|trans({}, 'Modules.Lengow.Admin') }}. + {{ 'View update guide'|trans({}, 'Modules.Lengow.Admin') }} +
+ {% endif %} + +
+ {# Merchant status #} + {% if merchantStatus %} +
+

{{ 'Account status'|trans({}, 'Modules.Lengow.Admin') }}

+
+

{{ 'Status'|trans({}, 'Modules.Lengow.Admin') }}: + {% if merchantStatus.type == 'success' %} + {{ merchantStatus.message }} + {% else %} + {{ merchantStatus.message }} + {% endif %} +

+
+
+ {% endif %} + + {# Statistics placeholder #} +
+

{{ 'Statistics'|trans({}, 'Modules.Lengow.Admin') }}

+
+

{{ 'Dashboard statistics'|trans({}, 'Modules.Lengow.Admin') }}

+
+
+ + {# Quick actions #} + +
+
+{% endblock %} diff --git a/views/templates/twig/admin/home/index.html.twig b/views/templates/twig/admin/home/index.html.twig new file mode 100644 index 00000000..0b60a835 --- /dev/null +++ b/views/templates/twig/admin/home/index.html.twig @@ -0,0 +1,25 @@ +{% extends '@Modules/lengow/views/templates/twig/admin/_partials/base.html.twig' %} + +{% block title %}{{ 'Connection'|trans({}, 'Modules.Lengow.Admin') }} - Lengow{% endblock %} + +{% block content %} +
+
+

{{ 'Connect your Lengow account'|trans({}, 'Modules.Lengow.Admin') }}

+
+ +
+ {% if isNewMerchant %} +
+

{{ 'Welcome to Lengow'|trans({}, 'Modules.Lengow.Admin') }}

+

{{ 'Please connect your Lengow account to start using the module'|trans({}, 'Modules.Lengow.Admin') }}.

+
+ {% else %} +
+

{{ 'Account connected'|trans({}, 'Modules.Lengow.Admin') }}

+

{{ 'Your Lengow account is successfully connected'|trans({}, 'Modules.Lengow.Admin') }}.

+
+ {% endif %} +
+
+{% endblock %} diff --git a/views/templates/twig/admin/orders/_partials/last_importation.html.twig b/views/templates/twig/admin/orders/_partials/last_importation.html.twig new file mode 100644 index 00000000..c78193bc --- /dev/null +++ b/views/templates/twig/admin/orders/_partials/last_importation.html.twig @@ -0,0 +1,36 @@ +{# + # Copyright 2017 Lengow SAS. + # + # Licensed under the Apache License, Version 2.0 (the "License"); you may + # not use this file except in compliance with the License. You may obtain + # a copy of the License at + # + # http://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, software + # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + # License for the specific language governing permissions and limitations + # under the License. + # + # @author Team Connector + # @copyright 2017 Lengow SAS + # @license http://www.apache.org/licenses/LICENSE-2.0 + #} + +{% if orderCollection.last_import_type != 'none' %} +

+ {{ locale.t('order.screen.last_order_importation') }} : {{ orderCollection.last_import_date }} +

+{% else %} +

+ {{ locale.t('order.screen.no_order_importation') }} +

+{% endif %} + +{% if reportMailEnabled %} +

+ {{ locale.t('order.screen.all_order_will_be_sent_to') }} {{ report_mail_address|join(', ') }} + ({{ locale.t('order.screen.change_this') }}) +

+{% endif %} diff --git a/views/templates/twig/admin/orders/_partials/no_order.html.twig b/views/templates/twig/admin/orders/_partials/no_order.html.twig new file mode 100644 index 00000000..ae732ca8 --- /dev/null +++ b/views/templates/twig/admin/orders/_partials/no_order.html.twig @@ -0,0 +1,29 @@ +{# + # Copyright 2017 Lengow SAS. + # + # Licensed under the Apache License, Version 2.0 (the "License"); you may + # not use this file except in compliance with the License. You may obtain + # a copy of the License at + # + # http://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, software + # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + # License for the specific language governing permissions and limitations + # under the License. + # + # @author Team Connector + # @copyright 2017 Lengow SAS + # @license http://www.apache.org/licenses/LICENSE-2.0 + #} + +
+
+
+
+
+

{{ locale.t('order.screen.no_order_title') }}

+

{{ locale.t('order.screen.no_order_message') }}

+
+
diff --git a/views/templates/twig/admin/orders/_partials/warning_message.html.twig b/views/templates/twig/admin/orders/_partials/warning_message.html.twig new file mode 100644 index 00000000..6ce4af4a --- /dev/null +++ b/views/templates/twig/admin/orders/_partials/warning_message.html.twig @@ -0,0 +1,25 @@ +{# + # Copyright 2017 Lengow SAS. + # + # Licensed under the Apache License, Version 2.0 (the "License"); you may + # not use this file except in compliance with the License. You may obtain + # a copy of the License at + # + # http://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, software + # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + # License for the specific language governing permissions and limitations + # under the License. + # + # @author Team Connector + # @copyright 2017 Lengow SAS + # @license http://www.apache.org/licenses/LICENSE-2.0 + #} + +{% if warning_message %} +

+ {{ warning_message|raw }} +

+{% endif %} diff --git a/views/templates/twig/admin/orders/index.html.twig b/views/templates/twig/admin/orders/index.html.twig new file mode 100644 index 00000000..ac83ed47 --- /dev/null +++ b/views/templates/twig/admin/orders/index.html.twig @@ -0,0 +1,89 @@ +{# + # Copyright 2017 Lengow SAS. + # + # Licensed under the Apache License, Version 2.0 (the "License"); you may + # not use this file except in compliance with the License. You may obtain + # a copy of the License at + # + # http://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, software + # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + # License for the specific language governing permissions and limitations + # under the License. + # + # @author Team Connector + # @copyright 2017 Lengow SAS + # @license http://www.apache.org/licenses/LICENSE-2.0 + #} + +{% extends '@Modules/lengow/views/templates/twig/admin/_partials/base.html.twig' %} + +{% block lengow_content %} +
+
+ {% if debugMode %} +
+ {{ locale.t('menu.debug_active') }} +
+ {% endif %} + +
+ {# Warning Messages #} +
+ {% include '@Modules/lengow/views/templates/twig/admin/orders/_partials/warning_message.html.twig' %} +
+ +
+ {# Last Importation Info #} +
+ {% include '@Modules/lengow/views/templates/twig/admin/orders/_partials/last_importation.html.twig' %} +
+ + {# Messages Container #} + +
+ + + + {# Loading Indicator #} + + + {# Orders Table #} +
+
+ {% if nb_order_imported == 0 %} + {% include '@Modules/lengow/views/templates/twig/admin/orders/_partials/no_order.html.twig' %} + {% else %} + {{ lengow_table|raw }} + {% endif %} +
+
+
+
+ + + +{% endblock %}