diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 00000000..389be000
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,20 @@
+.git
+.github
+.dockerignore
+.php-cs-fixer.cache
+.php-cs-fixer.dist.php
+.vscode
+.env*
+build
+config_*.xml
+dist
+docker
+node_modules
+scripts
+vendor
+work
+_dev/node_modules
+views/css/_dev
+views/images/*
+!views/images/favicon.ico
+**/.DS_Store
diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml
index 1aec0e8e..4d40d240 100644
--- a/.github/workflows/build-release.yml
+++ b/.github/workflows/build-release.yml
@@ -1,74 +1,32 @@
-name: Build
-on: [push, pull_request]
+name: Build release package
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ workflow_dispatch:
+
+permissions:
+ contents: read
jobs:
- deploy:
- name: Build dependencies & create artifact
+ build-release:
+ name: Build installable ZIP
runs-on: ubuntu-latest
+ timeout-minutes: 20
+
steps:
- name: Checkout
- uses: actions/checkout@v3
- - name: Install composer dependencies
- run: composer install --no-dev -o
- - name: Setup node env
- uses: actions/setup-node@v3
- with:
- node-version: 18
- - name: Install node dependencies
- working-directory: ./_dev
- run: npm i
- - name: Build assets
- working-directory: ./_dev
- run: npm run build
- - name: Remove unuseful data
- run: |
- rm .git/ -rf
- rm .github/ -rf
- - name: Prepare auto-index tool
- run: |
- composer global require prestashop/autoindex
- - name: Generate index.php
- run: |
- ~/.composer/vendor/bin/autoindex
- - name: Create & upload artifact
- uses: actions/upload-artifact@v1
- with:
- name: ${{ github.event.repository.name }}
- path: ../
- update_release_draft:
- runs-on: ubuntu-latest
- needs: [deploy]
- if: github.event_name == 'push' && github.event.ref == 'refs/heads/master'
- steps:
- - name: Download artifact
- uses: actions/download-artifact@v1
- with:
- name: ${{ github.event.repository.name }}
- - id: release_info
- uses: toolmantim/release-drafter@v5
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- - name: Prepare for Release
- run: |
- cd ${{ github.event.repository.name }}
- zip -r ${{ github.event.repository.name }}.zip ${{ github.event.repository.name }}
- - name: Clean existing assets
- shell: bash
- run: |
- curl -fsSL https://github.com/github/hub/raw/master/script/get | bash -s 2.14.1
- assets=`bin/hub api -t repos/${{ github.repository }}/releases/${{ steps.release_info.outputs.id }}/assets | awk '/\].url/ { print $2 }'`
- for asset in $assets
- do
- bin/hub api -X DELETE $asset
- done
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- - name: Publish to GitHub Release
- uses: actions/upload-release-asset@v1.0.1
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ uses: actions/checkout@v7
+
+ - name: Build and validate release
+ run: scripts/build-release.sh
+
+ - name: Upload release packages
+ uses: actions/upload-artifact@v7
with:
- upload_url: ${{ steps.release_info.outputs.upload_url }}
- asset_path: ./${{ github.event.repository.name }}/${{ github.event.repository.name }}.zip
- asset_name: ${{ github.event.repository.name }}.zip
- asset_content_type: application/zip
+ name: prettyblocks-${{ github.sha }}
+ path: dist/*.zip
+ if-no-files-found: error
+ retention-days: 14
diff --git a/.gitignore b/.gitignore
index 63a42462..b13d8e30 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
config_*.xml
build/
+dist/
vendor/
node_modules/
**/node_modules
@@ -12,4 +13,4 @@ test.php
.php-cs-fixer.cache
_dev/yarn.lock
.env
-!views/js/build
\ No newline at end of file
+!views/js/build
diff --git a/_dev/src/components/Iframe.vue b/_dev/src/components/Iframe.vue
index db3ba71e..780b94b2 100644
--- a/_dev/src/components/Iframe.vue
+++ b/_dev/src/components/Iframe.vue
@@ -34,12 +34,7 @@ let currentPrettyBlocksId = computed(() => currentBlock.id_prettyblocks)
* onMounted events
*/
onMounted(() => {
-
- document.addEventListener('DOMContentLoaded', () => {
- setTimeout(() => {
- prettyBlocksContext.setIframe()
- }, 200)
- })
+ prettyBlocksContext.setIframe()
})
watch(currentBlock, (newVal, oldValue) => {
@@ -71,6 +66,7 @@ watch(prettyBlocksContext.psContext, () => {
allow="geolocation; microphone; camera; midi; encrypted-media"
:sandbox="iframe_sandbox"
:class="[height, width, showLoader ? 'opacity-50' : '']" class="border-none h-full mx-auto rounded" :src="filteredURL"
+ @load="prettyBlocksContext.iframeLoaded"
frameborder="0">
Chargement en cours
@@ -81,4 +77,4 @@ watch(prettyBlocksContext.psContext, () => {
#website-iframe {
transition: all 0.5s ease;
}
-
\ No newline at end of file
+
diff --git a/_dev/src/store/pinia.js b/_dev/src/store/pinia.js
index 721e2aa1..bdaf68e3 100644
--- a/_dev/src/store/pinia.js
+++ b/_dev/src/store/pinia.js
@@ -123,8 +123,8 @@ export const usePrettyBlocksContext = defineStore('prettyBlocksContext', {
setIframe() {
this.$patch((state) => {
state.iframe.domElement = ref(document.getElementById('website-iframe'))
- this.listenIframe()
})
+ this.listenIframe()
},
displaySettingsPanel() {
this.$patch((state) => {
@@ -157,16 +157,24 @@ export const usePrettyBlocksContext = defineStore('prettyBlocksContext', {
})
},
listenIframe() {
+ window.removeEventListener("message", eventHandler);
window.addEventListener("message", eventHandler);
- this.iframe.domElement.addEventListener('load', (e) => {
- setTimeout(() => {
- this.sendPrettyBlocksEvents('initIframe')
- this.sendPrettyBlocksEvents('getContext')
- this.sendPrettyBlocksEvents('getZones')
- this.hideLoader()
- this.emit('iframeLoaded')
- },100)
- })
+ },
+ iframeLoaded(event) {
+ if (!this.iframe.domElement && event && event.currentTarget) {
+ this.$patch((state) => {
+ state.iframe.domElement = ref(event.currentTarget)
+ })
+ this.listenIframe()
+ }
+
+ setTimeout(() => {
+ this.sendPrettyBlocksEvents('initIframe')
+ this.sendPrettyBlocksEvents('getContext')
+ this.sendPrettyBlocksEvents('getZones')
+ this.hideLoader()
+ this.emit('iframeLoaded')
+ },100)
},
sendPrettyBlocksEvents(eventType, data = []) {
@@ -440,4 +448,4 @@ export const storedBlocks = defineStore('storedBlocks', {
}
-})
\ No newline at end of file
+})
diff --git a/controllers/front/ajax.php b/controllers/front/ajax.php
index 984b06a6..ef499ac7 100644
--- a/controllers/front/ajax.php
+++ b/controllers/front/ajax.php
@@ -271,12 +271,14 @@ public function displayAjaxduplicateState()
'id_shop',
'id_lang',
'position',
+ 'instance_id',
];
foreach ($excludedProperties as $property) {
unset($originalValues[$property]);
}
$newBlock = new PrettyBlocksModel();
$newBlock->hydrate($originalValues);
+ $newBlock->instance_id = uniqid();
$newBlock->id_shop = $idShop;
$newBlock->id_lang = (int) $language;
$newBlock->add();
diff --git a/docker/Dockerfile b/docker/Dockerfile
new file mode 100644
index 00000000..f5451284
--- /dev/null
+++ b/docker/Dockerfile
@@ -0,0 +1,11 @@
+ARG PRESTASHOP_IMAGE=prestashop/prestashop:8.2.7-8.1-apache
+
+FROM composer:2 AS composer
+
+FROM ${PRESTASHOP_IMAGE}
+
+COPY --from=composer /usr/bin/composer /usr/local/bin/composer
+COPY scripts/install-prettyblocks.sh /tmp/init-scripts/90-install-prettyblocks.sh
+COPY scripts/install-prettyblocks-release.php /usr/local/lib/prettyblocks/install-release.php
+
+RUN chmod +x /tmp/init-scripts/90-install-prettyblocks.sh
diff --git a/docker/README.md b/docker/README.md
new file mode 100644
index 00000000..c7099b2b
--- /dev/null
+++ b/docker/README.md
@@ -0,0 +1,81 @@
+# PrettyBlocks compatibility stacks
+
+This Compose project runs the current PrettyBlocks checkout against two isolated
+shops:
+
+| Service | PrestaShop | PHP | URL |
+| --- | --- | --- | --- |
+| `ps82` | 8.2.7 | 8.1 | |
+| `ps91` | 9.1.4 | 8.5 | |
+
+Both shops use MariaDB 10.11. The repository is bind-mounted into each shop,
+while each stack keeps its own Composer `vendor` volume. A one-shot Node
+container builds the Vite assets before the shops start, then the image init
+hook installs PrettyBlocks automatically on the first boot.
+
+## Start
+
+```bash
+docker compose -f docker/compose.yml up --build -d --wait
+docker/scripts/smoke-test.sh
+```
+
+The back office is available at `/admin-dev` on both ports:
+
+- login: `demo@prestashop.com`
+- password: `prestashop_demo`
+
+Use `PS82_PORT` and `PS91_PORT` to override the host ports if needed. The PS 9.1
+stack uses its recommended PHP 8.5 runtime by default. To compare both
+PrestaShop cores on PHP 8.1 instead:
+
+```bash
+PS91_IMAGE=prestashop/prestashop:9.1.4-8.1 \
+ docker compose -f docker/compose.yml up --build -d --wait
+```
+
+After changing files in `_dev`, rebuild the editor bundle with:
+
+```bash
+docker compose -f docker/compose.yml run --rm assets
+```
+
+## Logs and shell access
+
+```bash
+docker compose -f docker/compose.yml logs -f ps82 ps91
+docker compose -f docker/compose.yml exec ps82 bash
+docker compose -f docker/compose.yml exec ps91 bash
+```
+
+## Reset everything
+
+This removes both shops and databases, including all test content:
+
+```bash
+docker compose -f docker/compose.yml down -v
+```
+
+## Build and test the release ZIP
+
+The release builder runs Composer and Vite in Docker, creates an installable ZIP
+with a top-level `prettyblocks/` directory, and validates its contents:
+
+```bash
+scripts/build-release.sh
+```
+
+The versioned archive and its stable `prettyblocks.zip` alias are written to
+`dist/`. To prove that the ZIP is self-contained, run it against two fresh,
+temporary shops. This test disables both the source asset build and the runtime
+Composer install, so only files present in the archive can be used:
+
+```bash
+scripts/test-release.sh
+```
+
+The release test validates and extracts the ZIP with PrestaShop's native ZIP
+handler, then installs that extracted artifact with the PrestaShop CLI. It uses
+ports `8182` and `8191` by default and deletes its shops, databases, and
+extracted module when it finishes. Override the ports with `PS82_RELEASE_PORT`
+and `PS91_RELEASE_PORT` if needed.
diff --git a/docker/compose.release-test.yml b/docker/compose.release-test.yml
new file mode 100644
index 00000000..cb5f898d
--- /dev/null
+++ b/docker/compose.release-test.yml
@@ -0,0 +1,19 @@
+services:
+ assets:
+ working_dir: /tmp
+ command: ["true"]
+ volumes: !override []
+
+ ps82:
+ environment:
+ PRETTYBLOCKS_RELEASE_ARCHIVE: /tmp/prettyblocks.zip
+ volumes: !override
+ - ps82-data:/var/www/html
+ - ${PRETTYBLOCKS_RELEASE_ARCHIVE_PATH}:/tmp/prettyblocks.zip:ro
+
+ ps91:
+ environment:
+ PRETTYBLOCKS_RELEASE_ARCHIVE: /tmp/prettyblocks.zip
+ volumes: !override
+ - ps91-data:/var/www/html
+ - ${PRETTYBLOCKS_RELEASE_ARCHIVE_PATH}:/tmp/prettyblocks.zip:ro
diff --git a/docker/compose.yml b/docker/compose.yml
new file mode 100644
index 00000000..688f8131
--- /dev/null
+++ b/docker/compose.yml
@@ -0,0 +1,133 @@
+name: prettyblocks
+
+x-prestashop-environment: &prestashop-environment
+ PS_INSTALL_AUTO: "1"
+ PS_LANGUAGE: fr
+ PS_COUNTRY: FR
+ PS_ALL_LANGUAGES: "0"
+ PS_DEV_MODE: "1"
+ PS_FOLDER_ADMIN: admin-dev
+ PS_FOLDER_INSTALL: install-dev
+ PS_HANDLE_DYNAMIC_DOMAIN: "0"
+ ADMIN_MAIL: demo@prestashop.com
+ ADMIN_PASSWD: prestashop_demo
+ DB_NAME: prestashop
+ DB_USER: prestashop
+ DB_PASSWD: prestashop
+
+x-database-environment: &database-environment
+ MARIADB_DATABASE: prestashop
+ MARIADB_USER: prestashop
+ MARIADB_PASSWORD: prestashop
+ MARIADB_ROOT_PASSWORD: root
+ MARIADB_AUTO_UPGRADE: "1"
+
+x-database-healthcheck: &database-healthcheck
+ test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
+ interval: 5s
+ timeout: 5s
+ retries: 30
+ start_period: 20s
+
+x-prestashop-healthcheck: &prestashop-healthcheck
+ interval: 10s
+ timeout: 5s
+ retries: 30
+ start_period: 5m
+
+services:
+ assets:
+ image: node:20-alpine
+ working_dir: /app/_dev
+ command:
+ - sh
+ - -lc
+ - corepack yarn install --frozen-lockfile --non-interactive && corepack yarn build
+ volumes:
+ - ..:/app
+ - prettyblocks-node-modules:/app/_dev/node_modules
+
+ db82:
+ image: mariadb:10.11
+ environment: *database-environment
+ command: --max-allowed-packet=64M
+ volumes:
+ - db82-data:/var/lib/mysql
+ healthcheck: *database-healthcheck
+ networks:
+ - net82
+
+ ps82:
+ build:
+ context: .
+ args:
+ PRESTASHOP_IMAGE: ${PS82_IMAGE:-prestashop/prestashop:8.2.7-8.1-apache}
+ depends_on:
+ assets:
+ condition: service_completed_successfully
+ db82:
+ condition: service_healthy
+ environment:
+ <<: *prestashop-environment
+ DB_SERVER: db82
+ PS_DOMAIN: "localhost:${PS82_PORT:-8082}"
+ ports:
+ - "${PS82_PORT:-8082}:80"
+ volumes:
+ - ps82-data:/var/www/html
+ - ..:/var/www/html/modules/prettyblocks
+ - prettyblocks-vendor82:/var/www/html/modules/prettyblocks/vendor
+ healthcheck:
+ <<: *prestashop-healthcheck
+ test: ["CMD", "curl", "-fsS", "-H", "Host: localhost:${PS82_PORT:-8082}", "http://127.0.0.1/"]
+ networks:
+ - net82
+
+ db91:
+ image: mariadb:10.11
+ environment: *database-environment
+ command: --max-allowed-packet=64M
+ volumes:
+ - db91-data:/var/lib/mysql
+ healthcheck: *database-healthcheck
+ networks:
+ - net91
+
+ ps91:
+ build:
+ context: .
+ args:
+ PRESTASHOP_IMAGE: ${PS91_IMAGE:-prestashop/prestashop:9.1.4-apache}
+ depends_on:
+ assets:
+ condition: service_completed_successfully
+ db91:
+ condition: service_healthy
+ environment:
+ <<: *prestashop-environment
+ DB_SERVER: db91
+ PS_DOMAIN: "localhost:${PS91_PORT:-8091}"
+ ports:
+ - "${PS91_PORT:-8091}:80"
+ volumes:
+ - ps91-data:/var/www/html
+ - ..:/var/www/html/modules/prettyblocks
+ - prettyblocks-vendor91:/var/www/html/modules/prettyblocks/vendor
+ healthcheck:
+ <<: *prestashop-healthcheck
+ test: ["CMD", "curl", "-fsS", "-H", "Host: localhost:${PS91_PORT:-8091}", "http://127.0.0.1/"]
+ networks:
+ - net91
+
+networks:
+ net82:
+ net91:
+
+volumes:
+ db82-data:
+ db91-data:
+ ps82-data:
+ ps91-data:
+ prettyblocks-vendor82:
+ prettyblocks-vendor91:
+ prettyblocks-node-modules:
diff --git a/docker/release.Dockerfile b/docker/release.Dockerfile
new file mode 100644
index 00000000..396aa22f
--- /dev/null
+++ b/docker/release.Dockerfile
@@ -0,0 +1,93 @@
+# syntax=docker/dockerfile:1.7
+
+FROM node:20-alpine AS assets
+
+WORKDIR /src
+COPY _dev/package.json _dev/yarn.lock ./_dev/
+RUN --mount=type=cache,target=/usr/local/share/.cache/yarn \
+ cd _dev \
+ && corepack yarn install --frozen-lockfile --non-interactive
+
+COPY _dev ./_dev
+RUN cd _dev && corepack yarn build
+
+
+FROM composer:2 AS production-dependencies
+
+WORKDIR /src
+COPY . .
+RUN --mount=type=cache,target=/tmp/cache \
+ COMPOSER_CACHE_DIR=/tmp/cache composer install \
+ --no-dev \
+ --no-interaction \
+ --no-progress \
+ --prefer-dist \
+ --optimize-autoloader \
+ --classmap-authoritative
+
+
+FROM composer:2 AS release-tools
+
+WORKDIR /src
+COPY . .
+RUN --mount=type=cache,target=/tmp/cache \
+ COMPOSER_CACHE_DIR=/tmp/cache composer install \
+ --no-interaction \
+ --no-progress \
+ --prefer-dist
+
+
+FROM composer:2 AS package
+
+ARG MODULE_VERSION
+
+RUN apk add --no-cache zip
+
+COPY --from=production-dependencies /src /stage/prettyblocks
+COPY --from=assets /src/build /stage/prettyblocks/build
+COPY --from=release-tools /src/vendor /tools/vendor
+
+RUN set -eux; \
+ test -n "$MODULE_VERSION"; \
+ rm -rf \
+ /stage/prettyblocks/.git \
+ /stage/prettyblocks/.github \
+ /stage/prettyblocks/_dev \
+ /stage/prettyblocks/docker \
+ /stage/prettyblocks/dist \
+ /stage/prettyblocks/node_modules \
+ /stage/prettyblocks/scripts \
+ /stage/prettyblocks/work; \
+ rm -f \
+ /stage/prettyblocks/.dockerignore \
+ /stage/prettyblocks/.env \
+ /stage/prettyblocks/.env.local \
+ /stage/prettyblocks/.env.development.local \
+ /stage/prettyblocks/.env.test.local \
+ /stage/prettyblocks/.env.production.local \
+ /stage/prettyblocks/.gitignore \
+ /stage/prettyblocks/.php-cs-fixer.cache \
+ /stage/prettyblocks/.php-cs-fixer.dist.php \
+ /stage/prettyblocks/config.xml \
+ /stage/prettyblocks/config_*.xml; \
+ find /stage/prettyblocks/views/images \
+ -mindepth 1 \
+ -maxdepth 1 \
+ -type f \
+ ! -name favicon.ico \
+ ! -name index.php \
+ -delete; \
+ /tools/vendor/bin/autoindex \
+ prestashop:add:index \
+ --exclude=vendor \
+ --no-interaction \
+ /stage/prettyblocks; \
+ mkdir -p /out; \
+ cd /stage; \
+ zip -q -r "/out/prettyblocks-${MODULE_VERSION}.zip" prettyblocks
+
+
+FROM scratch AS release
+
+ARG MODULE_VERSION
+COPY --from=package /out/ /
diff --git a/docker/scripts/install-prettyblocks-release.php b/docker/scripts/install-prettyblocks-release.php
new file mode 100644
index 00000000..572847b7
--- /dev/null
+++ b/docker/scripts/install-prettyblocks-release.php
@@ -0,0 +1,30 @@
+employee = new Employee(1);
+
+ $sourceHandler = new ZipSourceHandler(_PS_MODULE_DIR_, $context->getTranslator());
+ $moduleName = $sourceHandler->getModuleName($argv[1]);
+
+ if ('prettyblocks' !== $moduleName) {
+ throw new RuntimeException(sprintf('Unexpected module name in ZIP: %s', $moduleName));
+ }
+
+ $sourceHandler->handle($argv[1]);
+ printf("Validated and extracted %s from the release ZIP.\n", $moduleName);
+} catch (Throwable $exception) {
+ fwrite(STDERR, $exception::class . ': ' . $exception->getMessage() . "\n");
+ exit(1);
+}
diff --git a/docker/scripts/install-prettyblocks.sh b/docker/scripts/install-prettyblocks.sh
new file mode 100755
index 00000000..da0c620f
--- /dev/null
+++ b/docker/scripts/install-prettyblocks.sh
@@ -0,0 +1,68 @@
+#!/bin/sh
+set -eu
+
+module_dir=/var/www/html/modules/prettyblocks
+install_marker=/var/www/html/var/.prettyblocks-installed
+
+if [ -n "${PRETTYBLOCKS_RELEASE_ARCHIVE:-}" ]; then
+ if [ ! -f "$install_marker" ]; then
+ printf '\n* Installing the packaged PrettyBlocks release...\n'
+ runuser -g www-data -u www-data -- \
+ php -d memory_limit=-1 \
+ /usr/local/lib/prettyblocks/install-release.php \
+ "$PRETTYBLOCKS_RELEASE_ARCHIVE"
+
+ cd /var/www/html
+ runuser -g www-data -u www-data -- \
+ php -d memory_limit=-1 \
+ bin/console prestashop:module install prettyblocks --no-interaction
+
+ # shellcheck disable=SC2016
+ runuser -g www-data -u www-data -- php -r '
+ require "/var/www/html/config/config.inc.php";
+ $module = Module::getInstanceByName("prettyblocks");
+ if (!$module || !Module::isInstalled("prettyblocks") || !$module->active) {
+ fwrite(STDERR, "PrettyBlocks was not installed and enabled from the ZIP.\n");
+ exit(1);
+ }
+ printf(
+ "Installed PrettyBlocks %s from ZIP on PrestaShop %s.\n",
+ $module->version,
+ _PS_VERSION_
+ );
+ '
+
+ touch "$install_marker"
+ chown www-data:www-data "$install_marker"
+ else
+ printf '\n* Packaged PrettyBlocks release is already installed.\n'
+ fi
+
+ exit 0
+fi
+
+printf '\n* Preparing PrettyBlocks dependencies...\n'
+export COMPOSER_ALLOW_SUPERUSER=1
+export COMPOSER_HOME=/tmp/composer
+
+cd "$module_dir"
+composer install \
+ --no-dev \
+ --no-interaction \
+ --no-progress \
+ --optimize-autoloader \
+ --prefer-dist
+
+chown -R www-data:www-data "$module_dir/vendor"
+
+if [ ! -f "$install_marker" ]; then
+ printf '\n* Installing PrettyBlocks...\n'
+ cd /var/www/html
+ runuser -g www-data -u www-data -- \
+ php -d memory_limit=-1 bin/console prestashop:module install prettyblocks --no-interaction
+
+ touch "$install_marker"
+ chown www-data:www-data "$install_marker"
+else
+ printf '\n* PrettyBlocks is already installed.\n'
+fi
diff --git a/docker/scripts/smoke-test.sh b/docker/scripts/smoke-test.sh
new file mode 100755
index 00000000..9ab68f77
--- /dev/null
+++ b/docker/scripts/smoke-test.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+set -eu
+
+script_dir=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
+project_dir=$(dirname "$(dirname "$script_dir")")
+compose_file="$project_dir/docker/compose.yml"
+
+for service in ps82 ps91; do
+ docker compose -f "$compose_file" exec -T "$service" php -r '
+ require "/var/www/html/config/config.inc.php";
+ $module = Module::getInstanceByName("prettyblocks");
+ if (!$module || !Module::isInstalled("prettyblocks") || !$module->active) {
+ fwrite(STDERR, "PrettyBlocks is not installed and enabled.\n");
+ exit(1);
+ }
+ printf(
+ "PrestaShop %s | PHP %s | PrettyBlocks %s\n",
+ _PS_VERSION_,
+ PHP_VERSION,
+ $module->version
+ );
+ '
+
+ docker compose -f "$compose_file" exec -T -e PS_DEV_MODE=0 "$service" \
+ sh -lc 'php bin/console debug:router | grep -q prettyblocks'
+
+ front_url=$(docker compose -f "$compose_file" exec -T "$service" php -r '
+ require "/var/www/html/config/config.inc.php";
+ $context = Context::getContext();
+ echo (new Link())->getPageLink(
+ "index",
+ true,
+ (int) $context->language->id,
+ [],
+ false,
+ (int) $context->shop->id
+ ), PHP_EOL;
+ ' | tail -n 1 | tr -d '\r')
+
+ front_page=$(curl -fsS "${front_url}?prettyblocks=1")
+ if printf '%s' "$front_page" | grep -Fq '[Debug] This page has moved'; then
+ echo "PrettyBlocks iframe URL redirects through a debug page: $front_url" >&2
+ exit 1
+ fi
+done
+
+curl -fsS "http://localhost:${PS82_PORT:-8082}/" >/dev/null
+curl -fsS "http://localhost:${PS91_PORT:-8091}/" >/dev/null
+
+echo "PrettyBlocks smoke tests passed on both PrestaShop versions."
diff --git a/prettyblocks.php b/prettyblocks.php
index 8f45520f..1f43a669 100755
--- a/prettyblocks.php
+++ b/prettyblocks.php
@@ -73,7 +73,7 @@ public function __construct()
{
$this->name = 'prettyblocks';
$this->tab = 'administration';
- $this->version = '3.2.0';
+ $this->version = '3.2.1';
$this->author = 'PrestaSafe';
$this->need_instance = 1;
$this->js_path = $this->_path . 'views/js/';
diff --git a/scripts/build-release.sh b/scripts/build-release.sh
new file mode 100755
index 00000000..eb2d697d
--- /dev/null
+++ b/scripts/build-release.sh
@@ -0,0 +1,130 @@
+#!/usr/bin/env bash
+set -Eeuo pipefail
+
+script_dir=$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
+project_dir=$(dirname "$script_dir")
+output_dir=${1:-"$project_dir/dist"}
+
+version=$(awk -F"'" '/\$this->version[[:space:]]*=/{print $2; exit}' "$project_dir/prettyblocks.php")
+if [[ ! $version =~ ^[0-9A-Za-z][0-9A-Za-z.+-]*$ ]]; then
+ echo "Unable to read a valid module version from prettyblocks.php." >&2
+ exit 1
+fi
+
+for command_name in docker unzip; do
+ if ! command -v "$command_name" >/dev/null 2>&1; then
+ echo "Required command not found: $command_name" >&2
+ exit 1
+ fi
+done
+
+sha256_file() {
+ if command -v sha256sum >/dev/null 2>&1; then
+ sha256sum "$1" | awk '{print $1}'
+ elif command -v shasum >/dev/null 2>&1; then
+ shasum -a 256 "$1" | awk '{print $1}'
+ else
+ echo "A SHA-256 checksum command is required." >&2
+ return 1
+ fi
+}
+
+temporary_dir=$(mktemp -d "${TMPDIR:-/tmp}/prettyblocks-build.XXXXXX")
+trap 'rm -rf "$temporary_dir"' EXIT
+
+echo "Building PrettyBlocks ${version} release with Docker..."
+docker build \
+ --file "$project_dir/docker/release.Dockerfile" \
+ --target release \
+ --build-arg "MODULE_VERSION=$version" \
+ --output "type=local,dest=$temporary_dir/output" \
+ "$project_dir"
+
+archive_name="prettyblocks-${version}.zip"
+built_archive="$temporary_dir/output/$archive_name"
+if [[ ! -f $built_archive ]]; then
+ echo "Docker did not produce the expected archive: $archive_name" >&2
+ exit 1
+fi
+
+listing="$temporary_dir/archive.list"
+unzip -tq "$built_archive"
+unzip -Z1 "$built_archive" > "$listing"
+
+required_paths=(
+ prettyblocks/prettyblocks.php
+ prettyblocks/vendor/autoload.php
+ prettyblocks/vendor/composer/installed.php
+ prettyblocks/build/.vite/manifest.json
+ prettyblocks/views/js/build/build.js
+ prettyblocks/views/css/iframe.css
+ prettyblocks/views/css/dist/main.css
+ prettyblocks/index.php
+)
+
+for required_path in "${required_paths[@]}"; do
+ if ! grep -Fxq "$required_path" "$listing"; then
+ echo "Release archive is missing: $required_path" >&2
+ exit 1
+ fi
+done
+
+if grep -Evq '^prettyblocks(/|$)' "$listing"; then
+ echo "Release archive contains entries outside the prettyblocks/ directory." >&2
+ exit 1
+fi
+
+if grep -Eq '^prettyblocks/(\.git[^/]*|\.github|\.php-cs-fixer[^/]*|_dev|docker|dist|node_modules|scripts|views/css/_dev|work)(/|$)' "$listing"; then
+ echo "Release archive contains development-only root files." >&2
+ exit 1
+fi
+
+if grep -Eq '(^|/)node_modules(/|$)' "$listing"; then
+ echo "Release archive contains Node dependencies." >&2
+ exit 1
+fi
+
+if grep -Eq '(^|/)\.\.(/|$)' "$listing" || grep -Fq $'\\' "$listing"; then
+ echo "Release archive contains an unsafe path." >&2
+ exit 1
+fi
+
+if [[ -n $(sort "$listing" | uniq -d) ]]; then
+ echo "Release archive contains duplicate paths." >&2
+ exit 1
+fi
+
+if grep -Eq '^prettyblocks/\.env($|\.)' "$listing"; then
+ echo "Release archive contains a local environment file." >&2
+ exit 1
+fi
+
+if grep -Eq '^prettyblocks/vendor/(prestashop/(autoindex|php-dev-tools)|friendsofphp|squizlabs)(/|$)' "$listing"; then
+ echo "Release archive contains development Composer dependencies." >&2
+ exit 1
+fi
+
+if ! grep -Eq '^prettyblocks/build/assets/.+\.js$' "$listing"; then
+ echo "Release archive does not contain the built editor JavaScript." >&2
+ exit 1
+fi
+
+if ! grep -Eq '^prettyblocks/build/assets/.+\.css$' "$listing"; then
+ echo "Release archive does not contain the built editor CSS." >&2
+ exit 1
+fi
+
+mkdir -p "$output_dir"
+cp "$built_archive" "$output_dir/$archive_name"
+cp "$built_archive" "$output_dir/prettyblocks.zip"
+
+versioned_checksum=$(sha256_file "$output_dir/$archive_name")
+stable_checksum=$(sha256_file "$output_dir/prettyblocks.zip")
+if [[ $versioned_checksum != "$stable_checksum" ]]; then
+ echo "Versioned and stable release archives differ." >&2
+ exit 1
+fi
+
+echo "Release ready: $output_dir/$archive_name"
+echo "Stable alias: $output_dir/prettyblocks.zip"
+echo "SHA-256: $versioned_checksum"
diff --git a/scripts/test-release.sh b/scripts/test-release.sh
new file mode 100755
index 00000000..a2d2b6b8
--- /dev/null
+++ b/scripts/test-release.sh
@@ -0,0 +1,87 @@
+#!/usr/bin/env bash
+set -Eeuo pipefail
+
+script_dir=$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
+project_dir=$(dirname "$script_dir")
+compose_files=(
+ -f "$project_dir/docker/compose.yml"
+ -f "$project_dir/docker/compose.release-test.yml"
+)
+
+"$script_dir/build-release.sh"
+
+version=$(awk -F"'" '/\$this->version[[:space:]]*=/{print $2; exit}' "$project_dir/prettyblocks.php")
+export PRETTYBLOCKS_RELEASE_ARCHIVE_PATH="$project_dir/dist/prettyblocks-${version}.zip"
+export COMPOSE_PROJECT_NAME="prettyblocks-release-test-${$}"
+export PS82_PORT=${PS82_RELEASE_PORT:-8182}
+export PS91_PORT=${PS91_RELEASE_PORT:-8191}
+
+cleanup() {
+ status=$?
+ if [ "$status" -ne 0 ]; then
+ echo "Release test failed; PrestaShop logs follow:" >&2
+ docker compose "${compose_files[@]}" logs --no-color ps82 ps91 >&2 || true
+ fi
+ docker compose "${compose_files[@]}" down -v --remove-orphans >/dev/null 2>&1 || true
+ return "$status"
+}
+trap cleanup EXIT INT TERM
+
+echo "Installing the packaged ZIP on clean PrestaShop 8.2 and 9.1 stacks..."
+docker compose "${compose_files[@]}" up \
+ --build \
+ --detach \
+ --wait \
+ --wait-timeout "${PRETTYBLOCKS_WAIT_TIMEOUT:-600}"
+
+"$project_dir/docker/scripts/smoke-test.sh"
+
+for service in ps82 ps91; do
+ docker compose "${compose_files[@]}" exec -T "$service" php -r '
+ require "/var/www/html/modules/prettyblocks/vendor/autoload.php";
+
+ if (!class_exists("ScssPhp\\ScssPhp\\Compiler")) {
+ fwrite(STDERR, "The production SCSS dependency is missing.\n");
+ exit(1);
+ }
+
+ if (Composer\InstalledVersions::isInstalled("prestashop/php-dev-tools")) {
+ fwrite(STDERR, "A development Composer dependency leaked into the release.\n");
+ exit(1);
+ }
+ '
+
+ docker compose "${compose_files[@]}" exec -T "$service" sh -lc \
+ 'find modules/prettyblocks -type f -name "*.php" -print0 | xargs -0 -n 1 php -l >/dev/null'
+done
+
+for service_and_port in "ps82:$PS82_PORT" "ps91:$PS91_PORT"; do
+ service=${service_and_port%%:*}
+ port=${service_and_port##*:}
+
+ asset_paths=$(docker compose "${compose_files[@]}" exec -T "$service" php -r '
+ $manifest = json_decode(
+ file_get_contents("modules/prettyblocks/build/.vite/manifest.json"),
+ true,
+ 512,
+ JSON_THROW_ON_ERROR
+ );
+
+ foreach ($manifest as $entry) {
+ echo $entry["file"], "\n";
+ foreach ($entry["css"] ?? [] as $css) {
+ echo $css, "\n";
+ }
+ }
+ ' | tr -d '\r')
+
+ while IFS= read -r asset_path; do
+ [ -n "$asset_path" ] || continue
+ curl -fsS "http://localhost:${port}/modules/prettyblocks/build/${asset_path}" >/dev/null
+ done <<< "$asset_paths"
+
+ curl -fsS "http://localhost:${port}/modules/prettyblocks/views/js/build/build.js" >/dev/null
+ curl -fsS "http://localhost:${port}/modules/prettyblocks/views/css/dist/main.css" >/dev/null
+done
+
+echo "Packaged release passed on both clean PrestaShop stacks."
diff --git a/src/Controller/AdminThemeManagerController.php b/src/Controller/AdminThemeManagerController.php
index 6b3f7cb8..26213c06 100644
--- a/src/Controller/AdminThemeManagerController.php
+++ b/src/Controller/AdminThemeManagerController.php
@@ -167,10 +167,9 @@ private function buildShopUri($shop)
*/
private function getSFUrl($route, $entity = 'sf')
{
-
$useDomain = version_compare(_PS_VERSION_, '9.0.0.0', '<');
- $domain = $useDomain ? '' : \Tools::getShopDomainSsl(true);
+ $domain = !$useDomain ? '' : \Tools::getShopDomainSsl(true);
return $domain . \Link::getUrlSmarty([
'entity' => $entity,
@@ -237,7 +236,14 @@ public function indexAction()
$ajax_editing_url = $this->getSFUrl('prettyblocks_get_connected_employees');
$blockAvailableUrls = $this->getSFUrl('prettyblocks_api_get_blocks_available');
$settingsUrls = $this->getSFUrl('prettyblocks_theme_settings');
- $shop_url = $context->shop->getBaseUrl(true) . $this->getLangLink($context->language->id, $context, $context->shop->id);
+ $shop_url = $link->getPageLink(
+ 'index',
+ true,
+ (int) $context->language->id,
+ [],
+ false,
+ (int) $context->shop->id
+ );
$translator = \Context::getContext()->getTranslator();
$shops = $this->getShops();
$available_language_ids = \Language::getLanguages(true, $context->shop->id);