diff --git a/prestashop-image/Dockerfile b/prestashop-image/Dockerfile new file mode 100644 index 0000000..cf19058 --- /dev/null +++ b/prestashop-image/Dockerfile @@ -0,0 +1,52 @@ +FROM prestashop/prestashop:9 + +# Apply EduBox patches so PrestaShop 9 works behind the dynamic-domain reverse proxy. +COPY edubox-tools.patch \ + edubox-link.patch \ + edubox-frontcontroller.patch \ + edubox-shop.patch \ + edubox-shopurl.patch \ + edubox-shop-getbaseurl.patch \ + edubox-shopcontext.patch \ + edubox-asseturl.patch \ + edubox-install.patch \ + edubox-install-language.patch \ + edubox-language.patch \ + edubox-configuration.patch \ + edubox-dashboard-warning.patch \ + edubox-docker-run.patch \ + /tmp/ +RUN patch -p1 -d /var/www/html < /tmp/edubox-tools.patch && \ + patch -p1 -d /var/www/html < /tmp/edubox-link.patch && \ + patch -p1 -d /var/www/html < /tmp/edubox-frontcontroller.patch && \ + patch -p1 -d /var/www/html < /tmp/edubox-shop.patch && \ + patch -p1 -d /var/www/html < /tmp/edubox-shopurl.patch && \ + patch -p1 -d /var/www/html < /tmp/edubox-shop-getbaseurl.patch && \ + patch -p1 -d /var/www/html < /tmp/edubox-shopcontext.patch && \ + patch -p1 -d /var/www/html < /tmp/edubox-asseturl.patch && \ + patch -p1 -d /var/www/html < /tmp/edubox-install.patch && \ + patch -p1 -d /var/www/html < /tmp/edubox-install-language.patch && \ + patch -p1 -d /var/www/html < /tmp/edubox-language.patch && \ + patch -p1 -d /var/www/html < /tmp/edubox-configuration.patch && \ + patch -p1 -d /var/www/html < /tmp/edubox-dashboard-warning.patch && \ + patch -p1 -d / < /tmp/edubox-docker-run.patch && \ + rm /tmp/edubox-*.patch + +# Apache proxy configuration +COPY proxy.conf /etc/apache2/conf-enabled/edubox-proxy.conf + +# Pre-download French translation pack so the installer works offline. +# Agents may not have outbound internet access during installation. +# The official image copies /tmp/data-ps/prestashop/ into /var/www/html on first +# boot, so we place the pack there as well. +COPY translations-symfony-fr-FR.zip /tmp/data-ps/prestashop/translations/sf-fr-FR.zip +RUN chown -R www-data:www-data /tmp/data-ps/prestashop/translations + +# Early bootstrap normalisation for X-Forwarded-* headers +COPY defines_custom.inc.php /var/www/html/config/defines_custom.inc.php + +# Clear caches on every start so dynamic domains/ports are picked up +COPY edubox-clear-cache-init.sh /tmp/init-scripts/edubox-clear-cache.sh +RUN chmod +x /tmp/init-scripts/edubox-clear-cache.sh + +RUN chown -R www-data:www-data /var/www/html diff --git a/prestashop-image/README.md b/prestashop-image/README.md new file mode 100644 index 0000000..1d06304 --- /dev/null +++ b/prestashop-image/README.md @@ -0,0 +1,99 @@ +# EduBox PrestaShop 9 Image + +Image Docker patchée basée sur `prestashop/prestashop:9`, conçue pour fonctionner +avec le reverse proxy dynamique d'EduBox. + +## Pourquoi une image patchée ? + +PrestaShop 9 (Apache 2.4 + PHP 8.5) a plusieurs problèmes majeurs derrière EduBox : + +1. Les headers `X-Forwarded-*` sont corrompus par Apache/PHP : `$_SERVER` les + reçoit sous forme d'arrays au lieu de strings. On contourne ce bug via + `getenv()` dans `config/defines_custom.inc.php`. +2. PrestaShop utilise partout le domaine stocké en base (`ps_shop_url`) et la + configuration `PS_SSL_ENABLED`. Derrière EduBox, le domaine public change à + chaque instance (`.alfrednobel.edudeploy.com`) et toutes les requêtes + publiques arrivent en HTTPS. Les patches forcent l'utilisation de l'hôte et + du protocole de la requête courante. +3. Les agents étudiants peuvent être hors ligne. Le pack de langue français est + donc embarqué dans l'image pour éviter tout téléchargement pendant + l'installation. + +## Build local + +```bash +cd /opt/edubox/prestashop-image +docker build -t edubox-prestashop:9 . +``` + +## Push sur le registry Gitea + +```bash +docker tag edubox-prestashop:9 \ + 151.80.60.98:3001/yacine/edubox/edubox-prestashop:9-edubox-9 +docker push \ + 151.80.60.98:3001/yacine/edubox/edubox-prestashop:9-edubox-9 +``` + +## Patches appliqués + +| Patch | Fichier modifié | Objectif | +|-------|-----------------|----------| +| `edubox-tools.patch` | `classes/Tools.php` | `getShopDomain()` / `getShopDomainSsl()` utilisent `getHttpHost()` dynamiquement en conservant les ports non standards (ex. `localhost:8088`) ; `.htaccess` généré sans condition `HTTP_HOST` (images/catégories). | +| `edubox-link.patch` | `classes/Link.php` | `getBaseLink()` et `getAdminBaseLink()` utilisent `usingSecureMode()` et `getHttpHost()`. | +| `edubox-frontcontroller.patch` | `classes/controller/FrontController.php` | Désactive `sslRedirection()` pour éviter les boucles HTTP/HTTPS. | +| `edubox-shop.patch` | `classes/shop/Shop.php` | `Shop::initialize()` utilise le shop par défaut sans redirection forcée. | +| `edubox-shopurl.patch` | `classes/shop/ShopUrl.php` | `getMainShopDomain()` / `getMainShopDomainSSL()` retournent le domaine de la requête en conservant les ports non standards. | +| `edubox-shop-getbaseurl.patch` | `classes/shop/Shop.php` | `Shop::getBaseURL()` utilise le host/port de la requête courante. | +| `edubox-shopcontext.patch` | `src/Core/Context/ShopContext.php` | `getBaseURL()` du BO est reconstruit à partir de la requête courante. | +| `edubox-configuration.patch` | `classes/Configuration.php` | `PS_SHOP_DOMAIN`, `PS_SHOP_DOMAIN_SSL`, `PS_SSL_ENABLED`, `_PS_BASE_URL_`, `_PS_BASE_URL_SSL_` sont résolus dynamiquement depuis la requête, pas depuis le cache DB. | +| `edubox-asseturl.patch` | `src/Adapter/Assets/AssetUrlGeneratorTrait.php` | Les assets CCC utilisent le protocole de la requête, pas `PS_SSL_ENABLED`. | +| `edubox-install.patch` | `src/PrestaShopBundle/Install/Install.php` | `finalize()` respecte `PS_FOLDER_ADMIN` (évite le bug overlayfs `admin` → `admin-edubox`). | +| `edubox-install-language.patch` | `src/PrestaShopBundle/Install/Install.php` | Évite le téléchargement du pack legacy `fr.gzip` quand le pack Symfony est embarqué. | +| `edubox-language.patch` | `classes/Language.php` | Utilise `_PS_TRANSLATIONS_DIR_` au runtime pour le cache langue ; évite le téléchargement réseau si le pack est présent. | +| `edubox-dashboard-warning.patch` | `controllers/admin/AdminDashboardController.php` | Désactive le bandeau d’avertissement "domaine différent de SEO & URL". | +| `edubox-docker-run.patch` | `/tmp/docker_run.sh` | Supprime un `install.lock` résiduel si une installation précédente a échoué. | + +## Fichiers injectés + +- `proxy.conf` : Apache truste `X-Forwarded-Proto: https` pour positionner + `HTTPS=on` dans l'environnement PHP. Active aussi `AllowOverride All` pour + que le `.htaccess` de PrestaShop fonctionne. +- `config/defines_custom.inc.php` : normalise `HTTP_X_FORWARDED_HOST`, + `HTTP_X_FORWARDED_PROTO` et `HTTP_HOST` corrompus ; définit + `PS_TRUSTED_PROXIES` pour Symfony. +- `translations-symfony-fr-FR.zip` → copié sous `sf-fr-FR.zip` dans + `/var/www/html/translations/` : pack de langue Symfony français embarqué + ( PrestaShop attend le préfixe `sf-` ). +- `edubox-clear-cache-init.sh` → `/tmp/init-scripts/edubox-clear-cache.sh` : + vidage des caches Smarty/Symfony et des assets CCC à chaque démarrage du + conteneur, afin que les changements de domaine/port soient pris en compte. + +## Utilisation dans EduBox + +Le template PrestaShop 9 dans `server/prisma/seed.ts` utilise cette image : + +```yaml +app: + image: 151.80.60.98:3001/yacine/edubox/edubox-prestashop:9-edubox-8 +``` + +## Mise à jour vers une nouvelle version de PrestaShop + +Si PrestaShop sort une version `9.x.y` : + +1. Modifier le `FROM` du Dockerfile : `FROM prestashop/prestashop:9.x.y` +2. Relancer le build. Les patches qui échouent doivent être adaptés aux + nouvelles lignes/code de PrestaShop. +3. Re-tagger et pousser : `9.x.y-edubox-1`. +4. Mettre à jour `server/prisma/seed.ts` avec le nouveau tag. + +## Déploiement sur les agents + +L'image doit être accessible depuis chaque agent étudiant. Deux options : + +1. **Registry privé** (recommandé) : tagger et pousser l'image sur un registry + (Docker Hub, registry Gitea, GHCR, etc.) puis mettre à jour + `server/prisma/seed.ts` avec le nom complet. +2. **Build manuel sur chaque agent** : copier ce dossier sur l'agent et lancer + `docker build` avant le premier déploiement. diff --git a/prestashop-image/defines_custom.inc.php b/prestashop-image/defines_custom.inc.php new file mode 100644 index 0000000..ad48c85 --- /dev/null +++ b/prestashop-image/defines_custom.inc.php @@ -0,0 +1,34 @@ +isSecure() and +// $request->getHost() honour X-Forwarded-* headers. +putenv('PS_TRUSTED_PROXIES=127.0.0.1,REMOTE_ADDR'); +$_SERVER['PS_TRUSTED_PROXIES'] = '127.0.0.1,REMOTE_ADDR'; +$_ENV['PS_TRUSTED_PROXIES'] = '127.0.0.1,REMOTE_ADDR'; diff --git a/prestashop-image/edubox-asseturl.patch b/prestashop-image/edubox-asseturl.patch new file mode 100644 index 0000000..523e05e --- /dev/null +++ b/prestashop-image/edubox-asseturl.patch @@ -0,0 +1,20 @@ +--- a/src/Adapter/Assets/AssetUrlGeneratorTrait.php ++++ b/src/Adapter/Assets/AssetUrlGeneratorTrait.php +@@ -49,12 +49,14 @@ trait AssetUrlGeneratorTrait + protected function getFQDN() + { + if (null === $this->fqdn) { +- if ($this->configuration->get('PS_SSL_ENABLED') && ToolsLegacy::usingSecureMode()) { +- $this->fqdn = $this->configuration->get('_PS_BASE_URL_SSL_'); +- } else { ++ // EduBox: rely on the current request security, not on PS_SSL_ENABLED. ++ // Behind the reverse proxy every public request is HTTPS. ++ if (ToolsLegacy::usingSecureMode()) { ++ $this->fqdn = $this->configuration->get('_PS_BASE_URL_SSL_') ?: $this->configuration->get('_PS_BASE_URL_'); ++ } else { + $this->fqdn = $this->configuration->get('_PS_BASE_URL_'); + } + } + + return $this->fqdn; + } diff --git a/prestashop-image/edubox-clear-cache-init.sh b/prestashop-image/edubox-clear-cache-init.sh new file mode 100644 index 0000000..5504847 --- /dev/null +++ b/prestashop-image/edubox-clear-cache-init.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# EduBox: clear PrestaShop caches at every container start so that dynamic +# domains/ports (localhost:PORT or reverse-proxy public URL) are picked up. +echo "* EduBox: clearing PrestaShop caches for dynamic domain..." +rm -rf /var/www/html/var/cache/* +rm -rf /var/www/html/app/cache/* +rm -rf /var/www/html/cache/smarty/cache/* +rm -rf /var/www/html/cache/smarty/compile/* +rm -rf /var/www/html/themes/*/assets/cache/* +rm -rf /var/www/html/img/tmp/* diff --git a/prestashop-image/edubox-configuration.patch b/prestashop-image/edubox-configuration.patch new file mode 100644 index 0000000..60ae694 --- /dev/null +++ b/prestashop-image/edubox-configuration.patch @@ -0,0 +1,36 @@ +--- a/classes/Configuration.php 2026-06-04 14:48:44.000000000 +0000 ++++ b/classes/Configuration.php 2026-06-23 16:27:03.944472677 +0000 +@@ -210,6 +210,33 @@ + Configuration::loadConfiguration(); + } + ++ // EduBox: dynamic public domains and ports (local access + reverse proxy). ++ // These keys must be resolved from the current request, not from the DB cache. ++ if ($key === 'PS_SHOP_DOMAIN' || $key === 'PS_SHOP_DOMAIN_SSL') { ++ $host = Tools::getHttpHost(false, false, false); ++ if (substr($host, -3) === ':80' || substr($host, -4) === ':443') { ++ $host = substr($host, 0, strrpos($host, ':')); ++ } ++ return $host; ++ } ++ if ($key === 'PS_SSL_ENABLED' || $key === 'PS_SSL_ENABLED_EVERYWHERE') { ++ return Tools::usingSecureMode() ? '1' : '0'; ++ } ++ if ($key === '_PS_BASE_URL_') { ++ $host = Tools::getHttpHost(false, false, false); ++ if (substr($host, -3) === ':80' || substr($host, -4) === ':443') { ++ $host = substr($host, 0, strrpos($host, ':')); ++ } ++ return 'http://' . $host; ++ } ++ if ($key === '_PS_BASE_URL_SSL_') { ++ $host = Tools::getHttpHost(false, false, false); ++ if (substr($host, -3) === ':80' || substr($host, -4) === ':443') { ++ $host = substr($host, 0, strrpos($host, ':')); ++ } ++ return 'https://' . $host; ++ } ++ + $idLang = self::isLangKey($key) ? (int) $idLang : 0; + + if (self::$_new_cache_shop === null) { diff --git a/prestashop-image/edubox-dashboard-warning.patch b/prestashop-image/edubox-dashboard-warning.patch new file mode 100644 index 0000000..fd5c7b4 --- /dev/null +++ b/prestashop-image/edubox-dashboard-warning.patch @@ -0,0 +1,49 @@ +--- a/controllers/admin/AdminDashboardController.php ++++ b/controllers/admin/AdminDashboardController.php +@@ -330,43 +330,9 @@ + + protected function getWarningDomainName() + { +- $warning = false; +- if (Shop::isFeatureActive()) { +- return; +- } +- +- $shop = Context::getContext()->shop; +- if ($_SERVER['HTTP_HOST'] != $shop->domain && $_SERVER['HTTP_HOST'] != $shop->domain_ssl && Tools::getValue('ajax') == false) { +- $warning = $this->trans('You are currently connected under the following domain name:', [], 'Admin.Dashboard.Notification') . ' ' . $_SERVER['HTTP_HOST'] . '
'; +- if (Configuration::get('PS_MULTISHOP_FEATURE_ACTIVE')) { +- $warning .= $this->trans( +- 'This is different from the shop domain name set in the Multistore settings: "%s".', +- [ +- '%s' => $shop->domain, +- ], +- 'Admin.Dashboard.Notification' +- ) . $this->trans( +- 'If this is your main domain, please {link}change it now{/link}.', +- [ +- '{link}' => '', +- '{/link}' => '', +- ], +- 'Admin.Dashboard.Notification' +- ); +- } else { +- $warning .= $this->trans('This is different from the domain name set in the "SEO & URLs" tab.', [], 'Admin.Dashboard.Notification') . ' +- ' . $this->trans( +- 'If this is your main domain, please {link}change it now{/link}.', +- [ +- '{link}' => '', +- '{/link}' => '', +- ], +- 'Admin.Dashboard.Notification' +- ); +- } +- } +- +- return $warning; ++ // EduBox: instances use dynamic public domains behind a reverse proxy. ++ // The domain stored during installation never matches the request host. ++ return false; + } + + public function ajaxProcessRefreshDashboard() diff --git a/prestashop-image/edubox-docker-run.patch b/prestashop-image/edubox-docker-run.patch new file mode 100644 index 0000000..185a172 --- /dev/null +++ b/prestashop-image/edubox-docker-run.patch @@ -0,0 +1,16 @@ +--- a/tmp/docker_run.sh 2026-06-20 17:57:12.682339048 +0000 ++++ b/tmp/docker_run.sh 2026-06-20 17:57:12.852338398 +0000 +@@ -21,6 +21,13 @@ + + # From now, stop at error + set -e ++# EduBox: if a previous installation failed, install.lock remains but PrestaShop is not configured. ++# Remove the stale lock so the installer can run again on the next start. ++if [ -f ./install.lock ] && [ ! -f ./config/settings.inc.php ] && [ ! -f ./app/config/parameters.php ]; then ++ echo "\n* Stale install.lock detected, removing it to allow reinstallation ..." ++ rm -f ./install.lock ++fi ++ + + if [ ! -f ./config/settings.inc.php ] && [ ! -f ./app/config/parameters.php ] && [ ! -f ./install.lock ]; then + diff --git a/prestashop-image/edubox-frontcontroller.patch b/prestashop-image/edubox-frontcontroller.patch new file mode 100644 index 0000000..f2376da --- /dev/null +++ b/prestashop-image/edubox-frontcontroller.patch @@ -0,0 +1,24 @@ +--- a/classes/controller/FrontController.php ++++ b/classes/controller/FrontController.php +@@ -849,18 +849,9 @@ + */ + protected function sslRedirection() + { +- // If we call a SSL controller without SSL or a non SSL controller with SSL, we redirect with the right protocol +- if (Configuration::get('PS_SSL_ENABLED') && $_SERVER['REQUEST_METHOD'] != 'POST' && $this->ssl != Tools::usingSecureMode()) { +- $this->context->cookie->disallowWriting(); +- header('HTTP/1.1 301 Moved Permanently'); +- header('Cache-Control: no-cache'); +- if ($this->ssl) { +- header('Location: ' . Tools::getShopDomainSsl(true) . $_SERVER['REQUEST_URI']); +- } else { +- header('Location: ' . Tools::getShopDomain(true) . $_SERVER['REQUEST_URI']); +- } +- exit; +- } ++ // EduBox: disabled. Behind the EduBox reverse proxy every request is ++ // served over HTTPS publicly, so PrestaShop must never redirect to HTTP. ++ return; + } + + /** diff --git a/prestashop-image/edubox-install-language.patch b/prestashop-image/edubox-install-language.patch new file mode 100644 index 0000000..e13d367 --- /dev/null +++ b/prestashop-image/edubox-install-language.patch @@ -0,0 +1,30 @@ +--- a/src/PrestaShopBundle/Install/Install.php 2026-06-20 18:07:13.506985399 +0000 ++++ b/src/PrestaShopBundle/Install/Install.php 2026-06-20 18:07:22.294363061 +0000 +@@ -622,17 +622,20 @@ + 'locale' => (string) $xml->locale, + ]; + +- if (file_exists(_PS_TRANSLATIONS_DIR_ . (string) $iso . '.gzip') == false) { +- $language = EntityLanguage::downloadLanguagePack($iso, _PS_INSTALL_VERSION_); ++ // EduBox: skip legacy language pack download if Symfony pack is bundled ++ $errors = []; ++ $locale = $params_lang['locale']; ++ ++ if (!EntityLanguage::translationPackIsInCache($locale)) { ++ if (file_exists(_PS_TRANSLATIONS_DIR_ . (string) $iso . '.gzip') == false) { ++ $language = EntityLanguage::downloadLanguagePack($iso, _PS_INSTALL_VERSION_); + +- if ($language == false) { +- throw new PrestashopInstallerException($this->translator->trans('Cannot download language pack "%iso%"', ['%iso%' => $iso], 'Install')); ++ if ($language == false) { ++ throw new PrestashopInstallerException($this->translator->trans('Cannot download language pack "%iso%"', ['%iso%' => $iso], 'Install')); ++ } + } + } + +- $errors = []; +- $locale = $params_lang['locale']; +- + /* @todo check if a newer pack is available */ + if (!EntityLanguage::translationPackIsInCache($locale)) { + EntityLanguage::downloadXLFLanguagePack($locale, $errors); diff --git a/prestashop-image/edubox-install.patch b/prestashop-image/edubox-install.patch new file mode 100644 index 0000000..c9036ea --- /dev/null +++ b/prestashop-image/edubox-install.patch @@ -0,0 +1,9 @@ +--- a/src/PrestaShopBundle/Install/Install.php ++++ b/src/PrestaShopBundle/Install/Install.php +@@ -1202,7 +1202,7 @@ class Install extends AbstractInstall + { +- $adminFolder = 'admin-dev'; ++ $adminFolder = getenv('PS_FOLDER_ADMIN') ?: 'admin-dev'; + + // If we need, we generate a random name for admin folder (for security purpose!) + if (file_exists(_PS_ROOT_DIR_ . '/admin/')) { diff --git a/prestashop-image/edubox-language.patch b/prestashop-image/edubox-language.patch new file mode 100644 index 0000000..5e52a88 --- /dev/null +++ b/prestashop-image/edubox-language.patch @@ -0,0 +1,36 @@ +--- a/classes/Language.php ++++ b/classes/Language.php +@@ -1235,6 +1235,12 @@ + */ + public static function downloadXLFLanguagePack($locale, &$errors = [], $type = self::PACK_TYPE_SYMFONY) + { ++ // EduBox: if the translation pack is already present in the image, ++ // do not try to download it (agents may be offline). ++ if (static::translationPackIsInCache($locale, $type)) { ++ return true; ++ } ++ + $file = self::getPathToCachedTranslationPack($locale, $type); + $url = (self::PACK_TYPE_EMAILS === $type) ? self::EMAILS_LANGUAGE_PACK_URL : self::SF_LANGUAGE_PACK_URL; + $url = str_replace( +@@ -1697,7 +1703,9 @@ + */ + public static function translationPackIsInCache(string $locale, string $type = self::PACK_TYPE_SYMFONY): bool + { +- return file_exists(self::getPathToCachedTranslationPack($locale, $type)); ++ // EduBox: use runtime constant instead of class constant, because ++ // _PS_TRANSLATIONS_DIR_ may not be defined when this file is compiled. ++ return file_exists(_PS_TRANSLATIONS_DIR_ . $type . '-' . $locale . '.zip'); + } + + /** +@@ -1710,7 +1718,8 @@ + */ + private static function getPathToCachedTranslationPack(string $locale, string $type = self::PACK_TYPE_SYMFONY): string + { +- return self::TRANSLATION_PACK_CACHE_DIR . $type . '-' . $locale . '.zip'; ++ // EduBox: use runtime constant instead of class constant. ++ return _PS_TRANSLATIONS_DIR_ . $type . '-' . $locale . '.zip'; + } + + /** diff --git a/prestashop-image/edubox-link.patch b/prestashop-image/edubox-link.patch new file mode 100644 index 0000000..c70b186 --- /dev/null +++ b/prestashop-image/edubox-link.patch @@ -0,0 +1,46 @@ +--- a/classes/Link.php 2026-06-20 20:05:45.983104609 +0000 ++++ b/classes/Link.php 2026-06-20 20:05:46.195748630 +0000 +@@ -862,7 +862,7 @@ + public function getAdminBaseLink($idShop = null, $ssl = null, $relativeProtocol = false) + { + if (null === $ssl) { +- $ssl = Configuration::get('PS_SSL_ENABLED'); ++ $ssl = Tools::usingSecureMode(); + } + + if (Configuration::get('PS_MULTISHOP_FEATURE_ACTIVE')) { +@@ -881,9 +881,10 @@ + } + + if ($relativeProtocol) { +- $base = '//' . ($ssl && $this->ssl_enable ? $shop->domain_ssl : $shop->domain); ++ $base = '//' . Tools::getHttpHost(false, false, false); + } else { +- $base = (($ssl && $this->ssl_enable) ? 'https://' . $shop->domain_ssl : 'http://' . $shop->domain); ++ $protocol = Tools::usingSecureMode() ? 'https://' : 'http://'; ++ $base = $protocol . Tools::getHttpHost(false, false, false); + } + + return $base . $shop->getBaseURI(); +@@ -1391,7 +1392,7 @@ + public function getBaseLink($idShop = null, $ssl = null, $relativeProtocol = false) + { + if (null === $ssl) { +- $ssl = Configuration::get('PS_SSL_ENABLED'); ++ $ssl = Tools::usingSecureMode(); + } + + if (Configuration::get('PS_MULTISHOP_FEATURE_ACTIVE') && $idShop !== null) { +@@ -1401,9 +1402,10 @@ + } + + if ($relativeProtocol) { +- $base = '//' . ($ssl && $this->ssl_enable ? $shop->domain_ssl : $shop->domain); ++ $base = '//' . Tools::getHttpHost(false, false, false); + } else { +- $base = (($ssl && $this->ssl_enable) ? 'https://' . $shop->domain_ssl : 'http://' . $shop->domain); ++ $protocol = Tools::usingSecureMode() ? 'https://' : 'http://'; ++ $base = $protocol . Tools::getHttpHost(false, false, false); + } + + return $base . $shop->getBaseURI(); diff --git a/prestashop-image/edubox-shop-getbaseurl.patch b/prestashop-image/edubox-shop-getbaseurl.patch new file mode 100644 index 0000000..dbd51a7 --- /dev/null +++ b/prestashop-image/edubox-shop-getbaseurl.patch @@ -0,0 +1,29 @@ +--- a/classes/shop/Shop.php ++++ b/classes/shop/Shop.php +@@ -489,15 +489,16 @@ class ShopCore extends ObjectModel + */ + public function getBaseURL($auto_secure_mode = true, $add_base_uri = true) + { +- if ($auto_secure_mode && Tools::usingSecureMode()) { +- if (!$this->domain_ssl) { +- return false; +- } +- $url = 'https://' . $this->domain_ssl; ++ // EduBox: use the current request host so local access on non-standard ++ // ports (e.g. localhost:8088) and reverse-proxy domains both work. ++ $host = Tools::getHttpHost(false, false, false); ++ if (substr($host, -3) === ':80' || substr($host, -4) === ':443') { ++ $host = substr($host, 0, strrpos($host, ':')); ++ } ++ ++ if ($auto_secure_mode && Tools::usingSecureMode()) { ++ $url = 'https://' . $host; + } else { +- if (!$this->domain) { +- return false; +- } +- $url = 'http://' . $this->domain; ++ $url = 'http://' . $host; + } + + if ($add_base_uri) { diff --git a/prestashop-image/edubox-shop.patch b/prestashop-image/edubox-shop.patch new file mode 100644 index 0000000..2fe03b8 --- /dev/null +++ b/prestashop-image/edubox-shop.patch @@ -0,0 +1,46 @@ +--- a/classes/shop/Shop.php ++++ b/classes/shop/Shop.php +@@ -411,38 +411,14 @@ + } else { + $shop = new Shop($id_shop); + if (!Validate::isLoadedObject($shop) || !$shop->active) { +- // No shop found ... too bad, let's redirect to default shop +- $default_shop = new Shop((int) Configuration::get('PS_SHOP_DEFAULT')); ++ // EduBox: behind a reverse proxy with dynamic public domains, ++ // the requested host never matches ps_shop_url. Always use the ++ // default shop instead of redirecting to a fixed canonical URL. ++ $shop = new Shop((int) Configuration::get('PS_SHOP_DEFAULT')); + +- // Hmm there is something really bad in your Prestashop ! +- if (!Validate::isLoadedObject($default_shop)) { ++ if (!Validate::isLoadedObject($shop)) { + throw new PrestaShopException('Shop not found'); + } +- +- $params = $_GET; +- unset($params['id_shop']); +- $url = $default_shop->domain; +- if (!Configuration::get('PS_REWRITING_SETTINGS')) { +- $url .= $default_shop->getBaseURI() . 'index.php?' . http_build_query($params); +- } else { +- // Catch url with subdomain "www" +- if (strpos($url, 'www.') === 0 && 'www.' . $_SERVER['HTTP_HOST'] === $url || $_SERVER['HTTP_HOST'] === 'www.' . $url) { +- $url .= $_SERVER['REQUEST_URI']; +- } else { +- $url .= $default_shop->getBaseURI(); +- } +- +- if (count($params)) { +- $url .= '?' . http_build_query($params); +- } +- } +- +- $redirect_type = Configuration::get('PS_CANONICAL_REDIRECT'); +- $redirect_code = ($redirect_type == 1 ? '302' : '301'); +- $redirect_header = ($redirect_type == 1 ? 'Found' : 'Moved Permanently'); +- header('HTTP/1.0 ' . $redirect_code . ' ' . $redirect_header); +- header('Location: ' . Tools::getShopProtocol() . $url); +- exit; + } elseif (defined('_PS_ADMIN_DIR_') && empty($shop->physical_uri)) { + $shop_default = new Shop((int) Configuration::get('PS_SHOP_DEFAULT')); + $shop->physical_uri = $shop_default->physical_uri; diff --git a/prestashop-image/edubox-shopcontext.patch b/prestashop-image/edubox-shopcontext.patch new file mode 100644 index 0000000..61bfbfd --- /dev/null +++ b/prestashop-image/edubox-shopcontext.patch @@ -0,0 +1,28 @@ +--- a/src/Core/Context/ShopContext.php ++++ b/src/Core/Context/ShopContext.php +@@ -9,6 +9,7 @@ declare(strict_types=1); + + namespace PrestaShop\PrestaShop\Core\Context; + ++use Tools; + use PrestaShop\PrestaShop\Core\Domain\Shop\ValueObject\ShopConstraint; + + /** +@@ -121,11 +122,12 @@ class ShopContext + + public function getBaseURL(): string + { +- if ($this->secured) { +- $url = 'https://' . $this->domainSSL; +- } else { +- $url = 'http://' . $this->domain; +- } ++ // EduBox: behind a reverse proxy with dynamic public domains the shop ++ // URL stored in the database is never the real public URL. Rebuild the ++ // base URL from the current request instead. ++ $secure = Tools::usingSecureMode(); ++ $domain = $secure ? Tools::getShopDomainSsl(false, false) : Tools::getShopDomain(false, false); ++ $url = ($secure ? 'https://' : 'http://') . $domain; + + return $url . $this->getBaseURI(); + } diff --git a/prestashop-image/edubox-shopurl.patch b/prestashop-image/edubox-shopurl.patch new file mode 100644 index 0000000..ac13baa --- /dev/null +++ b/prestashop-image/edubox-shopurl.patch @@ -0,0 +1,32 @@ +--- a/classes/shop/ShopUrl.php ++++ b/classes/shop/ShopUrl.php +@@ -175,15 +175,23 @@ + + public static function getMainShopDomain($id_shop = null) + { +- ShopUrl::cacheMainDomainForShop($id_shop); +- +- return self::$main_domain[(int) $id_shop] ?? null; ++ // EduBox: dynamic public domain behind reverse proxy or direct local access. ++ // Always use the request host instead of the domain stored in database. ++ // Keep non-standard ports (e.g. localhost:8088) so local access works. ++ $host = Tools::getHttpHost(false, false, false); ++ if (substr($host, -3) === ':80' || substr($host, -4) === ':443') { ++ $host = substr($host, 0, strrpos($host, ':')); ++ } ++ return $host; + } + + public static function getMainShopDomainSSL($id_shop = null) + { +- ShopUrl::cacheMainDomainForShop($id_shop); +- +- return self::$main_domain_ssl[(int) $id_shop] ?? null; ++ // EduBox: dynamic public domain behind reverse proxy or direct local access. ++ $host = Tools::getHttpHost(false, false, false); ++ if (substr($host, -3) === ':80' || substr($host, -4) === ':443') { ++ $host = substr($host, 0, strrpos($host, ':')); ++ } ++ return $host; + } + } diff --git a/prestashop-image/edubox-tools.patch b/prestashop-image/edubox-tools.patch new file mode 100644 index 0000000..b371e08 --- /dev/null +++ b/prestashop-image/edubox-tools.patch @@ -0,0 +1,44 @@ +--- a/classes/Tools.php 2026-06-04 14:48:44.000000000 +0000 ++++ b/classes/Tools.php 2026-06-23 16:34:13.226899992 +0000 +@@ -269,8 +269,10 @@ + */ + public static function getShopDomain($http = false, $entities = false) + { +- if (!$domain = ShopUrl::getMainShopDomain()) { +- $domain = Tools::getHttpHost(); ++ // EduBox: dynamic domain + keep non-standard ports (e.g. localhost:8088). ++ $domain = Tools::getHttpHost(false, false, false); ++ if (substr($domain, -3) === ':80' || substr($domain, -4) === ':443') { ++ $domain = substr($domain, 0, strrpos($domain, ':')); + } + if ($entities) { + $domain = htmlspecialchars($domain, ENT_COMPAT, 'UTF-8'); +@@ -292,14 +294,16 @@ + */ + public static function getShopDomainSsl($http = false, $entities = false) + { +- if (!$domain = ShopUrl::getMainShopDomainSSL()) { +- $domain = Tools::getHttpHost(); ++ // EduBox: dynamic domain + keep non-standard ports. ++ $domain = Tools::getHttpHost(false, false, false); ++ if (substr($domain, -3) === ':80' || substr($domain, -4) === ':443') { ++ $domain = substr($domain, 0, strrpos($domain, ':')); + } + if ($entities) { + $domain = htmlspecialchars($domain, ENT_COMPAT, 'UTF-8'); + } + if ($http) { +- $domain = static::getProtocol((bool) Configuration::get('PS_SSL_ENABLED')) . $domain; ++ $domain = static::getProtocol(Tools::usingSecureMode()) . $domain; + } + + return $domain; +@@ -2246,7 +2250,7 @@ + $rewrite_settings = (int) Configuration::get('PS_REWRITING_SETTINGS', null, null, (int) $uri['id_shop']); + } + +- $domain_rewrite_cond = 'RewriteCond %{HTTP_HOST} ^' . $domain . '$' . PHP_EOL; ++ $domain_rewrite_cond = ''; // EduBox: removed HTTP_HOST condition for dynamic domains + // Rewrite virtual multishop uri + if ($uri['virtual']) { + if (!$rewrite_settings) { diff --git a/prestashop-image/proxy.conf b/prestashop-image/proxy.conf new file mode 100644 index 0000000..1dd0b9c --- /dev/null +++ b/prestashop-image/proxy.conf @@ -0,0 +1,10 @@ +# EduBox reverse proxy handling +# Apache sees HTTP requests from the EduBox resolver. The public request is HTTPS. +SetEnvIf X-Forwarded-Proto ^https$ HTTPS=on +SetEnvIf X-Forwarded-Proto ^https$ SERVER_PORT=443 + +# Enable .htaccess overrides for PrestaShop URL rewriting (images, products, etc.) + + AllowOverride All + Require all granted + diff --git a/prestashop-image/translations-symfony-fr-FR.zip b/prestashop-image/translations-symfony-fr-FR.zip new file mode 100644 index 0000000..3a13ad4 Binary files /dev/null and b/prestashop-image/translations-symfony-fr-FR.zip differ diff --git a/server/prisma/seed.ts b/server/prisma/seed.ts index af569ce..30e3d19 100644 --- a/server/prisma/seed.ts +++ b/server/prisma/seed.ts @@ -53,7 +53,7 @@ async function main() { { name: "PrestaShop 9 vierge (edubox)", type: "prestashop", - dockerImage: "151.80.60.98:3001/yacine/edubox/edubox-prestashop:9-edubox-8", + dockerImage: "151.80.60.98:3001/yacine/edubox/edubox-prestashop:9-edubox-9", dbImage: "mariadb:10.11", dbName: "prestashop", dbUser: "prestashop",