diff --git a/.docker/Dockerfile b/.docker/Dockerfile new file mode 100644 index 0000000..33da89a --- /dev/null +++ b/.docker/Dockerfile @@ -0,0 +1,38 @@ +ARG PRESTASHOP_VERSION=8.2.1 +ARG PHP_VERSION=8.1 +FROM prestashop/prestashop:${PRESTASHOP_VERSION}-${PHP_VERSION} + +ARG PRESTASHOP_VERSION +ARG PHP_VERSION + +LABEL org.opencontainers.image.source=https://github.com/bulkgate/prestasms +LABEL version=${PRESTASHOP_VERSION}-${PHP_VERSION} + +# install composer with platform dependencies, xdebug +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + curl \ + unzip \ + zip \ + git \ + && curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +RUN pecl install xdebug$(php -r "echo(version_compare(getenv('PHP_VERSION'), '8', '>') ? '' : '-3.1.5');") && \ + docker-php-ext-enable xdebug && \ + #https://www.jetbrains.com/help/idea/configuring-xdebug.html#537ef8ac + echo "xdebug.mode=debug" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \ + echo "xdebug.client_host = host.docker.internal" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini # this is address of default docker bridge network + +COPY config-files/post-install.sh /tmp/post-install-scripts/ + +RUN git config --system --add safe.directory '*' + +RUN chmod +x /tmp/post-install-scripts/post-install.sh + +WORKDIR /var/www/html + +ENV PS_DEV_MODE=1 \ + PS_INSTALL_AUTO=1 + diff --git a/.docker/config-files/post-install.sh b/.docker/config-files/post-install.sh new file mode 100644 index 0000000..2dd4068 --- /dev/null +++ b/.docker/config-files/post-install.sh @@ -0,0 +1,3 @@ +echo "BulkGate module - postinstall" + +su -s /bin/bash www-data -c "bin/console prestashop:module install bg_prestasms" diff --git a/.docker/generate.sh b/.docker/generate.sh new file mode 100755 index 0000000..a312970 --- /dev/null +++ b/.docker/generate.sh @@ -0,0 +1,82 @@ +set -e + +save_version_into_file() { + local versions_file="$SCRIPT_DIR/versions.json" + local php_version="$1" + local prestashop_version="$2" + + php -r ' + $file = $argv[1]; + $php = $argv[2]; + $ps = $argv[3]; + $arr = json_decode(file_get_contents($file), true); + $arr[] = ["php" => $php, "prestashop" => $ps]; + // Odstraň duplicity + $arr = array_map("unserialize", array_unique(array_map("serialize", $arr))); + // Seřaď podle prestashop, pak php + usort($arr, function($a, $b) { + $cmp = version_compare($a["prestashop"], $b["prestashop"]); + if ($cmp !== 0) return $cmp; + return version_compare($a["php"], $b["php"]); + }); + file_put_contents($file, json_encode($arr, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES)); + ' "$versions_file" "$php_version" "$prestashop_version" +} + +# get directory of this file +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) + +# Zpracování argumentů +PUSH=1 +EMIT_VERSION=1 +PHP_VERSION="" +PRESTASHOP_VERSION="" +BUILD_ARGS="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --no-push) + PUSH=0 + shift + ;; + --no-emit-version) + EMIT_VERSION=0 + shift + ;; + --php-version) + PHP_VERSION="$2" + shift 2 + ;; + --prestashop-version) + PRESTASHOP_VERSION="$2" + shift 2 + ;; + *) + shift + ;; + esac +done + + +if [[ -n "$PHP_VERSION" && -n "$PRESTASHOP_VERSION" ]]; then + BUILD_ARGS="$BUILD_ARGS --build-arg PHP_VERSION=$PHP_VERSION --build-arg PRESTASHOP_VERSION=$PRESTASHOP_VERSION" +fi + +# build image +docker build $BUILD_ARGS "$SCRIPT_DIR" -t ghcr.io/bulkgate/prestasms +VERSION=$(docker inspect --format '{{ .Config.Labels.version }}' ghcr.io/bulkgate/prestasms) + +# tag image +docker image tag ghcr.io/bulkgate/prestasms ghcr.io/bulkgate/prestasms:$VERSION + +# add version matrix +if [[ -n "$BUILD_ARGS" && $EMIT_VERSION -eq 1 ]]; then + echo "writing version into file ... $VERSION" + save_version_into_file $PHP_VERSION $PRESTASHOP_VERSION +fi + +# push image +if [ $PUSH -eq 1 ]; then + echo "Pushing image..." + docker push ghcr.io/bulkgate/prestasms:$VERSION +fi diff --git a/.docker/readme.md b/.docker/readme.md new file mode 100644 index 0000000..af4baf8 --- /dev/null +++ b/.docker/readme.md @@ -0,0 +1,28 @@ +# Docker image +Tento image je určen pro účely vývoje BulkGate PrestaShop SMS modulu. Image můžete použít k napojení do IDE a také ke spuštění webové aplikace. +Image obsahuje instalaci composeru a xdebugu. [Seznam dostupných images](https://github.com/BulkGate/prestasms/pkgs/container/prestasms). + +## 1. Definování matice verzí +V souboru versions.json jsou uloženy meta informace o matici dostupných verzí php a prestashop aplikace. Pokud tento seznam potřebujete rozšířit, tak stačí spustit: + +```shell +.docker/generate.sh --php-version 8.4 --prestashop-version 9.0.0 --no-push +``` + +## 2. Vytvoření image +Pokud chcete pro lokální účely vytvořit image, který nechcete distribuovat do registru a ani přidat do versions.json, můžete spustit: + + +```shell +.docker/generate.sh --php-version=8.4 --prestashop-version=9.0.0 --no-push --no-emit-version +``` + +> Každé spuštění scriptu vygeneruje 2 image a to (ghcr.io/bulkgate/prestasms) pro lokální použití a (ghcr.io/bulkgate/prestasms:9.0.0-8.4) pro nahrání do registru + + +## 3. Použití image + +```yaml +prestashop: + image: ghcr.io/bulkgate/prestasms:8.2.1-8.1 +``` \ No newline at end of file diff --git a/.docker/versions.json b/.docker/versions.json new file mode 100644 index 0000000..47235b2 --- /dev/null +++ b/.docker/versions.json @@ -0,0 +1,22 @@ +[ + { + "php": "7.4", + "prestashop": "1.7.8.0" + }, + { + "php": "7.4", + "prestashop": "8.2.0" + }, + { + "php": "8.1", + "prestashop": "8.2.0" + }, + { + "php": "7.4", + "prestashop": "8.2.1" + }, + { + "php": "8.1", + "prestashop": "8.2.1" + } +] \ No newline at end of file diff --git a/.env.template b/.env.template new file mode 100644 index 0000000..d5e2aed --- /dev/null +++ b/.env.template @@ -0,0 +1,14 @@ +#verze naseho image prestashopu - https://github.com/BulkGate/prestasms/pkgs/container/prestasms +PRESTASHOP_IMAGE_VERSION=1.7.8.0-7.4 + +# kdykoliv nastavujes PRESTASHOP_DOMAIN s portem, tak presne ten samy port musi byt uveden zde +PRESTASHOP_PORT=8081 + +# domena vcetne portu v pripade, ze ne to neni default http (80), nebo https (443) port +PRESTASHOP_DOMAIN=localhost:8081 + +# na tomto portu pobezi adminer web service +ADMINER_PORT=9081 + +# nazev databaze, kterou bude pouzivat instance prestashopu +MYSQL_DATABASE=prestashop \ No newline at end of file diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..42d4a24 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,47 @@ +name: Build docker images + +on: + push: + branches: + - '**' + tags-ignore: + - '**' + paths: + - '.docker/**' + - '.github/workflows/docker.yml' + +jobs: + init-matrix: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Read version matrix + id: set-matrix + run: echo "matrix=$(jq -c . < .docker/versions.json)" >> $GITHUB_OUTPUT + + build: + needs: init-matrix + runs-on: ubuntu-latest + strategy: + matrix: + include: ${{ fromJSON(needs.init-matrix.outputs.matrix) }} + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Prepare executable + run: chmod +x .docker/generate.sh + + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build image + run: .docker/generate.sh --no-emit-version --php-version ${{ matrix.php }} --prestashop-version ${{ matrix.prestashop }} \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..5cde13b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,45 @@ +name: Release application + +on: + release: + types: [created] + +jobs: + bundle: + runs-on: ubuntu-latest + container: ghcr.io/bulkgate/prestasms:8.2.1-8.1 + env: + GH_TOKEN: ${{github.token}} #required by gh cli + steps: + - uses: actions/checkout@v1 + + - name: Install packages + run: composer install --prefer-dist --no-progress --no-suggest + + - name: Apply code style + run: composer run prestashop-lint + + - name: Keep only production dependencies + run: composer install --prefer-dist --no-progress --no-suggest --no-dev + + - name: Create plugin directory + run: mkdir -p /tmp/bg_prestasms + + - name: Copy plugin files + run: cp -r config controllers src translations vendor views logo.png LICENSE *.php composer.json /tmp/bg_prestasms + + - name: Create plugin zip + run: cd /tmp && zip -r bg_prestasms.zip bg_prestasms + + - name: Install GitHub CLI + run: | + apt-get update + apt-get install -y curl + curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg + chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null + apt-get update + apt-get install -y gh + + - name: Add zip to release + run: gh release upload ${{github.ref_name}} /tmp/bg_prestasms.zip \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..64a7daa --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,61 @@ +name: Run application tests + +on: + push: + branches: + - '**' + tags-ignore: + - '**' + paths-ignore: + - '.docker/**' + - '.github/**' + - '!.github/workflows/tests.yml' + +jobs: + tests: + runs-on: ubuntu-latest + + # minimum and maximum supported version + strategy: + matrix: + include: + - php: 7.4 + prestashop: 1.7.8.0 + - php: 7.4 + prestashop: 8.2.1 + - php: 8.1 + prestashop: 8.2.1 + + container: ghcr.io/bulkgate/prestasms:${{ matrix.prestashop }}-${{ matrix.php }} + + steps: + - uses: actions/checkout@v2 + + - name: Install dependencies + run: composer install --prefer-dist --no-progress --no-suggest + + - name: Run test suite + run: composer run tester + + - name: Run static analysis + run: composer run phpstan + + coverage: + runs-on: ubuntu-latest + + container: ghcr.io/bulkgate/prestasms:8.2.1-8.1 + + steps: + - uses: actions/checkout@v2 + + - name: Install dependencies + run: composer install --prefer-dist --no-progress --no-suggest + + - name: Run coverage + run: composer run coverage + + - name: Upload coverage + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: coverage.html \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9bfb0e7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +.idea +coverage.html +config*.xml +tests/**/output/*.actual +tests/**/output/*.expected +config/includes/*.local.yml +*.cache +.env +prestashop +composer.lock +vendor +affiliate.php diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index d8bcbd0..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "extensions"] - path = extensions - url = https://github.com/BulkGate/extensions diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..37c058e --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,21 @@ +in(__DIR__) + ->exclude('vendor'); + +foreach ($ignored as $pathOrFile) +{ + $finder->exclude($pathOrFile); +} + +return $config + ->setUsingCache(true) + ->setFinder($finder); diff --git a/README.md b/README.md index c27f1b3..20d4e2c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,274 @@ +# BulkGate PrestaShop SMS module +http://www.presta-sms.com/ -® +# Lokální vývoj a konfigurace +Pro úpravu parametrů prostředí (např. verze PrestaShopu, doména, port) vytvoř v kořenovém adresáři soubor *.env* podle vzoru *.env.template*. Hodnoty v tomto souboru se použijí při spuštění kontejnerů: +```shell +cp .env.template .env +docker compose up -d +``` +Pokud potřebuješ spustit více instancí s různými verzemi nebo parametry najednou, nastav proměnné prostředí přímo v příkazové řádce (inline): +```shell +MYSQL_DATABASE=prestashop_7 ADMINER_PORT=9070 PRESTASHOP_IMAGE_VERSION=1.7.8.0-7.4 PRESTASHOP_DOMAIN=ps7.dev.bulkgate.com:8070 PRESTASHOP_PORT=8070 docker compose --project-name ps_1_7 up -d +MYSQL_DATABASE=prestashop_8 ADMINER_PORT=9080 PRESTASHOP_IMAGE_VERSION=8.2.1-7.4 PRESTASHOP_DOMAIN=ps8.dev.bulkgate.com:8080 PRESTASHOP_PORT=8080 docker compose --project-name ps_8_2 up -d +``` -http://www.presta-sms.com/ +Aby ti fungovalo napovídání v IDE (prestashop source code), musíš namountovat instalaci prestashopu z kontejneru na disk. +```shell +docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d +``` +Pokud používáš oba compose soubory, tak pred kazdou zmenou konfigurace bysi mel spustit: +```shell +rm -rf prestashop config*.xml +``` + +> Je to z toho duvodu, ze prestashop je v tuto chvili persistentne ulozen v adresari prestashop na tvojem disku a image by tak pri startupu neprovedl instalaci! +> Soubory config.xml vznikaji prave pri instalaci modulu a je lepsi je take odstranit. + +## Known bugs + +### Auto instalace modulu +Instalace modulu probehne v poradku (podle vypisu z console behem startupu kontejneru), ale modul nefunguje. +```text +You have requested a non-existent service "PrestaShop\PrestaShop\Adapter\Shop\Url\BaseUrlProvider" +``` +Toto se vyresi jednoduse tak, ze v kontejneru spustis: +```shell +/tmp/post-install-scripts/post-install.sh +``` + +# Tester guide +Otevři si prohlížeč na http://localhost (default) a nebo podle hodnoty env proměnné PS_DOMAIN, pokud si ji uvedl v soubor **.env**. + +## Setup obchodu + +### Povolení platebních metod pro státy +Payment > preferences > country restrictions ![img.png](img/payment_country_restrictions.png) + +### Povoleni multistoru +Shop parameters > general > Enable multistore ![img_1.png](img/enable_multistore.png) + +### Nastaveni defaultni meny a jazyka +International > Localization ![img.png](img/localization.png) + +## Variables (BulkGate\Plugin\Event\DataLoader interface) +### BulkGate\PrestaShop\Event\Loader\Shop + +#### shop_email, shop_phone +Shop parameters > contact > stores ![img.png](img/contacts_multistore.png) + +#### shop_name +Advanced parameters > multistore ![img.png](img/multistore_shop_name.png) + +#### shop_domain +Advanced parameters > multistore ![img.png](img/multistore_domain.png) + +### BulkGate\PrestaShop\Event\Loader\Product + +#### product_name, product_description +Catalog > products ![img.png](img/product_name.png) + +#### product_ref, product_ean13, product_upc, product_isbn +Catalog > products > details ![img.png](img/product_ref.png) + +#### product_supplier +Catalog > Brands & Suppliers > Suppliers ![img.png](img/product_supplier.png) + +### BulkGate\PrestaShop\Event\Loader\Customer +Customers > {ITEM} > addresses ![img.png](img/customer_detail.png) + +#### customer_address, customer_city, customer_company, customer_country, customer_country_id, customer_email, customer_firstname, customer_id, customer_lastname, customer_mobile, customer_phone, customer_postcode, customer_vat_number +Addresses > {ITEM} ![img.png](img/customer_address.png) + +#### customer_invoice_address, customer_invoice_city, customer_invoice_company, customer_invoice_country, customer_invoice_country_id, customer_invoice_firstname, customer_invoice_lastname, customer_invoice_mobile, customer_invoice_phone, customer_invoice_postcode, customer_invoice_vat_number +Tyto proměnné jsou generovány pouze v případě, že je předané **id_address_invoice** + +### BulkGate\PrestaShop\Event\Loader\Order +Customer service > Merchandise returns ![img.png](img/product_return_settings.png) +Orders > {ITEM} > return products ![img.png](img/product_return.png) +[product return](https://help-center.prestashop.com/en/articles/115000586771-make-a-product-return-in-the-back-office) + + +## PrestaShop +- [Documentation for developers](https://devdocs.prestashop-project.org/8/modules/creation/tutorial/) +- [Legacy/Core/Adapter/PrestaShopBundle](https://devdocs.prestashop-project.org/1.7/development/architecture/file-structure/understanding-src-folder/) +- [coding standards](https://devdocs.prestashop-project.org/8/development/coding-standards/) +- [example modules](https://github.com/PrestaShop/example-modules) +- [how to](https://devdocs.prestashop-project.org/8/modules/sample-modules/order-pages-new-hooks/module-base/) +## Doctrine +- [Documentation for doctrine](https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/data-retrieval-and-manipulation.html#executestatement) + +Nektere sluzby v PrestaShopu maji pouze identifikator v ramci DI kontejneru (prestashop.adapter.data_provider.order_state, atd...). To znamena, ze pokud k nim nechceme pristupovat pres ServiceLocator, +musime jim vytvorit alias. + +```yml +PrestaShop\PrestaShop\Adapter\OrderState\OrderStateDataProvider: '@prestashop.adapter.data_provider.order_state' +``` +>Takto budu moct pouzit sluzbu pomoci type hintu :) + +## Common errors +Expected to find class "XXX" in file "xxx" while importing services from resource "../src/", but it was not found! Check the namespace prefix used with the resource. -> nejspis jsme zapomneli spustit: composer dumpautoload + +## bin/console +CLI nastroj pro prestashop (instalace, odinstalace modulu, atd...) +```sh +sudo -u www-data bash # prihlasim se pod www-data usera +bin/console prestashop:module install bg_prestasms +# Toto by se mohlo vyresit rovnou ve sluzbe prestashop v docker-compose +``` + + +# External Links +- [platform stats](https://storeleads.app/reports/prestashop) +- [https://www.prestasoo.com/](https://www.prestasoo.com/) + +# TODO automatizace vs (admin/customer sms) +Aktualne se za pojem **automatizace** berou admin/customer sms. V blizke budoucnosti se ale automatizace zobecni a bude potreba lepe strukturovat data a tim padem prijit s lepsim API nez s Variables. +Ty bouzel funguji pouze na principu sberneho obejktu, ale uplne jim chybi kontext a treba taky pokrocilejsi metody (flatten -> podpora stavajiciho formatu). + +Jak si dokazu predstavit "nove" Variables? + +## Context index +V principu je to docela podobny koncept graphQL. Kontext tvori zakladni informace (strukturu) o tom, co se bude nacitat. +Kontext je vstup pro loader, ktery na zaklade klicu a hodnot kontextu bude moci naplnit struktury kontextu. + +- id_shop -> id obchodu pro ktery kontext plati +- id_lang -> id jazyku pro ktery kontext plati +- id_currency -> id meny pro kterou kontext plati +- id_carrier -> id dopravce pro ktereho kontext plati +- id_order -> id objednavky pro ktery kontext plati +- id_product -> id produktu pro ktery kontext plati +- id_cart -> id kosiku pro ktery kontext plati +- id_user -> id uzivatele pro ktery kontext plati + + +### Příklad definice kontextu (index) +Takto může vypadat struktura kontextu + +```json5 +{ + id_shop: 1, + id_lang: 1, + order: { + id_order: 5, + id_lang: 1, + id_product: [20, 30], + id_carrier: 3, + }, +} +``` + +### Příklad načteného kontextu (resolved) +Takto může vypadat struktura resolvnutého indexu kontextu. + +```json5 +{ + shop: { //id_shop: 1 + id: 1, + name: "Alza.cz", + email: "support@alza.cz", + currency: "CZK", + phone: "800-600-500", + url: "https://www.alza.cz" + }, + lang: { //id_lang: 1 + id: 1, + iso: "cs" + }, + order: { //literal key + id: 3, + currency: "CZK", + date: "2025-01-23T06:11:00.000Z", + price: 169.9, //value in order currency + lang: { //id_lang: 1 + id: 1, + iso: "cs" + }, + product: [ //id_product: [20, 30] + { + id: 20, + quantity: 2, + price: 11.9, //value in order currency + name: "Mug Today is a good day", + reference: "demo_13" + }, + { + id: 30, + quantity: 1, + price: 19.12, //value in order currency + name: "Hummingbird printed t-shirt (Size: S - Color: Black)", + reference: "demo_1" + } + ], + carrier: { //id_carrier: 3 + price: 0, //relative to order + name: "DPD", + url: "https://dpd.com/tracinking/AXJFGKLOURT", + weight: 300, //grams + tracking_number: "AXJFGKLOURT", + tracking_date: "2025-01-23T12:45:00.000Z" + } + } +} +``` +### Vyhody nevyhody +- Samotne rozdeleni do kontextu ma pozitivni vliv na omezeni vyberu promennych. Uzivatel muze pracovat pouze s promennymi, ktere nabizi kontext, nebo ktere se daji na zaklade kontextu derivovat (foreach ,filtry, formatovace) +- Data budou vzdy strukturovana, takze budeme posilat mensi pocet dat (bez formatovacu a filtru) a budeme schopni vytvorit odpovidajici datove typy s truktury, ktere mohou byt zdokumentovany + +### Automatizace todo +- kazda automatizace bude mit svuj vlastni soubor (neco jako github actions) +- budu mit prehled o vsech spustenych instancich s tim, ze se budu moci podivat i na automatizacni graf (tzn. presne budu videt kam se interpret dostal a jake mel k dispozici data) +- napriklad z detailu (objednavky, kosiku, uzivatele atd..) budu moct otestovat automatizaci -> simulovat. Tim si vyzkousim nastaveni na realnych datech... +- kazda eshop platforma muze (a asi bude) mit vlastni automatizacni soubor, protoze moznosti kazde platformy jsou ruzne. +- mely by se automatizace verzovat (source)? Melo by verzovani byt na zaklade features/deprecations na dane platforme? +- pokud uzivatel vytvori automatizaci a nasledne ji bude spravovat, budou se tyto zmeny verzovat (stejne jako GTM)? +- pokud si uzivatel bude chtit otestovat automatizaci bude to spoustet dummy a nebo realne triggery? +- budou instance automatizaci bezet v dockeru? + - prestashop-1.7 -> /app/model/automation/prestashop/1.7/ -> docker compose run prestashop-1.7 + - prestashop-8 -> /app/model/automation/prestashop/8/ -> docker compose run prestashop-8.0 -> volume bude vest ke zdrojovym souborum automatizace (runtime, triggery) +- docker image: + - bude postaveny tak, aby se dal jednoduse zkonfigurovat (db connection, rabbit connection, network etc...) + - vice mene to bude konzolove php, protoze neni duvod vyuzivat serverove + - automatizace by se spoustely cronem ze strany bulkgate. Je otazka jestli je poustet primo. Abychom zajistili bezpecnost a asynchronni chovani + +Příklad toho, jak by se mohla volat automatizace + +/api/1.0/eshop/automation/run/ +```json5 +{ + context: CONTEXT_INDEX, + data: CONTEXT_INDEX_RESOLVED +} +``` + +# TODO +- docker image: vytvorit image, ktery bude resit (PHP_VERSION, PRESTASHOP_VERSION). Verze modulu je dana gitem. v pripade wordpressu to bude WORDPRESS_VERSION + WOOCOMMERCE_VERSION +- pri spusteni modulu (login) se neprenesou data modulu (pokud nejsou). Az po refreshi se nactou. To je lehce bug, protoze data muzou byt jina kvuli napr. synchronizaci... +- novy hook actionCustomerAccountUpdate -> muzeme aktualizovat informace o uzivateli +- preklady https://devdocs.prestashop-project.org/8/modules/creation/module-translation/new-system/ +- zvalidovat modul oproti implementacim nativnich modulu napr. https://github.com/PrestaShop/example-modules/tree/master/demovieworderhooks +- dodelat nove metody - isLoggedIn atp ... budou to jenom ciste obalky nad standardnim $settings->load('static:token') +- proverit asynchronous order = -1 -> toto se stane, kdyz nastane chyba behem zpracovani. Musime zajistit, ze se tasky opet odeslou +- ~~zkusit marketing opt-in v checkoutu~~ +- ~~dodelat cron~~ +- ~~dopsat testy~~ +- ~~sloty ve web komponente - detail objednavky SEND message button ?~~ Jeste je potreba zjistit, jak by se jednoduse daly navesit event handlery v ramci reactu pro slotted element resp. pokud navesim na , tak chci, aby se event propagoval... +- ~~zprovoznit vsechny hooky~~ +- ~~overit proxyRedirect v pripade zmeny jazyku v settings 192.168.32.3~~ +- ~~zbavit se custom AsynchronousDatabase~~ +- ~~implementovat rozhrani databaze~~ +- ~~defaultni hodnoty pro nastaveni~~ +- ~~browser asset cron nacitat i v admin casti~~ +- ~~Lifecycle modulu (instalace, odinstalace, aktivace, deaktivace) Legacy environment https://devdocs.prestashop-project.org/1.7/modules/concepts/services/#services-in-legacy-environment -> musime doladit Module class~~ + - ~~usporadat service config podle legacy environmentu (abychom mohli prave v Modulu a na frontu pouzivat DI kontejner s nasema sluzbama)~~ + - ~~pouzit tabs zpusob pro definovani menu~~ +- Hooky + - ~~asset, cron, direct -> https://devdocs.prestashop-project.org/8/modules/creation/displaying-content-in-front-office/~~ + - ~~napojeni na kontejner~~ +# bugs: +- ~~UrlGeneratorInterface::ABSOLUTE_URL -> potrebujeme pri odhlaseni a prihlaseni, aby aplikace spravne presmerovavala. // https://github.com/PrestaShop/PrestaShop/issues/18703 - z nejakeho duvodu proste nefunguje ABSOLUTE_URL~~ vyresilo se tak, ze se pouze meni url SPA. +- ~~pri prihlaseni modulu se nekdy zobrazuje last sync jako 01.01.1970~~ +- ~~[Prestashop] pokud je aktivovany (default) Advanced parameters > Security > Back office token protection, tak router negeneruje ABSOLUTE_URL. To potom spatne funguje v pripadech, kdy potrebujeme hard reload (viz. zmena jazyka)~~ +- [module:plugin] kdyz neni nastaveno synchronize="all", tak se potom nereflektuji automatizace. Na frontendu sice jdou videt (data z portalu), ale v modulu se realne nespusti! Je to z duvodu jak je implementovana metoda BulkGate\Plugin\Event\Dispatcher::check!! +Je to optimalizace kvuli rychlosti. Takhle ovsem o tom, co se odeslalo rozhodl modul, protoze vedel co ma nastaveno. Nyni to vsak vedet nebude, tim padem o tom bude muset rozhodnout BG. Mohlo by to do jiste miry mit i vyhodu v tom, ze si uzivatel toto bude moct nastavit na BG. \ No newline at end of file diff --git a/affiliate.template.php b/affiliate.template.php new file mode 100644 index 0000000..16abc7b --- /dev/null +++ b/affiliate.template.php @@ -0,0 +1,10 @@ + BulkGateWhiteLabel . ' SMS', + 'class_name' => 'AdminBulkGateConfigure', + 'parent_class_name' => 'SELL', + 'visible' => true, + 'icon' => 'send', + ], + [ + 'name' => BulkGateWhiteLabel . ' Debug', + 'class_name' => 'AdminBulkGateDebug', + 'parent_class_name' => 'CONFIGURE', + 'visible' => true, + 'icon' => 'bug_report', + ], + ]; public function __construct() { - $this->name = _BG_PRESTASMS_SLUG_; - $this->version = _BG_PRESTASMS_VERSION_; - $this->author = _BG_PRESTASMS_AUTHOR_; + $this->name = 'bg_prestasms'; $this->tab = 'emailing'; - $this->author_uri = _BG_PRESTASMS_AUTHOR_URL_; - $this->ps_versions_compliancy = [ - 'min' => _BG_PRESTASMS_PS_MIN_VERSION_, - 'max' => _PS_VERSION_, - ]; + $this->version = BulkGateModuleVersion; + $this->author = BulkGateWhiteLabel; + $this->author_uri = 'https://www.bulkgate.com/'; parent::__construct(); - $this->ps_di = new PrestaSms\DIContainer(\Db::getInstance()); - $this->ps_settings = $this->ps_di->getSettings(); - $this->ps_translator = $this->ps_di->getTranslator(); - - $this->displayName = _BG_PRESTASMS_NAME_; - $this->description = $this->l('Extend your PrestaShop store capabilities. Send personalized bulk SMS messages. Notify your customers about order status via customer SMS notifications. Receive order updates via Admin SMS notifications.'); + $this->ps_versions_compliancy = [ + 'min' => BulkGateMinimalPrestashopVersion, + 'max' => _PS_VERSION_, + ]; + $this->displayName = BulkGateWhiteLabel . ' SMS'; + $this->description = $this->l('Send personalized SMS messages that your customers will notice! Prevent important notifications from being overlooked among common channels like email. Use new channels such as SMS, RCS, WhatsApp, and others to stand out and capture your customers\' attention.'); $this->confirmUninstall = $this->l('Are you sure you want to uninstall this module?'); - - $this->context->smarty->assign('module_name', $this->name); } - - public function getContent() + public function getContent(): void { - if($this->ps_settings->load('static:application_token', false)) - { - \Tools::redirectAdmin($this->context->link->getAdminLink('AdminPrestaSmsModuleSettingsDefault')); - } - \Tools::redirectAdmin($this->context->link->getAdminLink('AdminPrestaSmsSignIn')); + // we have dedicated controller. + Tools::redirectAdmin($this->get('router')->generate('bulkgate_main_app', [])); } - - public function install() + public function install(): bool { $install = parent::install(); - $this->ps_settings->install(); - PrestaSms\Helpers::installMenu($this->ps_translator); - $this->installHooks(); - return $install; - } + $this->getBulkGateContainer()->getByClass(Plugin\Settings\Settings::class)->install(); + return $install && $this->installHooks(); + } public function uninstall() { $uninstall = parent::uninstall(); - $this->ps_settings->uninstall(); - PrestaSms\Helpers::uninstallMenu(); + + $this->getBulkGateContainer()->getByClass(Plugin\Settings\Settings::class)->uninstall(); return $uninstall; } + private function installHooks(): bool + { + $this->installAdminCustomerSmsHooks(); + $this->installBackOfficeHooks(); + $this->installFrontOfficeHooks(); - public function installHooks() + return true; + } + + private function installAdminCustomerSmsHooks(): bool { $this->registerHook('actionOrderStatusPostUpdate'); $this->registerHook('actionValidateOrder'); @@ -90,312 +99,534 @@ public function installHooks() $this->registerHook('actionAdminOrdersTrackingNumberUpdate'); $this->registerHook('actionPaymentConfirmation'); $this->registerHook('actionProductDelete'); - $this->registerHook('actionProductOutOfStock'); + // $this->registerHook('actionProductOutOfStock'); $this->registerHook('actionProductCancel'); $this->registerHook('actionEmailSendBefore'); $this->registerHook('actionPrestaSmsSendSms'); $this->registerHook('actionPrestaSmsExtendsVariables'); - $this->registerHook('displayAdminOrderRight'); + + return true; } + private function installFrontOfficeHooks(): bool + { + $this->registerHook('displayHeader'); + $this->registerHook('additionalCustomerFormFields'); - public function hookActionOrderStatusPostUpdate(array $params) + return true; + } + + private function installBackOfficeHooks(): bool { - if(isset($params['id_order']) && isset($params['newOrderStatus'])) - { - $order = new Order((int) $params['id_order']); + $this->registerHook('displayAdminOrderSide'); + $this->registerHook('displayBackOfficeHeader'); + $this->registerHook('actionListModules'); - if($order->id !== null) - { - return $this->runHook('order_status_change_'.$params['newOrderStatus']->id, new Extensions\Hook\Variables(array( - 'order_status_id' => $params['newOrderStatus']->id, - 'order_id' => (int) $order->id, - 'lang_id' => (int) $order->id_lang, - 'store_id' => (int) $order->id_shop, - 'customer_id' => (int) $order->id_customer - ))); - } - } return true; } + /* AdminCustomerSms hooks */ + + /** @see https://devdocs.prestashop-project.org/8/modules/concepts/hooks/list-of-hooks/actionorderstatuspostupdate */ + public function hookActionOrderStatusPostUpdate(array $params) + { + if (!isset($params['id_order']) || !isset($params['newOrderStatus'])) { + return; + } + + $order = new Order((int) $params['id_order']); + + $this->runHook('order', 'change-status', new Plugin\Event\Variables([ + 'order_status_id' => $params['newOrderStatus']->id, + 'order_id' => (int) $order->id, + 'lang_id' => (int) $order->id_lang, + 'shop_id' => (int) $order->id_shop, + 'customer_id' => (int) $order->id_customer, + ]), ['order' => $order]); + } + + public function testHookActionOrderStatusPostUpdate() + { + $newOrderStatus = new OrderState(2); + + // should invoke + $this->hookActionOrderStatusPostUpdate(['id_order' => 7, 'newOrderStatus' => $newOrderStatus]); + + // should not invoke + $this->hookActionOrderStatusPostUpdate(['id_order' => 7]); + $this->hookActionOrderStatusPostUpdate(['newOrderStatus' => $newOrderStatus]); + $this->hookActionOrderStatusPostUpdate([]); + } + /** @see https://devdocs.prestashop-project.org/8/modules/concepts/hooks/list-of-hooks/actionvalidateorder */ public function hookActionValidateOrder(array $params) { - if(isset($params['order']) && $params['order'] instanceof Order) - { - return $this->runHook('order_new', new Extensions\Hook\Variables(array( - 'order_id' => (int) $params['order']->id, - 'lang_id' => (int) $params['order']->id_lang, - 'store_id' => (int) $params['order']->id_shop, - 'customer_id' => (int) $params['order']->id_customer - ))); + if (!isset($params['order']) || !$params['order'] instanceof Order) { + return; } - return true; + + $this->runHook('order', 'new', new Plugin\Event\Variables([ + 'order_id' => (int) $params['order']->id, + 'lang_id' => (int) $params['order']->id_lang, + 'shop_id' => (int) $params['order']->id_shop, + 'customer_id' => (int) $params['order']->id_customer, + ]), ['order' => $params['order']]); } + private function testHookActionValidateOrder() + { + $order = new Order(8); // 7 - CZC | 8 - Alza + + // should invoke + $this->hookActionValidateOrder(['order' => $order]); + + // should not invoke + $this->hookActionValidateOrder(['order' => new stdClass()]); + $this->hookActionValidateOrder([]); + } + /** @see https://devdocs.prestashop-project.org/8/modules/concepts/hooks/list-of-hooks/actioncustomeraccountadd */ public function hookActionCustomerAccountAdd(array $params) { - if(isset($params['newCustomer']) && $params['newCustomer'] instanceof Customer) - { - return $this->runHook('customer_new', new Extensions\Hook\Variables(array( - 'customer_id' => (int) $params['newCustomer']->id, - 'lang_id' => (int) $params['newCustomer']->id_lang, - 'store_id' => (int) $params['newCustomer']->id_shop - ))); + if (!isset($params['newCustomer']) || !$params['newCustomer'] instanceof Customer) { + return; } - return true; + + $this->runHook('customer', 'new', new Plugin\Event\Variables([ + 'customer_id' => (int) $params['newCustomer']->id, + 'lang_id' => (int) $params['newCustomer']->id_lang, + 'shop_id' => (int) $params['newCustomer']->id_shop, + ]), ['customer' => $params['newCustomer']]); } + private function testHookActionCustomerAccountAdd() + { + $customer = new Customer(5); // 2 - CZC | 5 - Alza + + // should invoke + $this->hookActionCustomerAccountAdd(['newCustomer' => $customer]); + // should not invoke + $this->hookActionCustomerAccountAdd(['newCustomer' => new stdClass()]); + $this->hookActionCustomerAccountAdd([]); + } + + /** @see https://devdocs.prestashop-project.org/8/modules/concepts/hooks/list-of-hooks/actionorderreturn */ public function hookActionOrderReturn(array $params) { - if(isset($params['orderReturn']) && $params['orderReturn'] instanceof OrderReturn) - { - return $this->runHook('order_product_return', new Extensions\Hook\Variables(array( - 'return_id' => (int) $params['orderReturn']->id, - 'customer_id' => (int) $params['orderReturn']->id_customer, - 'order_id' => (int) $params['orderReturn']->id_order, - 'lang_id' => (int) $params['orderReturn']->id_lang, - 'store_id' => (int) $params['orderReturn']->id_shop - ))); + if (!isset($params['orderReturn']) || !$params['orderReturn'] instanceof OrderReturn) { + return; } - return true; + + $this->runHook('return', 'new', new Plugin\Event\Variables([ + 'return_id' => (int) $params['orderReturn']->id, + 'customer_id' => (int) $params['orderReturn']->id_customer, + 'order_id' => (int) $params['orderReturn']->id_order, + 'lang_id' => (int) $params['orderReturn']->getAssociatedLanguage()->id, + 'shop_id' => (int) $params['orderReturn']->getShopId(), + ])); } + public function testHookActionOrderReturn() + { + $order_return = new OrderReturn(1); + // should invoke + $this->hookActionOrderReturn(['orderReturn' => $order_return]); + + // should not invoke + $this->hookActionOrderReturn(['orderReturn' => new stdClass()]); + $this->hookActionOrderReturn([]); + } + + /** @see https://devdocs.prestashop-project.org/8/modules/concepts/hooks/list-of-hooks/actionorderslipadd */ public function hookActionOrderSlipAdd(array $params) { - if(isset($params['order']) && $params['order'] instanceof Order) - { - return $this->runHook('order_slip_add', new Extensions\Hook\Variables(array( - 'order_id' => (int) $params['order']->id, - 'customer_id' => (int) $params['order']->id_customer, - 'lang_id' => (int) $params['order']->id_lang, - 'store_id' => (int) $params['order']->id_shop, - 'filter_products' => array_keys(isset($params['qtyList']) ? $params['qtyList'] : array()) - ))); + if (!isset($params['order']) || !$params['order'] instanceof Order) { + return; } - return true; + + $this->runHook('order', 'TODO_slip_add', new Plugin\Event\Variables([ + 'order_id' => (int) $params['order']->id, + 'customer_id' => (int) $params['order']->id_customer, + 'lang_id' => (int) $params['order']->id_lang, + 'shop_id' => (int) $params['order']->id_shop, + 'filter_products' => array_keys(isset($params['qtyList']) ? $params['qtyList'] : []), + ]), ['order' => $params['order']]); } + public function testHookActionOrderSlipAdd() + { + $order = new Order(2); // 7 - CZC | 8 - Alza + + // should invoke + $this->hookActionOrderSlipAdd(['order' => $order, 'qtyList' => [3 => 1]]); + $this->hookActionOrderSlipAdd(['order' => $order, 'qtyList' => [3 => 1, 4 => 1]]); + // should not invoke + $this->hookActionOrderSlipAdd(['order' => new stdClass(), 'qtyList' => [3 => 1, 4 => 1]]); + $this->hookActionOrderSlipAdd(['order' => new stdClass()]); + $this->hookActionOrderSlipAdd([]); + } + + /** https://devdocs.prestashop-project.org/8/modules/concepts/hooks/list-of-hooks/actionadminorderstrackingnumberupdate */ public function hookActionAdminOrdersTrackingNumberUpdate(array $params) { - if(isset($params['order']) && $params['order'] instanceof Order) - { - return $this->runHook('order_tracking_number', new Extensions\Hook\Variables(array( - 'order_id' => (int) $params['order']->id, - 'customer_id' => (int) $params['order']->id_customer, - 'lang_id' => (int) $params['order']->id_lang, - 'store_id' => (int) $params['order']->id_shop - ))); + if (!isset($params['order']) || !$params['order'] instanceof Order) { + return; } - return true; + + $this->runHook('order', 'tracking-number', new Plugin\Event\Variables([ + 'order_id' => (int) $params['order']->id, + 'customer_id' => (int) $params['order']->id_customer, + 'lang_id' => (int) $params['order']->id_lang, + 'shop_id' => (int) $params['order']->id_shop, + ]), ['order' => $params['order']]); } + public function testHookActionAdminOrdersTrackingNumberPostUpdate() + { + $order = new Order(2); // 7 - CZC | 8 - Alza + + // should invoke + $this->hookActionAdminOrdersTrackingNumberUpdate(['order' => $order]); + + // should not invoke + $this->hookActionAdminOrdersTrackingNumberUpdate(['order' => new stdClass()]); + $this->hookActionAdminOrdersTrackingNumberUpdate([]); + } + /** https://devdocs.prestashop-project.org/8/modules/concepts/hooks/list-of-hooks/actionpaymentconfirmation */ public function hookActionPaymentConfirmation(array $params) { - if(isset($params['id_order'])) - { - $order = new Order($params['id_order']); - - if($order->id !== null) - { - return $this->runHook('order_payment_confirmation', new Extensions\Hook\Variables(array( - 'order_id' => (int) $order->id, - 'lang_id' => (int) $order->id_lang, - 'store_id' => (int) $order->id_shop, - 'customer_id' => (int) $order->id_customer - ))); - } + if (!isset($params['id_order'])) { + return; } - return true; + + $order = new Order($params['id_order']); + + $this->runHook('order', 'payment', new Plugin\Event\Variables([ + 'order_id' => (int) $order->id, + 'lang_id' => (int) $order->id_lang, + 'shop_id' => (int) $order->id_shop, + 'customer_id' => (int) $order->id_customer, + ]), ['order' => $order]); } + public function testHookActionPaymentConfirmation() + { + // should invoke + $this->hookActionPaymentConfirmation(['id_order' => 2]); + + // should not invoke + $this->hookActionPaymentConfirmation([]); + } + /** @see https://devdocs.prestashop-project.org/8/modules/concepts/hooks/list-of-hooks/actionproductdelete */ public function hookActionProductDelete(array $params) { - if(isset($params['product']) && $params['product'] instanceof Product) - { - return $this->runHook('product_delete', new Extensions\Hook\Variables(array( - 'store_id' => (int) $params['product']->id_shop_default, - 'product_id' => (int) $params['product']->id, - ))); + if (!isset($params['product']) || !$params['product'] instanceof Product) { + return; } - return true; + + $this->runHook('product', 'TODO_delete', new Plugin\Event\Variables([ + 'shop_id' => (int) $params['product']->id_shop_default, + 'product_id' => (int) $params['product']->id, + ]), ['product' => $params['product']]); } + public function testHookActionProductDelete() + { + $product = new Product(4); + + // should invoke + $this->hookActionProductDelete(['product' => $product]); + + // should not invoke + $this->hookActionProductDelete(['product' => new stdClass()]); + $this->hookActionProductDelete([]); + } + /** @see https://devdocs.prestashop-project.org/8/modules/concepts/hooks/list-of-hooks/actionupdatequantity */ public function hookActionUpdateQuantity(array $params) { - if(isset($params['id_product'])) - { - $product = new Product((int) $params['id_product']); + if (!isset($params['id_product'])) { + return; + } - return $this->runHook('product_update_quantity', new Extensions\Hook\Variables(array( - 'store_id' => (int) $product->id_shop_default, - 'product_id' => (int) $product->id, - 'id_product_attribute' => isset($params['id_product_attribute']) ? (int) $params['id_product_attribute'] : null, - ))); + if ($params['quantity'] === 0) { + $this->runHook('product', 'out-of-stock', new Plugin\Event\Variables([ + 'shop_id' => $params['id_shop'], + 'product_id' => $params['id_product'], + 'id_product_attribute' => $params['id_product_attribute'], + ])); } - return true; } + private function testHookActionUpdateQuantity() + { + // should invoke + $this->hookActionUpdateQuantity(['id_shop' => 2, 'id_product' => 19, 'quantity' => 0, 'id_product_attribute' => 0]); + + // should not invoke + $this->hookActionUpdateQuantity(['id_shop' => 2, 'quantity' => 5, 'id_product_attribute' => 0]); + $this->hookActionUpdateQuantity(['id_shop' => 2, 'id_product' => 19, 'quantity' => 5, 'id_product_attribute' => 0]); + } - public function hookActionProductOutOfStock(array $params) + /*public function hookActionProductOutOfStock(array $params) { if(isset($params['product']) && $params['product'] instanceof Product) { if((int) $params['product']->quantity <= (int) $params['product']->minimal_quantity) { - if(Extensions\Helpers::outOfStockCheck($this->ps_settings, (int) $params['product']->id)) + if(Extensions\Helpers::outOfStockCheck($this->settings, (int) $params['product']->id)) { - $this->runHook('product_out_of_stock', new Extensions\Hook\Variables(array( - 'store_id' => (int) $params['product']->id_shop_default, + $this->runHook('product', 'out-of-stock', new Variables([ + 'shop_id' => (int) $params['product']->id_shop_default, 'product_id' => (int) $params['product']->id, - ))); + ]), ['product' => $params['product']]); } } } - } - + }*/ + /** https://devdocs.prestashop-project.org/8/modules/concepts/hooks/list-of-hooks/actionproductcancel */ public function hookActionProductCancel(array $params) { - if(isset($params['order']) && $params['order'] instanceof Order) - { - return $this->runHook('order_product_cancel', new Extensions\Hook\Variables(array( - 'order_id' => (int) $params['order']->id, - 'id_order_detail' => isset($params['id_order_detail']) ? $params['id_order_detail'] : null, - 'customer_id' => (int) $params['order']->id_customer, - 'lang_id' => (int) $params['order']->id_lang, - 'store_id' => (int) $params['order']->id_shop - ))); + if (!isset($params['order']) || !$params['order'] instanceof Order) { + return; } - return true; + + // todo: tento hook se spousti ze 4 ruznych mist, viz CancellationActionType. i v pripade hookActionOrderSlipAdd (kdyz castecne vratim produkt) + $this->runHook('order', 'TODO_product_cancel', new Plugin\Event\Variables([ + 'order_id' => (int) $params['order']->id, + 'filter_products' => [$params['id_order_detail']], + 'customer_id' => (int) $params['order']->id_customer, + 'lang_id' => (int) $params['order']->id_lang, + 'shop_id' => (int) $params['order']->id_shop, + ]), ['order' => $params['order']]); } + public function testHookActionProductCancel() + { + $order = new Order(2); + + // should invoke + $this->hookActionProductCancel(['order' => $order, 'id_order_detail' => 3]); + // should not invoke + $this->hookActionProductCancel(['order' => new stdClass(), 'id_order_detail' => 3]); + $this->hookActionProductCancel(['id_order_detail' => 3]); + } + + /** https://devdocs.prestashop-project.org/8/modules/concepts/hooks/list-of-hooks/actionemailsendbefore */ public function hookActionEmailSendBefore(array $params) { - if(isset($params['templateVars']) && isset($params['template']) && $params['template'] === 'contact') - { - $customer_message = isset($params['templateVars']['{message}']) ? $params['templateVars']['{message}'] : null; + if (!isset($params['templateVars']) || !isset($params['template']) || $params['template'] !== 'contact') { + return; + } - if($customer_message !== null) - { - return $this->runHook('contact_form', new Extensions\Hook\Variables(array( - 'customer_email' => isset($params['templateVars']['{email}']) ? $params['templateVars']['{email}'] : null, - 'customer_message' => $customer_message, - 'customer_message_short_50' => substr($customer_message, 0, 50), - 'customer_message_short_80' => substr($customer_message, 0, 80), - 'customer_message_short_100' => substr($customer_message, 0, 100), - 'customer_message_short_120' => substr($customer_message, 0, 120), - 'lang_id' => isset($params['idLang']) ? (int) $params['idLang'] : null, - 'store_id' => isset($params['idShop']) ? (int) $params['idShop'] : null - ))); - } + $customer_message = isset($params['templateVars']['{message}']) ? $params['templateVars']['{message}'] : null; + + if ($customer_message !== null) { + $this->runHook('contact', 'form', new Plugin\Event\Variables([ + 'customer_email' => isset($params['templateVars']['{email}']) ? $params['templateVars']['{email}'] : null, + 'customer_message' => $customer_message, + 'customer_message_short_50' => substr($customer_message, 0, 50), + 'customer_message_short_80' => substr($customer_message, 0, 80), + 'customer_message_short_100' => substr($customer_message, 0, 100), + 'customer_message_short_120' => substr($customer_message, 0, 120), + 'lang_id' => isset($params['idLang']) ? (int) $params['idLang'] : null, + 'shop_id' => isset($params['idShop']) ? (int) $params['idShop'] : null, + ])); } - return true; } + public function testHookActionEmailSendBefore() + { + // should invoke + $this->hookActionEmailSendBefore(['templateVars' => ['{message}' => 'Hello world', '{email}' => 'john.doe@example.com'], 'template' => 'contact']); + $this->hookActionEmailSendBefore(['templateVars' => ['{message}' => 'Hello world'], 'template' => 'contact']); + + // should not invoke + $this->hookActionEmailSendBefore(['templateVars' => [], 'template' => 'contact']); + } public function hookActionPrestaSmsSendSms(array $params) { - $this->ps_di->getConnection()->run( - new BulkGate\Extensions\IO\Request( - $this->ps_di->getModule()->getUrl('/module/hook/custom'), - array( - 'number' => isset($params['number']) ? $params['number'] : null, - 'template' => isset($params['template']) ? $params['template'] : null, - 'variables' => isset($params['variables']) && is_array($params['variables']) ? $params['variables'] : array(), - 'settings' => isset($params['settings']) && is_array($params['settings']) ? $params['settings'] : array() - ), - true) - ); + $number = $params['number'] ?? null; + $template = $params['template'] ?? null; + $variables = $params['variables'] ?? []; + $settings = $params['settings'] ?? []; + + $hook = $this->getBulkGateContainer()->getByClass(Plugin\Event\Hook::class); + + $hook->send('/api/2.0/advanced/transactional', [ + 'number' => $number, + 'application_product' => 'ps', + 'tag' => 'module_custom', + 'variables' => $variables, + 'country' => $settings['country'] ?? null, + 'channel' => [ + 'sms' => [ + 'sender_id' => $settings['senderType'] ?? 'gSystem', + 'sender_id_value' => $settings['senderValue'] ?? '', + 'unicode' => $settings['unicode'] ?? false, + 'text' => $template, + ], + ], + ]); } - - public function hookDisplayAdminOrderRight(array $params) + public function hookActionPrestaSmsExtendsVariables(array $params) { - if($this->ps_settings->load("static:application_token", false)) - { - if(isset($params['id_order'])) - { - list($phone_number, $iso) = PrestaSms\Helpers::getOrderPhoneNumber($params['id_order']); - } - else - { - $phone_number = $iso = null; - } + } - $controller = 'AdminPrestaSmsDashboardDefault'; + /* BackOffice hooks */ - try - { - $this->context->smarty->registerPlugin('modifier', 'prestaSmsEscapeHtml', array('BulkGate\Extensions\Escape', 'html')); - $this->context->smarty->registerPlugin('modifier', 'prestaSmsEscapeJs', array('BulkGate\Extensions\Escape', 'js')); - $this->context->smarty->registerPlugin('modifier', 'prestaSmsEscapeUrl', array('BulkGate\Extensions\Escape', 'url')); - $this->context->smarty->registerPlugin('modifier', 'prestaSmsEscapeHtmlAttr', array('BulkGate\Extensions\Escape', 'htmlAttr')); - $this->context->smarty->registerPlugin('modifier', 'prestaSmsTranslate', array($this->ps_di->getTranslator(), 'translate')); - } - catch (SmartyException $e) - { - } + /** @see https://devdocs.prestashop-project.org/8/modules/concepts/hooks/list-of-hooks/displayadminorderside */ + public function hookDisplayAdminOrderSide(array $params) + { + ['id_order' => $id] = $params; + + $settings = $this->getBulkGateContainer()->getByClass(Plugin\Settings\Settings::class); - return $this->context->smarty->createTemplate(_BG_PRESTASMS_DIR_.'/templates/panel.tpl', null, null, array( - 'application_id' => $this->ps_settings->load('static:application_id', ''), - 'language' => $this->ps_settings->load('main:language', 'en'), - 'id' => $phone_number, - 'key' => $iso, - 'presenter' => 'ModuleComponents', - 'action' => 'sendSms', - 'mode' => defined('BULKGATE_DEV_MODE') ? 'dev' : 'dist', - 'widget_api_url' => $this->ps_di->getModule()->getUrl('/'.(defined('BULKGATE_DEV_MODE') ? 'dev' : 'dist').'/widget-api/widget-api.js'), - 'logo' => $this->ps_di->getModule()->getUrl('/images/products/ps.svg'), - 'proxy' => array(), - 'salt' => Extensions\Compress::compress(PrestaSms\Helpers::generateTokens()), - 'authenticate' => array( - 'ajax' => true, - 'controller' => $controller, - 'action' => 'authenticate', - 'token' => \Tools::getAdminTokenLite($controller), - ), - 'homepage' => $this->context->link->getAdminLink('AdminPrestaSmsDashboardDefault'), - 'info' => $this->ps_di->getModule()->info() - ))->fetch(); + if (!$settings->load('static:application_token')) { // todo: isModuleLoggedIn + return null; } - return ''; + + $sign = $this->getBulkGateContainer()->getByClass(Plugin\User\Sign::class); + $url = $this->getBulkGateContainer()->getByClass(Plugin\IO\Url::class); + $loader = $this->getBulkGateContainer()->getByClass(Plugin\Event\Loader::class); + + $order = new Order($id); + $variables = new Plugin\Event\Variables([ + 'order_id' => $order->id, + 'customer_id' => $order->id_customer, + 'lang_id' => $order->id_lang, + 'order_status_id' => $order->current_state, + ]); + $loader->load($variables); + $token = $sign->authenticate(); + + return $this->render($this->getModuleTemplatePath() . 'send-message.html.twig', [ + 'token' => $token, + 'url' => $url, + 'variables' => [ + ...$variables->toArray(), + // these variables are for web component + 'first_name' => Helpers::priorityValues(['customer_firstname', 'customer_invoice_firstname'], $variables), + 'last_name' => Helpers::priorityValues(['customer_lastname', 'customer_invoice_lastname'], $variables), + 'phone_mobile' => Helpers::priorityValues(['customer_mobile', 'customer_phone', 'customer_invoice_mobile', 'customer_invoice_phone'], $variables), + 'phone_number_iso' => Helpers::priorityValues(['customer_country_id', 'customer_invoice_country_id'], $variables), + ], + ]); } + /** @see https://devdocs.prestashop-project.org/8/modules/concepts/hooks/list-of-hooks */ + public function hookActionListModules() + { + $settings = $this->getBulkGateContainer()->getByClass(Plugin\Settings\Settings::class); + + if ($settings->load('static:application_token') === null) { + $this->warning = 'You must be logged in to BulkGate to start sending SMS!'; + } + } - public function hookActionPrestaSmsExtendsVariables(array $params) + /** https://devdocs.prestashop-project.org/8/modules/concepts/hooks/list-of-hooks/displaybackofficeheader */ + public function hookDisplayBackOfficeHeader() { + // $this->test(); + return $this->asynchronousAsset(); } + /* FrontOffice hooks */ - public function runHook($name, Extensions\Hook\Variables $variables) + /** @see https://devdocs.prestashop-project.org/8/modules/concepts/hooks/list-of-hooks/displayheader */ + public function hookDisplayHeader() { - $hook = new Extensions\Hook\Hook( - $this->ps_di->getModule()->getUrl('/module/hook'), - $variables->get('lang_id', (int) $this->context->language->id), - $variables->get('store_id', (int) $this->context->shop->id), - $this->ps_di->getConnection(), - $this->ps_settings, - new PrestaSms\HookLoad($this->ps_di->getDatabase(), $this->context) - ); + return $this->asynchronousAsset(); + } - try - { - $hook->run((string) $name, $variables); - return true; + /** @see https://devdocs.prestashop-project.org/8/modules/concepts/hooks/list-of-hooks/additionalcustomerformfields */ + public function hookAdditionalCustomerFormFields(array $params) + { + $settings = $this->getBulkGateContainer()->getByClass(Plugin\Settings\Settings::class); + + if (!$settings->load('main:marketing_message_opt_in_enabled')) { + return null; } - catch (Extensions\IO\ConnectionException $e) - { - return false; + + $url = $settings->load('main:marketing_message_opt_in_url'); + $label_suffix = $url && !preg_match('~^https?://$~', $url) ? '[1][2]%url%[/2]' : ''; + + $label = $this->trans( + 'I consent to receiving marketing communications via SMS, Viber, RCS, WhatsApp, and other similar channels.' . $label_suffix, + [ + '_raw' => true, + '[1]' => '
', + '[2]' => '', + '%url%' => Tools::htmlentitiesUTF8($url), + '[/2]' => '', + ] + ); + + return [ + (new FormField()) + ->setName('bulkgate_marketing_message_opt_in') + ->setType('checkbox') + ->setValue($settings->load('main:marketing_message_opt_in_default')) + ->setLabel($settings->load('main:marketing_message_opt_in_label') ?: $label), + ]; + } + + private function test() + { + $this->testHookActionOrderStatusPostUpdate(); + $this->testHookActionValidateOrder(); + $this->testHookActionCustomerAccountAdd(); + $this->testHookActionOrderReturn(); + $this->testHookActionOrderSlipAdd(); + $this->testHookActionAdminOrdersTrackingNumberPostUpdate(); + $this->testHookActionPaymentConfirmation(); + $this->testHookActionProductDelete(); + $this->testHookActionUpdateQuantity(); + $this->testHookActionProductCancel(); + $this->testHookActionEmailSendBefore(); + } + + /** + * Render a twig template. + */ + private function render(string $template, array $params = []): string + { + /** @var Twig_Environment $twig */ + $twig = $this->get('twig'); + + return $twig->render($template, $params); + } + + /** + * Get path to this module's template directory + */ + private function getModuleTemplatePath(): string + { + return sprintf('@Modules/%s/views/templates/admin/', $this->name); + } + + private function runHook(string $category, string $endpoint, Plugin\Event\Variables $variables, array $parameters = [], ?callable $success_callback = null): void + { + $dispatcher = $this->getBulkGateContainer()->getByClass(Plugin\Event\Dispatcher::class); + + $dispatcher->dispatch($category, $endpoint, $variables, $parameters, $success_callback); + } + + private function asynchronousAsset() + { + $settings = $this->getBulkGateContainer()->getByClass(Plugin\Settings\Settings::class); + + if (in_array($settings->load('main:dispatcher'), [Plugin\Event\Dispatcher::Asset, Plugin\Event\Dispatcher::Cron])) { + return ''; } } } diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..7026179 --- /dev/null +++ b/composer.json @@ -0,0 +1,39 @@ +{ + "name": "bulkgate/prestasms", + "type": "project", + "minimum-stability": "RC", + "require": { + "php": ">= 7.4", + "bulkgate/plugin": "^1.0" + }, + "require-dev": { + "tracy/tracy": "^2.9", + "nette/tester": "^2.4.3", + "phpstan/phpstan": "^2", + "phpstan/extension-installer": "^1.3", + "mockery/mockery": "^1.6", + "friendsofphp/php-cs-fixer": "^3.63", + "prestashop/php-dev-tools": "^5.0" + }, + "autoload": { + "classmap": [ + "src/" + ], + "files": [ + "distribution.php" + ] + }, + "scripts": { + "tester": "tester -C tests --colors=1", + "coverage": "tester -C tests --coverage=coverage.html --coverage-src=src --stop-on-fail -o console-lines", + "phpstan": "phpstan analyse -c phpstan.neon --memory-limit=1G", + "phpstan-baseline": "phpstan analyse -c phpstan.neon --memory-limit=1G --generate-baseline", + "prestashop-lint": "php-cs-fixer fix" + }, + "config": { + "prepend-autoloader": false, + "allow-plugins": { + "phpstan/extension-installer": true + } + } +} diff --git a/config/admin/services.yml b/config/admin/services.yml new file mode 100644 index 0000000..6e16e43 --- /dev/null +++ b/config/admin/services.yml @@ -0,0 +1,2 @@ +imports: + - { resource: ../common.yml } diff --git a/config/common.yml b/config/common.yml new file mode 100644 index 0000000..dd7287c --- /dev/null +++ b/config/common.yml @@ -0,0 +1,28 @@ +services: + _defaults: + autowire: true + autoconfigure: true + public: true + + # Adapter classes services + + PrestaShop\PrestaShop\Adapter\OrderState\OrderStateDataProvider: + class: PrestaShop\PrestaShop\Adapter\OrderState\OrderStateDataProvider + PrestaShop\PrestaShop\Adapter\OrderReturnState\OrderReturnStateDataProvider: + class: PrestaShop\PrestaShop\Adapter\OrderReturnState\OrderReturnStateDataProvider + PrestaShop\PrestaShop\Adapter\Shop\Context: + class: PrestaShop\PrestaShop\Adapter\Shop\Context + PrestaShop\PrestaShop\Adapter\Shop\Url\BaseUrlProvider: + class: PrestaShop\PrestaShop\Adapter\Shop\Url\BaseUrlProvider + arguments: + - "@=service('prestashop.adapter.legacy.context').getContext().link" + PrestaShop\PrestaShop\Adapter\Employee\ContextEmployeeProvider: + class: PrestaShop\PrestaShop\Adapter\Employee\ContextEmployeeProvider + arguments: + - '@=service("prestashop.adapter.legacy.context").getContext().employee' + + # Symfony Controller - https://devdocs.prestashop-project.org/1.7/modules/concepts/controllers/admin-controllers/#how-to-declare-a-new-controller + + BulkGate\PrestaShop\Controller\AdminController: + class: BulkGate\PrestaShop\Controller\AdminController + tags: [ controller.service_arguments ] diff --git a/config/front/services.yml b/config/front/services.yml new file mode 100644 index 0000000..6e16e43 --- /dev/null +++ b/config/front/services.yml @@ -0,0 +1,2 @@ +imports: + - { resource: ../common.yml } diff --git a/config/routes.yml b/config/routes.yml new file mode 100644 index 0000000..9cee366 --- /dev/null +++ b/config/routes.yml @@ -0,0 +1,22 @@ +# @see [_lagacy_link](https://devdocs.prestashop-project.org/1.7/modules/concepts/controllers/admin-controllers/tabs/) +bulkgate_main_app: + path: prestasms + methods: [GET] + defaults: + _controller: 'BulkGate\PrestaShop\Controller\AdminController::indexAction' + _legacy_controller: AdminBulkGateConfigure + _legacy_link: AdminBulkGateConfigure + +bulkgate_proxy: + path: prestasms/proxy-{action} + methods: [POST] + defaults: + _controller: 'BulkGate\PrestaShop\Controller\AdminController::proxyAction' + +bulkgate_debug: + path: prestasms/debug + methods: [GET] + defaults: + _controller: 'BulkGate\PrestaShop\Controller\AdminController::debugAction' + _legacy_controller: AdminBulkGateDebug + _legacy_link: AdminBulkGateDebug \ No newline at end of file diff --git a/controllers/admin/.htaccess b/controllers/admin/.htaccess deleted file mode 100644 index 22b9ed2..0000000 --- a/controllers/admin/.htaccess +++ /dev/null @@ -1,2 +0,0 @@ -Order Allow,Deny -Deny from all \ No newline at end of file diff --git a/controllers/admin/AdminPrestaSmsAboutDefaultController.php b/controllers/admin/AdminPrestaSmsAboutDefaultController.php deleted file mode 100644 index ffe46f6..0000000 --- a/controllers/admin/AdminPrestaSmsAboutDefaultController.php +++ /dev/null @@ -1,23 +0,0 @@ -meta_title = $this->_('about_module', 'About module'); - } - - public function renderView() - { - return $this->prestaSmsView("ModuleAbout", "default"); - } -} diff --git a/controllers/admin/AdminPrestaSmsBlackListDefaultController.php b/controllers/admin/AdminPrestaSmsBlackListDefaultController.php deleted file mode 100644 index 3f05959..0000000 --- a/controllers/admin/AdminPrestaSmsBlackListDefaultController.php +++ /dev/null @@ -1,23 +0,0 @@ -meta_title = $this->_('black_list', 'Black list'); - } - - public function renderView() - { - return $this->prestaSmsView("BlackList", "default", true); - } -} diff --git a/controllers/admin/AdminPrestaSmsBlackListImportController.php b/controllers/admin/AdminPrestaSmsBlackListImportController.php deleted file mode 100644 index 5529315..0000000 --- a/controllers/admin/AdminPrestaSmsBlackListImportController.php +++ /dev/null @@ -1,23 +0,0 @@ -meta_title = $this->_('black_list', 'Black list'); - } - - public function renderView() - { - return $this->prestaSmsView("BlackList", "import", true); - } -} diff --git a/controllers/admin/AdminPrestaSmsDashboardDefaultController.php b/controllers/admin/AdminPrestaSmsDashboardDefaultController.php deleted file mode 100644 index a10ebee..0000000 --- a/controllers/admin/AdminPrestaSmsDashboardDefaultController.php +++ /dev/null @@ -1,23 +0,0 @@ -meta_title = $this->_('dashboard', 'Dashboard'); - } - - public function renderView() - { - return $this->prestaSmsView("Dashboard", "default"); - } -} diff --git a/controllers/admin/AdminPrestaSmsHistoryListController.php b/controllers/admin/AdminPrestaSmsHistoryListController.php deleted file mode 100644 index 8f30f22..0000000 --- a/controllers/admin/AdminPrestaSmsHistoryListController.php +++ /dev/null @@ -1,23 +0,0 @@ -meta_title = $this->_('history', 'History'); - } - - public function renderView() - { - return $this->prestaSmsView("History", "list", true); - } -} diff --git a/controllers/admin/AdminPrestaSmsInboxListController.php b/controllers/admin/AdminPrestaSmsInboxListController.php deleted file mode 100644 index 26f0a16..0000000 --- a/controllers/admin/AdminPrestaSmsInboxListController.php +++ /dev/null @@ -1,23 +0,0 @@ -meta_title = $this->_('inbox', 'Inbox'); - } - - public function renderView() - { - return $this->prestaSmsView("Inbox", "list", true); - } -} diff --git a/controllers/admin/AdminPrestaSmsModuleNotificationsAdminController.php b/controllers/admin/AdminPrestaSmsModuleNotificationsAdminController.php deleted file mode 100644 index d1a0b51..0000000 --- a/controllers/admin/AdminPrestaSmsModuleNotificationsAdminController.php +++ /dev/null @@ -1,31 +0,0 @@ -meta_title = $this->_('admin_sms', 'Admin SMS'); - } - - public function renderView() - { - $this->ps_proxy->add('save', 'save'); - return $this->prestaSmsView("ModuleNotifications", "admin", true); - } - - public function ajaxProcessSave() - { - Extensions\JsonResponse::send( - $this->ps_di->getProxy()->saveAdminNotifications(Tools::getValue('__bulkgate')) - ); - } -} diff --git a/controllers/admin/AdminPrestaSmsModuleNotificationsCustomerController.php b/controllers/admin/AdminPrestaSmsModuleNotificationsCustomerController.php deleted file mode 100644 index 25ed853..0000000 --- a/controllers/admin/AdminPrestaSmsModuleNotificationsCustomerController.php +++ /dev/null @@ -1,31 +0,0 @@ -meta_title = $this->_('customer_sms', 'Customer SMS'); - } - - public function renderView() - { - $this->ps_proxy->add('save', 'save'); - return $this->prestaSmsView("ModuleNotifications", "customer", true); - } - - public function ajaxProcessSave() - { - Extensions\JsonResponse::send( - $this->ps_di->getProxy()->saveCustomerNotifications(Tools::getValue('__bulkgate')) - ); - } -} diff --git a/controllers/admin/AdminPrestaSmsModuleSettingsDefaultController.php b/controllers/admin/AdminPrestaSmsModuleSettingsDefaultController.php deleted file mode 100644 index 966ac99..0000000 --- a/controllers/admin/AdminPrestaSmsModuleSettingsDefaultController.php +++ /dev/null @@ -1,55 +0,0 @@ -meta_title = $this->_('settings', 'Settings'); - } - - public function renderView() - { - if(_BG_PRESTASMS_DEMO_) - { - \Tools::redirectAdmin($this->context->link->getAdminLink('AdminPrestaSmsDashboardDefault')); - } - $this->ps_proxy->add('save', 'save'); - $this->ps_proxy->add('logout', 'logout'); - return $this->prestaSmsView("ModuleSettings", "default"); - } - - public function ajaxProcessSave() - { - $post = Tools::getValue('__bulkgate', false); - - if($post) - { - $this->ps_di->getProxy()->saveSettings($post); - - if(isset($post['language'])) - { - $this->ps_translator->init($post['language']); - PrestaSms\Helpers::uninstallMenu(); - PrestaSms\Helpers::installMenu($this->ps_translator); - } - } - Extensions\JsonResponse::send(array('redirect' => $this->context->link->getAdminLink($this->controller_name))); - } - - - public function ajaxProcessLogout() - { - $this->ps_di->getProxy()->logout(); - - Extensions\JsonResponse::send(array('token' => 'guest', 'redirect' => $this->context->link->getAdminLink('AdminPrestaSmsSignIn'))); - } -} diff --git a/controllers/admin/AdminPrestaSmsPaymentListController.php b/controllers/admin/AdminPrestaSmsPaymentListController.php deleted file mode 100644 index d6404d6..0000000 --- a/controllers/admin/AdminPrestaSmsPaymentListController.php +++ /dev/null @@ -1,23 +0,0 @@ -meta_title = $this->_('invoice_list', 'Invoice list'); - } - - public function renderView() - { - return $this->prestaSmsView("Payment", "list", true); - } -} diff --git a/controllers/admin/AdminPrestaSmsSignInController.php b/controllers/admin/AdminPrestaSmsSignInController.php deleted file mode 100644 index 8a69705..0000000 --- a/controllers/admin/AdminPrestaSmsSignInController.php +++ /dev/null @@ -1,35 +0,0 @@ -meta_title = $this->_('sign_in', 'Sign in'); - } - - public function renderView() - { - $this->ps_proxy->add('login', 'login'); - return $this->prestaSmsView("ModuleSign", "in"); - } - - public function ajaxProcessLogin() - { - $response = $this->ps_di->getProxy()->login(array_merge(array("name" => Configuration::get('PS_SHOP_NAME')), Tools::getValue('__bulkgate'))); - - if($response instanceof Extensions\IO\Response) - { - Extensions\JsonResponse::send($response); - } - Extensions\JsonResponse::send(array('token' => $response, 'redirect' => $this->context->link->getAdminLink('AdminPrestaSmsDashboardDefault'))); - } -} diff --git a/controllers/admin/AdminPrestaSmsSignUpController.php b/controllers/admin/AdminPrestaSmsSignUpController.php deleted file mode 100644 index 62df9d0..0000000 --- a/controllers/admin/AdminPrestaSmsSignUpController.php +++ /dev/null @@ -1,35 +0,0 @@ -meta_title = $this->_('sign_up', 'Sign up'); - } - - public function renderView() - { - $this->ps_proxy->add('register', 'register'); - return $this->prestaSmsView("Sign", "up"); - } - - public function ajaxProcessRegister() - { - $response = $this->ps_di->getProxy()->register(array_merge(array("name" => Configuration::get('PS_SHOP_NAME')), Tools::getValue('__bulkgate'))); - - if($response instanceof Extensions\IO\Response) - { - Extensions\JsonResponse::send($response); - } - Extensions\JsonResponse::send(array('token' => $response, 'redirect' => $this->context->link->getAdminLink('AdminPrestaSmsDashboardDefault'))); - } -} diff --git a/controllers/admin/AdminPrestaSmsSmsCampaignActiveController.php b/controllers/admin/AdminPrestaSmsSmsCampaignActiveController.php deleted file mode 100644 index c6209d6..0000000 --- a/controllers/admin/AdminPrestaSmsSmsCampaignActiveController.php +++ /dev/null @@ -1,23 +0,0 @@ -meta_title = $this->_('sms_campaign', 'Sms Campaign'); - } - - public function renderView() - { - return $this->prestaSmsView("SmsCampaign", "active", false); - } -} diff --git a/controllers/admin/AdminPrestaSmsSmsCampaignCampaignController.php b/controllers/admin/AdminPrestaSmsSmsCampaignCampaignController.php deleted file mode 100644 index b0e4be8..0000000 --- a/controllers/admin/AdminPrestaSmsSmsCampaignCampaignController.php +++ /dev/null @@ -1,71 +0,0 @@ -meta_title = $this->_('sms_campaign', 'Sms Campaign'); - } - - public function renderView() - { - $this->ps_proxy->add('loadModuleData', 'loadModuleData', 'campaign'); - $this->ps_proxy->add('saveModuleCustomers', 'saveModuleCustomers', 'campaign'); - $this->ps_proxy->add('addModuleFilter', 'addModuleFilter', 'campaign'); - $this->ps_proxy->add('removeModuleFilter', 'removeModuleFilter', 'campaign'); - return $this->prestaSmsView("SmsCampaign", "campaign", true); - } - - public function ajaxProcessLoadModuleData() - { - $post = \Tools::getValue('__bulkgate'); - - Extensions\JsonResponse::send($this->ps_di->getProxy()->loadCustomersCount( - isset($post['application_id']) ? $post['application_id'] : null, - isset($post['campaign_id']) ? $post['campaign_id'] : null - )); - } - - public function ajaxProcessSaveModuleCustomers() - { - $post = \Tools::getValue('__bulkgate'); - - Extensions\JsonResponse::send($this->ps_di->getProxy()->saveModuleCustomers( - isset($post['application_id']) ? $post['application_id'] : null, - isset($post['campaign_id']) ? $post['campaign_id'] : null - )); - } - - public function ajaxProcessAddModuleFilter() - { - $post = \Tools::getValue('__bulkgate'); - - Extensions\JsonResponse::send($this->ps_di->getProxy()->loadCustomersCount( - isset($post['application_id']) ? $post['application_id'] : null, - isset($post['campaign_id']) ? $post['campaign_id'] : null, - 'addFilter', - $post - )); - } - - public function ajaxProcessRemoveModuleFilter() - { - $post = \Tools::getValue('__bulkgate'); - - Extensions\JsonResponse::send($this->ps_di->getProxy()->loadCustomersCount( - isset($post['application_id']) ? $post['application_id'] : null, - isset($post['campaign_id']) ? $post['campaign_id'] : null, - 'removeFilter', - $post - )); - } -} diff --git a/controllers/admin/AdminPrestaSmsSmsCampaignDefaultController.php b/controllers/admin/AdminPrestaSmsSmsCampaignDefaultController.php deleted file mode 100644 index f631a72..0000000 --- a/controllers/admin/AdminPrestaSmsSmsCampaignDefaultController.php +++ /dev/null @@ -1,23 +0,0 @@ -meta_title = $this->_('sms_campaigns', 'Sms Campaigns'); - } - - public function renderView() - { - return $this->prestaSmsView("SmsCampaign", "default", true); - } -} diff --git a/controllers/admin/AdminPrestaSmsSmsCampaignNewController.php b/controllers/admin/AdminPrestaSmsSmsCampaignNewController.php deleted file mode 100644 index 5449bf9..0000000 --- a/controllers/admin/AdminPrestaSmsSmsCampaignNewController.php +++ /dev/null @@ -1,23 +0,0 @@ -meta_title = $this->_('start_campaign', 'Start Campaign'); - } - - public function renderView() - { - return $this->prestaSmsView("SmsCampaign", "new", true); - } -} diff --git a/controllers/admin/AdminPrestaSmsSmsPriceListController.php b/controllers/admin/AdminPrestaSmsSmsPriceListController.php deleted file mode 100644 index febeb1e..0000000 --- a/controllers/admin/AdminPrestaSmsSmsPriceListController.php +++ /dev/null @@ -1,23 +0,0 @@ -meta_title = $this->_('price_list', 'Price list'); - } - - public function renderView() - { - return $this->prestaSmsView("SmsPrice", "list"); - } -} diff --git a/controllers/admin/AdminPrestaSmsSmsSettingsDefaultController.php b/controllers/admin/AdminPrestaSmsSmsSettingsDefaultController.php deleted file mode 100644 index a1077cf..0000000 --- a/controllers/admin/AdminPrestaSmsSmsSettingsDefaultController.php +++ /dev/null @@ -1,23 +0,0 @@ -meta_title = $this->_('sender_id_settings', 'Sender ID Settings'); - } - - public function renderView() - { - return $this->prestaSmsView("SmsSettings", "default", true); - } -} diff --git a/controllers/admin/AdminPrestaSmsStatisticsDefaultController.php b/controllers/admin/AdminPrestaSmsStatisticsDefaultController.php deleted file mode 100644 index 600bc8c..0000000 --- a/controllers/admin/AdminPrestaSmsStatisticsDefaultController.php +++ /dev/null @@ -1,23 +0,0 @@ -meta_title = $this->_('statistics', 'Statistics'); - } - - public function renderView() - { - return $this->prestaSmsView("Statistics", "default", true); - } -} diff --git a/controllers/admin/AdminPrestaSmsTopUpController.php b/controllers/admin/AdminPrestaSmsTopUpController.php deleted file mode 100644 index de21967..0000000 --- a/controllers/admin/AdminPrestaSmsTopUpController.php +++ /dev/null @@ -1,27 +0,0 @@ -meta_title = $this->_('top_up', 'Top up'); - } - - public function renderView() - { - if(_BG_PRESTASMS_DEMO_) - { - \Tools::redirectAdmin($this->context->link->getAdminLink('AdminPrestaSmsDashboardDefault')); - } - return $this->prestaSmsView("Top", "up", true); - } -} diff --git a/controllers/admin/AdminPrestaSmsUserProfileController.php b/controllers/admin/AdminPrestaSmsUserProfileController.php deleted file mode 100644 index 2f7ebe9..0000000 --- a/controllers/admin/AdminPrestaSmsUserProfileController.php +++ /dev/null @@ -1,23 +0,0 @@ -meta_title = $this->_('user_profile', 'User profile'); - } - - public function renderView() - { - return $this->prestaSmsView("User", "profile", false); - } -} diff --git a/controllers/admin/AdminPrestaSmsWalletDetailController.php b/controllers/admin/AdminPrestaSmsWalletDetailController.php deleted file mode 100644 index 4a8e680..0000000 --- a/controllers/admin/AdminPrestaSmsWalletDetailController.php +++ /dev/null @@ -1,23 +0,0 @@ -meta_title = $this->_('billing_informations', 'Billing informations'); - } - - public function renderView() - { - return $this->prestaSmsView("Wallet", "detail", true); - } -} diff --git a/controllers/admin/PrestaSmsController.php b/controllers/admin/PrestaSmsController.php deleted file mode 100644 index 5bf3a53..0000000 --- a/controllers/admin/PrestaSmsController.php +++ /dev/null @@ -1,130 +0,0 @@ -bootstrap = true; - $this->display = 'view'; - - parent::__construct(); - - if (!$this->module->active) - { - \Tools::redirectAdmin($this->context->link->getAdminLink('AdminHome')); - } - - $this->ps_di = new PrestaSms\DIContainer(\Db::getInstance()); - - $this->ps_module = $this->ps_di->getModule(); - $this->ps_settings = $this->ps_di->getSettings(); - $this->ps_translator = $this->ps_di->getTranslator(); - $this->ps_proxy = new PrestaSms\ProxyGenerator($this->controller_name, \Tools::getAdminTokenLite($this->controller_name)); - } - - public function setMedia($isNewTheme = false) - { - $this->addCSS(array( - $this->ps_module->getUrl('/dist/css/devices.min.css'), - $this->ps_module->getUrl('/'.(defined('BULKGATE_DEV_MODE') ? 'dev' : 'dist').'/css/bulkgate-prestasms.css'), - 'https://fonts.googleapis.com/icon?family=Material+Icons|Open+Sans:300,300i,400,400i,600,600i,700,700i,800,800i' - )); - parent::setMedia($isNewTheme); - } - - protected function prestaSmsView($presenter, $action, $box = false) - { - $this->synchronize(); - $this->context->smarty->registerPlugin('modifier', 'prestaSmsEscapeHtml', array('BulkGate\Extensions\Escape', 'html')); - $this->context->smarty->registerPlugin('modifier', 'prestaSmsEscapeJs', array('BulkGate\Extensions\Escape', 'js')); - $this->context->smarty->registerPlugin('modifier', 'prestaSmsEscapeUrl', array('BulkGate\Extensions\Escape', 'url')); - $this->context->smarty->registerPlugin('modifier', 'prestaSmsEscapeHtmlAttr', array('BulkGate\Extensions\Escape', 'htmlAttr')); - $this->context->smarty->registerPlugin('modifier', 'prestaSmsTranslate', array($this->ps_di->getTranslator(), 'translate')); - - return $this->prestaSmsTemplate(array( - 'application_id' => $this->ps_settings->load('static:application_id', ''), - 'language' => $this->ps_settings->load('main:language', 'en'), - 'presenter' => $presenter, - 'action' => $action, - 'title' => $this->meta_title, - 'mode' => defined('BULKGATE_DEV_MODE') ? 'dev' : 'dist', - 'box' => $box, - 'widget_api_url' => $this->ps_module->getUrl('/'.(defined('BULKGATE_DEV_MODE') ? 'dev' : 'dist').'/widget-api/widget-api.js'), - 'logo' => $this->ps_module->getUrl('/images/products/ps.svg'), - 'proxy' => $this->ps_proxy->get(), - 'salt' => Extensions\Compress::compress(PrestaSms\Helpers::generateTokens()), - 'authenticate' => array( - 'ajax' => true, - 'controller' => $this->controller_name, - 'action' => 'authenticate', - 'token' => \Tools::getAdminTokenLite($this->controller_name), - ), - 'homepage' => $this->context->link->getAdminLink('AdminPrestaSmsDashboardDefault'), - 'info' => $this->ps_module->info() - )); - } - - public function ajaxProcessAuthenticate() - { - try - { - Extensions\JsonResponse::send($this->ps_di->getProxy()->authenticate()); - } - catch (Extensions\IO\AuthenticateException $e) - { - Extensions\JsonResponse::send(array('redirect' => $this->context->link->getAdminLink('AdminPrestaSmsSignIn'))); - } - } - - protected function prestaSmsTemplate(array $data = array(), $template = 'base') - { - return $this->context->smarty->createTemplate(_BG_PRESTASMS_DIR_.'/templates/'.$template.'.tpl', null, null, $data)->fetch(); - } - - protected function synchronize($now = false) - { - if($this->ps_settings->load('static:application_token')) - { - $status = $this->ps_module->statusLoad(); $language = $this->ps_module->languageLoad(); $store = $this->ps_module->storeLoad(); - - $now = $now || $status || $language || $store; - - try - { - $this->ps_di->getSynchronize()->run($this->ps_module->getUrl('/module/settings/synchronize'), $now); - - return true; - } - catch (Extensions\IO\ConnectionException $e) - { - } - } - return false; - } - - protected function _($translate, $default = null) - { - return $this->ps_translator->translate($translate, $default); - } -} diff --git a/controllers/front/AsynchronousAsset.php b/controllers/front/AsynchronousAsset.php new file mode 100644 index 0000000..e652de8 --- /dev/null +++ b/controllers/front/AsynchronousAsset.php @@ -0,0 +1,38 @@ +getBulkGateContainer()->getByClass(Plugin\Settings\Settings::class); + + if (in_array($settings->load('main:dispatcher'), [Plugin\Event\Dispatcher::Cron, Plugin\Event\Dispatcher::Asset])) { + $count = $this->getBulkGateContainer()->getByClass(Plugin\Event\Asynchronous::class)->run(max(5, (int) ($settings->load('main:cron-limit') ?? 10))); + + echo "// Asynchronous task consumer has processed $count tasks"; + } else { + echo '// Asynchronous task consumer is disabled'; + } + + return true; + } +} diff --git a/controllers/front/Cron.php b/controllers/front/Cron.php new file mode 100644 index 0000000..4b3475f --- /dev/null +++ b/controllers/front/Cron.php @@ -0,0 +1,38 @@ +getBulkGateContainer()->getByClass(Plugin\Settings\Settings::class); + + if (in_array($settings->load('main:dispatcher'), [Plugin\Event\Dispatcher::Cron, Plugin\Event\Dispatcher::Asset])) { + $count = $this->getBulkGateContainer()->getByClass(Plugin\Event\Asynchronous::class)->run(max(5, (int) ($settings->load('main:cron-limit') ?? 10))); + + echo "// Asynchronous task consumer has processed $count tasks\n"; + } else { + echo "// Asynchronous task consumer is disabled\n"; + } + + return true; + } +} diff --git a/cron.php b/cron.php new file mode 100644 index 0000000..bb73cbb --- /dev/null +++ b/cron.php @@ -0,0 +1,15 @@ +\.$#' + identifier: offsetAssign.valueType + count: 9 + path: src/DI/Factory.php + + - + message: '#^BulkGate\\Plugin\\DI\\Container does not accept array\\|string\.$#' + identifier: offsetAssign.valueType + count: 1 + path: src/DI/Factory.php + + - + message: '#^Cannot call method getLanguage\(\) on object\|null\.$#' + identifier: method.nonObject + count: 1 + path: src/DI/Factory.php + + - + message: '#^Parameter \#1 \$multistore of class BulkGate\\PrestaShop\\Eshop\\MultiStore constructor expects PrestaShop\\PrestaShop\\Core\\Multistore\\MultistoreContextCheckerInterface, object\|null given\.$#' + identifier: argument.type + count: 1 + path: src/DI/Factory.php + + - + message: '#^Parameter \#1 \$order_state of class BulkGate\\PrestaShop\\Eshop\\OrderStatus constructor expects PrestaShop\\PrestaShop\\Adapter\\OrderState\\OrderStateDataProvider, object\|null given\.$#' + identifier: argument.type + count: 1 + path: src/DI/Factory.php + + - + message: '#^Parameter \#1 \$order_state of class BulkGate\\PrestaShop\\Eshop\\ReturnStatus constructor expects PrestaShop\\PrestaShop\\Adapter\\OrderReturnState\\OrderReturnStateDataProvider, object\|null given\.$#' + identifier: argument.type + count: 1 + path: src/DI/Factory.php + + - + message: '#^Parameter \#2 \$employee of class BulkGate\\PrestaShop\\Eshop\\OrderStatus constructor expects PrestaShop\\PrestaShop\\Adapter\\Employee\\ContextEmployeeProvider, object\|null given\.$#' + identifier: argument.type + count: 1 + path: src/DI/Factory.php + + - + message: '#^Parameter \#2 \$employee of class BulkGate\\PrestaShop\\Eshop\\ReturnStatus constructor expects PrestaShop\\PrestaShop\\Adapter\\Employee\\ContextEmployeeProvider, object\|null given\.$#' + identifier: argument.type + count: 1 + path: src/DI/Factory.php + + - + message: '#^Parameter \#2 \$url_provider of class BulkGate\\PrestaShop\\Eshop\\Configuration constructor expects PrestaShop\\PrestaShop\\Adapter\\Shop\\Url\\BaseUrlProvider, object\|null given\.$#' + identifier: argument.type + count: 1 + path: src/DI/Factory.php + + - + message: '#^Parameter \#3 \$shop of class BulkGate\\PrestaShop\\Eshop\\Configuration constructor expects PrestaShop\\PrestaShop\\Adapter\\Shop\\Context, object\|null given\.$#' + identifier: argument.type + count: 1 + path: src/DI/Factory.php diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..f90ede5 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,17 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: 8 + paths: + - src + - controllers + scanDirectories: + - /var/www/html + scanFiles: + - distribution.php + + # required by PS 1.7.x + bootstrapFiles: + - /var/www/html/autoload.php + - /var/www/html/vendor/autoload.php \ No newline at end of file diff --git a/prestasms/.htaccess b/prestasms/.htaccess deleted file mode 100644 index 22b9ed2..0000000 --- a/prestasms/.htaccess +++ /dev/null @@ -1,2 +0,0 @@ -Order Allow,Deny -Deny from all \ No newline at end of file diff --git a/prestasms/src/.htaccess b/prestasms/src/.htaccess deleted file mode 100644 index 22b9ed2..0000000 --- a/prestasms/src/.htaccess +++ /dev/null @@ -1,2 +0,0 @@ -Order Allow,Deny -Deny from all \ No newline at end of file diff --git a/prestasms/src/Api.php b/prestasms/src/Api.php deleted file mode 100644 index 6246916..0000000 --- a/prestasms/src/Api.php +++ /dev/null @@ -1,25 +0,0 @@ -database); - - $this->sendResponse(new Extensions\Api\Response($customers->loadCount($data->filter), true)); - } - - public function actionCampaignCustomer(Extensions\Api\IRequest $data) - { - $customers = new Customers($this->database); - - $this->sendResponse(new Extensions\Api\Response($customers->load($data->filter), true)); - } -} diff --git a/prestasms/src/Customers.php b/prestasms/src/Customers.php deleted file mode 100644 index 226a6b5..0000000 --- a/prestasms/src/Customers.php +++ /dev/null @@ -1,130 +0,0 @@ -table_user_key = 'id_customer'; - } - - - public function getTotal() - { - return (int) $this->db->execute("SELECT COUNT(DISTINCT `id_customer`) AS `total` FROM `{$this->db->table('address')}` WHERE `id_customer` != 0 AND (`phone_mobile` OR `phone`) AND `active` = 1")->getRow()->total; - } - - - public function getFilteredTotal(array $customers) - { - return (int) $this->db->execute("SELECT COUNT(DISTINCT `id_customer`) AS `total` FROM `{$this->db->table('address')}` WHERE `id_customer` IN ('".implode("','", $customers)."') AND (`phone_mobile` OR `phone`) AND `active` = 1")->getRow()->total; - } - - - protected function loadCustomers(array $customers, $limit = null) - { - return $this->db->execute(" - SELECT - `{$this->db->table('customer')}`.`email`, - `{$this->db->table('address')}`.`firstname` AS `first_name`, - `{$this->db->table('address')}`.`lastname` AS `last_name`, - `{$this->db->table('address')}`.`address1` AS `street1`, - `{$this->db->table('address')}`.`address2` AS `street2`, - `{$this->db->table('address')}`.`postcode` AS `zip`, - `{$this->db->table('address')}`.`city`, - `{$this->db->table('address')}`.`phone_mobile`, - `{$this->db->table('address')}`.`phone`, - `{$this->db->table('address')}`.`company` AS `company_name`, - `{$this->db->table('address')}`.`vat_number` AS `company_vat`, - LOWER(`{$this->db->table('country')}`.`iso_code`) AS `country`, - `{$this->db->table('state')}`.`name` AS `state` - FROM `{$this->db->table('customer')}` - LEFT JOIN `{$this->db->table('address')}` ON `{$this->db->table('customer')}`.`id_customer` = `{$this->db->table('address')}`.`id_customer` - LEFT JOIN `{$this->db->table('country')}` ON `{$this->db->table('address')}`.`id_country` = `{$this->db->table('country')}`.`id_country` - LEFT JOIN `{$this->db->table('state')}` ON `{$this->db->table('address')}`.`id_state` = `{$this->db->table('state')}`.`id_state` - WHERE - ". (count($customers) > 0 ? "`{$this->db->table('customer')}`.`id_customer` IN ('".implode("','", $customers)."') AND" : "") . " - `{$this->db->table('address')}`.`active` = 1 AND - (`{$this->db->table('address')}`.`phone_mobile` OR `{$this->db->table('address')}`.`phone`) - GROUP BY `{$this->db->table('customer')}`.`id_customer` - ". ($limit !== null ? ("LIMIT ".(int) $limit) : "") - )->getRows(); - } - - - protected function filter(array $filters) - { - $customers = array(); $filtered = false; - - foreach($filters as $key => $filter) - { - if(isset($filter['values']) && count($filter['values']) > 0 && !$this->empty) - { - switch ($key) - { - case 'first_name': - $customers = $this->getCustomers($this->db->execute("SELECT `id_customer` FROM `{$this->db->table('address')}` WHERE {$this->getSql($filter, 'firstname')}"), $customers); - break; - case 'last_name': - $customers = $this->getCustomers($this->db->execute("SELECT `id_customer` FROM `{$this->db->table('address')}` WHERE {$this->getSql($filter, 'lastname')}"), $customers); - break; - case 'country': - $customers = $this->getCustomers($this->db->execute("SELECT `{$this->db->table('address')}`.`id_customer` FROM `{$this->db->table('address')}` LEFT JOIN `{$this->db->table('country')}` ON `{$this->db->table('address')}`.`id_country` = `{$this->db->table('country')}`.`id_country` WHERE {$this->getSql($filter, 'iso_code', 'country')}"), $customers); - break; - case 'city': - $customers = $this->getCustomers($this->db->execute("SELECT `id_customer` FROM `{$this->db->table('address')}` WHERE {$this->getSql($filter, 'city')}"), $customers); - break; - case 'company_name': - $customers = $this->getCustomers($this->db->execute("SELECT `id_customer` FROM `{$this->db->table('address')}` WHERE {$this->getSql($filter, 'company')}"), $customers); - break; - case 'gender': - $customers = $this->getCustomers($this->db->execute("SELECT `id_customer` FROM `{$this->db->table('customer')}` WHERE {$this->getSql($filter, 'id_gender')}"), $customers); - break; - case 'advert': - $customers = $this->getCustomers($this->db->execute("SELECT `id_customer` FROM `{$this->db->table('customer')}` WHERE {$this->getSql($filter, 'optin')}"), $customers); - break; - case 'newsletter': - $customers = $this->getCustomers($this->db->execute("SELECT `id_customer` FROM `{$this->db->table('customer')}` WHERE {$this->getSql($filter, 'newsletter')}"), $customers); - break; - case 'order_amount': - $customers = $this->getCustomers($this->db->execute("SELECT id_customer, MAX(`total_paid`) AS `total` FROM `{$this->db->table('orders')}` GROUP BY `{$this->db->table('orders')}`.`id_customer` HAVING {$this->getSql($filter, 'total')}"), $customers); - break; - case 'all_orders_amount': - $customers = $this->getCustomers($this->db->execute("SELECT id_customer, SUM(`total_paid`) AS `total` FROM `{$this->db->table('orders')}` GROUP BY `{$this->db->table('orders')}`.`id_customer` HAVING {$this->getSql($filter, 'total')}"), $customers); - break; - case 'product': - $customers = $this->getCustomers($this->db->execute("SELECT `{$this->db->table('orders')}`.`id_customer` FROM `{$this->db->table('order_detail')}` INNER JOIN `{$this->db->table('orders')}` ON `{$this->db->table('orders')}`.`id_order` = `{$this->db->table('order_detail')}`.`id_order` INNER JOIN `{$this->db->table('product_lang')}` ON `{$this->db->table('product_lang')}`.`id_product` = `{$this->db->table('order_detail')}`.`product_id` WHERE {$this->getSql($filter, 'name', 'product_lang')} GROUP BY `{$this->db->table('orders')}`.`id_customer`"), $customers); - break; - case 'born_date': - $customers = $this->getCustomers($this->db->execute("SELECT `id_customer` FROM `{$this->db->table('customer')}` WHERE {$this->getSql($filter, 'birthday')}"), $customers); - break; - case 'product_reference': - $customers = $this->getCustomers($this->db->execute("SELECT `{$this->db->table('orders')}`.`id_customer` FROM `{$this->db->table('order_detail')}` INNER JOIN `{$this->db->table('orders')}` ON `{$this->db->table('orders')}`.`id_order` = `{$this->db->table('order_detail')}`.`id_order` INNER JOIN `{$this->db->table('product')}` ON `{$this->db->table('product')}`.`id_product` = `{$this->db->table('order_detail')}`.`product_id` WHERE {$this->getSql($filter, 'reference', 'product')} GROUP BY `{$this->db->table('orders')}`.`id_customer`"), $customers); - break; - case 'registration_date': - $customers = $this->getCustomers($this->db->execute("SELECT `id_customer` FROM `{$this->db->table('customer')}` WHERE {$this->getSql($filter, 'date_add')}"), $customers); - break; - case 'order_date': - $customers = $this->getCustomers($this->db->execute("SELECT `id_customer` FROM `{$this->db->table('orders')}` WHERE {$this->getSql($filter, 'date_add')}"), $customers); - break; - case 'order_status': - $customers = $this->getCustomers($this->db->execute("SELECT `id_customer` FROM `{$this->db->table('orders')}` WHERE {$this->getSql($filter, 'current_state')}"), $customers); - break; - case 'store': - $customers = $this->getCustomers($this->db->execute("SELECT `id_customer` FROM `{$this->db->table('customer')}` WHERE {$this->getSql($filter, 'id_shop')}"), $customers); - break; - } - - $filtered = true; - } - } - return array(array_unique($customers), $filtered); - } -} diff --git a/prestasms/src/DIContainer.php b/prestasms/src/DIContainer.php deleted file mode 100644 index a9bed72..0000000 --- a/prestasms/src/DIContainer.php +++ /dev/null @@ -1,49 +0,0 @@ -db = $db; - } - - - /** - * @return Database - */ - protected function createDatabase() - { - return new Database($this->db); - } - - - /** - * @return PrestaSMS - */ - protected function createModule() - { - return new PrestaSMS($this->getService('settings'), $this->getService('database')); - } - - - /** - * @return Customers - */ - protected function createCustomers() - { - return new Customers($this->getService('database')); - } -} diff --git a/prestasms/src/Database.php b/prestasms/src/Database.php deleted file mode 100644 index 8b60722..0000000 --- a/prestasms/src/Database.php +++ /dev/null @@ -1,81 +0,0 @@ -db = $db; - } - - public function execute($sql) - { - $output = array(); - - $this->sql[] = $sql; - - if(strpos(strtolower(trim($sql)), "select") === 0) - { - $result = $this->db->executeS($sql); - - if(is_array($result) && count($result)) - { - foreach ($result as $key => $item) - { - $output[$key] = (object) $item; - } - } - } - else - { - $this->db->execute($sql); - } - - return new BulkGate\Extensions\Database\Result($output); - } - - public function lastId() - { - return $this->db->Insert_ID(); - } - - public function escape($string) - { - return $this->db->_escape($string); - } - - public function prefix() - { - return _DB_PREFIX_; - } - - public function getSqlList() - { - return $this->sql; - } - - public function prepare($sql, array $params = []) - { - foreach($params as $param) - { - $sql = preg_replace("/%s/", "'".$this->db->_escape((string) $param)."'", $sql, 1); - } - return $sql; - } - - public function table($table) - { - return $this->prefix().$table; - } -} diff --git a/prestasms/src/Helpers.php b/prestasms/src/Helpers.php deleted file mode 100644 index 90dbcf7..0000000 --- a/prestasms/src/Helpers.php +++ /dev/null @@ -1,147 +0,0 @@ - $language) - { - if(isset($language['id_lang'])) - { - $tab->name[$language['id_lang']] = $name; - } - } - - $tab->class_name = $class; - $tab->module = _BG_PRESTASMS_SLUG_; - $tab->id_parent = $parent; - $tab->icon = $icon; - - $tab->save(); - - return $tab->id; - } - - public static function uninstallModuleTab($class) - { - $id = \Tab::getIdFromClassName($class); - - if($id !== 0) - { - $tab = new \Tab($id); - $tab->delete(); - return true; - } - return false; - } - - public static function generateTokens() - { - $output = array(); - - $result = \Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('SELECT `id_tab`, `class_name`, `module` FROM `'._DB_PREFIX_.'tab` WHERE `module` = \''._BG_PRESTASMS_SLUG_.'\'', true, false); - - if (is_array($result)) { - foreach ($result as $row) { - $output[$row['class_name']] = \Tools::getAdminToken($row['class_name'].(int)$row['id_tab'].(int)\Context::getContext()->employee->id); - } - } - return $output; - } - - public static function installMenu(Extensions\Translator $translator) - { - $main = Helpers::installModuleTab('PRESTASMS', $translator->translate('presta_sms', 'PrestaSMS'), 0, 'mail_outline'); - - Helpers::installModuleTab('AdminPrestaSmsDashboardDefault', $translator->translate('dashboard','Dashboard'), $main, 'desktop_windows'); - - $sms = Helpers::installModuleTab('AdminPrestaSmsSmsCampaignCampaign', $translator->translate('sms', 'SMS'), $main, 'sms'); - Helpers::installModuleTab('AdminPrestaSmsSmsCampaignNew', $translator->translate('start_campaign','Start Campaign'), $sms); - Helpers::installModuleTab('AdminPrestaSmsSmsCampaignDefault', $translator->translate('campaigns','Campaigns'), $sms); - Helpers::installModuleTab('AdminPrestaSmsInboxList', $translator->translate('inbox','Inbox'), $sms); - Helpers::installModuleTab('AdminPrestaSmsHistoryList', $translator->translate('history','History'), $sms); - Helpers::installModuleTab('AdminPrestaSmsStatisticsDefault', $translator->translate('statistics','Statistics'), $sms); - Helpers::installModuleTab('AdminPrestaSmsBlackListDefault', $translator->translate('black_list','Black list'), $sms); - Helpers::installModuleTab('AdminPrestaSmsSmsPriceList', $translator->translate('price_list', 'Price list'), $sms); - - $payment = Helpers::installModuleTab('AdminPrestaSmsSignIn', $translator->translate('payments', 'Payments'), $main, 'payment'); - Helpers::installModuleTab('AdminPrestaSmsTopUp', $translator->translate('buy_credit', 'Buy credit'), $payment); - Helpers::installModuleTab('AdminPrestaSmsPaymentList', $translator->translate('invoices', 'Invoices'), $payment); - Helpers::installModuleTab('AdminPrestaSmsWalletDetail', $translator->translate('payments_data', 'Payments data'), $payment); - - $settings = Helpers::installModuleTab('AdminPrestaSmsSignUp', $translator->translate('settings', 'Settings'), $main, 'settings'); - Helpers::installModuleTab('AdminPrestaSmsUserProfile', $translator->translate('user_profile', 'User profile'), $settings); - Helpers::installModuleTab('AdminPrestaSmsModuleNotificationsAdmin', $translator->translate('admin_sms', 'Admin SMS'), $settings); - Helpers::installModuleTab('AdminPrestaSmsModuleNotificationsCustomer', $translator->translate('customer_sms', 'Customer SMS'), $settings); - Helpers::installModuleTab('AdminPrestaSmsSmsSettingsDefault', $translator->translate('sender_id_profiles', 'Sender ID Profiles'), $settings); - Helpers::installModuleTab('AdminPrestaSmsModuleSettingsDefault', $translator->translate('module_settings', 'Module settings'), $settings); - - Helpers::installModuleTab('AdminPrestaSmsAboutDefault', $translator->translate('about_module','About module'), $settings); - } - - public static function uninstallMenu() - { - Helpers::uninstallModuleTab('PRESTASMS'); - - Helpers::uninstallModuleTab('AdminPrestaSmsDashboardDefault'); - - Helpers::uninstallModuleTab('PRESTASMS_SMS'); - Helpers::uninstallModuleTab('AdminPrestaSmsSmsCampaignNew'); - Helpers::uninstallModuleTab('AdminPrestaSmsSmsCampaignDefault'); - Helpers::uninstallModuleTab('AdminPrestaSmsInboxList'); - Helpers::uninstallModuleTab('AdminPrestaSmsHistoryList'); - Helpers::uninstallModuleTab('AdminPrestaSmsStatisticsDefault'); - Helpers::uninstallModuleTab('AdminPrestaSmsBlackListDefault'); - Helpers::uninstallModuleTab('AdminPrestaSmsSmsPriceList'); - - Helpers::uninstallModuleTab('PRESTASMS_PAYMENTS'); - Helpers::uninstallModuleTab('AdminPrestaSmsTopUp'); - Helpers::uninstallModuleTab('AdminPrestaSmsPaymentList'); - Helpers::uninstallModuleTab('AdminPrestaSmsWalletDetail'); - - Helpers::uninstallModuleTab('PRESTASMS_SETTINGS'); - Helpers::uninstallModuleTab('AdminPrestaSmsUserProfile'); - Helpers::uninstallModuleTab('AdminPrestaSmsModuleNotificationsAdmin'); - Helpers::uninstallModuleTab('AdminPrestaSmsModuleNotificationsCustomer'); - Helpers::uninstallModuleTab('AdminPrestaSmsSmsSettingsDefault'); - Helpers::uninstallModuleTab('AdminPrestaSmsModuleSettingsDefault'); - - Helpers::uninstallModuleTab('AdminPrestaSmsAboutDefault'); - } - - public static function getOrderPhoneNumber($order_id) - { - $phone_number = null; $iso = null; - - $order = new \Order($order_id); - $address_delivery = new \Address($order->id_address_delivery); - - $phone_number = $address_delivery->phone_mobile ?: $address_delivery->phone; - - $country = new \Country($address_delivery->id_country); - - $iso = strtolower($country->iso_code); - - if(strlen(trim($phone_number)) === 0) - { - $address_invoice = new \Address($order->id_address_invoice); - - $phone_number = $address_invoice->phone_mobile ?: $address_invoice->phone; - - $country_invoice = new \Country($address_invoice->id_country); - - $iso = strtolower($country_invoice->iso_code); - } - - return array($phone_number, $iso); - } -} diff --git a/prestasms/src/HookExtension.php b/prestasms/src/HookExtension.php deleted file mode 100644 index 1586930..0000000 --- a/prestasms/src/HookExtension.php +++ /dev/null @@ -1,32 +0,0 @@ - $variables, - 'database' => $database - ), null, false, true, false, $variables->get('store_id')); - - /** @example PSEUDO CODE - - $result = $database->execute('SELECT `tracking_number` FROM `order` WHERE order_id = "'.$database->escape($variables->get('order_id')).'"'); - - if($result->getNumRows()) - { - $row = $result->getRow(); - - $variables->set('tracking_number', $row['tracking_number']); - } - - */ - } -} diff --git a/prestasms/src/HookLoad.php b/prestasms/src/HookLoad.php deleted file mode 100644 index 573a2f7..0000000 --- a/prestasms/src/HookLoad.php +++ /dev/null @@ -1,342 +0,0 @@ -db = $db; - $this->context = $context; - $this->locale = new BulkGate\Extensions\LocaleSimple(); - } - - public function language(Variables $variables) - { - if($variables->get('lang_id')) - { - $lang = new \Language($variables->get('lang_id'), $variables->get('lang_id')); - - if(extension_loaded('intl')) - { - $this->locale = new BulkGate\Extensions\LocaleIntl($lang->locale); - } - else - { - $this->locale = new BulkGate\Extensions\LocaleSimple($lang->date_format_lite); - } - $variables->set('language_iso', \Language::getIsoById((int) $variables->get('lang_id'))); - } - } - - public function order(Variables $variables) - { - if($variables->get('order_id')) - { - $order = new \Order($variables->get('order_id'), $variables->get('lang_id', null)); - $currency = \Currency::getCurrency($order->id_currency); - - $variables->set('long_order_id', sprintf("%06d", $variables->get('order_id'))); - - $variables->set('customer_id', $order->id_customer); - $date = new \DateTime($order->date_add); - - $variables->set('id_address_delivery', (int) $order->id_address_delivery); - $variables->set('id_address_invoice', (int) $order->id_address_invoice); - $variables->set('order_payment', $order->payment); - $variables->set('order_currency', isset($currency['iso_code']) ? $currency['iso_code'] : null); - $variables->set('order_currency_symbol', isset($currency['sign']) ? $currency['sign'] : null); - $variables->set('order_total_paid', number_format($order->total_paid, 2)); - $variables->set('order_total_locale', $this->locale->price($order->total_paid, $variables->get('order_currency'))); - $variables->set('order_total_paid_integer', (int) $order->total_paid); - $variables->set('cart_id', (int) $order->id_cart); - $variables->set('carrier_id', (int) $order->id_carrier); - $variables->set('order_reference', $order->reference); - - $variables->set('order_datetime', $this->locale->datetime($date)); - $variables->set('order_date', $this->locale->date($date)); - $variables->set('order_date1', preg_replace('/([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})/', '\\3.\\2.\\1', $order->date_add)); - $variables->set('order_date2', preg_replace('/([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})/', '\\3/\\2/\\1', $order->date_add)); - $variables->set('order_date3', preg_replace('/([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})/', '\\3-\\2-\\1', $order->date_add)); - $variables->set('order_date4', preg_replace('/([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})/', '\\1-\\2-\\3', $order->date_add)); - $variables->set('order_date5', preg_replace('/([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})/', '\\2.\\3.\\1', $order->date_add)); - $variables->set('order_date6', preg_replace('/([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})/', '\\2/\\3/\\1', $order->date_add)); - $variables->set('order_date7', preg_replace('/([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})/', '\\2-\\3-\\1', $order->date_add)); - $variables->set('order_time', preg_replace('/([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})/', '\\4:\\5', $order->date_add)); - $variables->set('order_time1', preg_replace('/([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})/', '\\4:\\5:\\6', $order->date_add)); - - if($variables->get('carrier_id')) - { - $carrier = new \Carrier($variables->get('carrier_id'), $variables->get('lang_id', null)); - - $variables->set('order_carrier_name', $carrier->name); - $variables->set('order_carrier_delay', $carrier->delay); - - $order_carrier = $this->db->execute($this->db->prepare("SELECT `id_order_carrier` FROM `{$this->db->table('order_carrier')}` WHERE `id_order` = %s AND `id_carrier` = %s", array($variables->get('order_id'), $variables->get('carrier_id')))); - - if($order_carrier->getNumRows() > 0) - { - $orderCarrier = new \OrderCarrier($order_carrier->getRow()->id_order_carrier, $variables->get('lang_id', null)); - $variables->set('order_carrier_tracking_number', $orderCarrier->tracking_number); - $variables->set('order_carrier_tracking_date', $this->locale->datetime(new \DateTime($orderCarrier->date_add))); - $variables->set('order_carrier_price', number_format($orderCarrier->shipping_cost_tax_incl, 2)); - $variables->set('order_carrier_weight', number_format($orderCarrier->weight, 2)); - $variables->set('order_carrier_price_locale', $this->locale->price($orderCarrier->shipping_cost_tax_incl, $variables->get('order_currency'))); - $variables->set('order_carrier_url', str_replace('@', $orderCarrier->tracking_number, $carrier->url)); - } - } - - $message = \Message::getMessageByCartId($variables->get('cart_id')); - - if(is_array($message) && isset($message['message'])) - { - $variables->set('order_message', $message['message']); - } - - if($variables->get('id_address_delivery')) - { - $this->address($variables, (int) $variables->get('id_address_delivery')); - } - - if($variables->get('id_address_invoice')) - { - $this->address($variables, (int) $variables->get('id_address_invoice'), 'invoice_'); - } - - $this->orderProducts($variables); - $this->returnProducts($variables, $order); - } - } - - - public function returnProducts(Variables $variables, \Order $order) - { - if($variables->get('return_id')) - { - $return = new \OrderReturn($variables->get('return_id'), $variables->get('lang_id')); - - $variables->set('return_question', $return->question); - - $return_detail = \OrderReturn::getOrdersReturnProducts($variables->get('return_id'), $order); - - $p1 = $p2 = $p3 = $p4 = array(); - - foreach($return_detail as $row) - { - $p1[] = $row['product_quantity'].'x '.$row['product_name'].' '.$row['product_reference']; - $p2[] = $row['product_quantity'].'x '.$row['product_name']; - $p3[] = $row['product_quantity'].'x ('.$row['product_id'].')'.$row['product_name'].' '.$row['product_reference']; - $p4[] = $row['product_quantity'].'x '.$row['product_reference']; - } - - $variables->set("return_products1", implode("; ", $p1)); - $variables->set("return_products2", implode("; ", $p2)); - $variables->set("return_products3", implode("; ", $p3)); - $variables->set("return_products4", implode("; ", $p4)); - - $variables->set("return_products5", implode("\n", $p1)); - $variables->set("return_products6", implode("\n", $p2)); - $variables->set("return_products7", implode("\n", $p3)); - $variables->set("return_products8", implode("\n", $p4)); - } - } - - - public function orderProducts(Variables $variables) - { - if($variables->get('order_id')) - { - $p1 = $p2 = $p3 = $p4 = $pr1 = $pr2 = $pr3 = $pr4 = array(); - - $list = \OrderDetail::getList($variables->get('order_id')); - - $filter = $variables->get('filter_products', array()); - - foreach($list as $row) - { - if(empty($filter) || in_array($row['id_order_detail'], $filter)) - { - $p1[] = $row['product_quantity'].'x '.$row['product_name'].' '.$row['product_reference']; - $p2[] = $row['product_quantity'].'x '.$row['product_name']; - $p3[] = $row['product_quantity'].'x ('.$row['product_id'].')'.$row['product_name'].' '.$row['product_reference']; - $p4[] = $row['product_quantity'].'x '.$row['product_reference']; - - $pr1[] = $row['product_quantity'].','.$row['product_name'].','.$this->locale->price($row['product_price'], $variables->get('order_currency')); - $pr2[] = $row['product_quantity'].';'.$row['product_name'].';'.$this->locale->price($row['product_price'], $variables->get('order_currency')); - $pr3[] = $row['product_quantity'].','.$row['product_reference'].','.$this->locale->price($row['product_price'], $variables->get('order_currency')); - $pr4[] = $row['product_quantity'].';'.$row['product_reference'].';'.$this->locale->price($row['product_price'], $variables->get('order_currency')); - } - } - - $variables->set('order_products1', implode('; ', $p1)); - $variables->set('order_products2', implode('; ', $p2)); - $variables->set('order_products3', implode('; ', $p3)); - $variables->set('order_products4', implode('; ', $p4)); - - - $variables->set('order_products5', implode("\n", $p1)); - $variables->set('order_products6', implode("\n", $p2)); - $variables->set('order_products7', implode("\n", $p3)); - $variables->set('order_products8', implode("\n", $p4)); - - $variables->set('order_smsprinter1', implode(';', $pr1)); - $variables->set('order_smsprinter2', implode(';', $pr2)); - $variables->set('order_smsprinter3', implode(';', $pr3)); - $variables->set('order_smsprinter4', implode(';', $pr4)); - - $variables->set('filter_products', '-'); - } - } - - - public function customer(Variables $variables) - { - if($variables->get('customer_id')) - { - $customer = new \Customer($variables->get('customer_id')); - - $variables->set('customer_email', $customer->email, '', false); - $variables->set('customer_lastname', $customer->lastname, '', false); - $variables->set('customer_firstname', $customer->firstname, '', false); - - $address_id = (int) \Address::getFirstCustomerAddressId($variables->get('customer_id')); - - if($address_id !== $variables->get('id_address_delivery', -1) && $address_id !== $variables->get('id_address_invoice', -1)) - { - $this->address($variables, $address_id); - } - } - } - - - public function address(Variables $variables, $address_id, $prefix = '') - { - $address = new \Address($address_id, $variables->get('lang_id')); - - $variables->set('customer_'.$prefix.'firstname', $address->firstname, '', false); - $variables->set('customer_'.$prefix.'lastname', $address->lastname, '', false); - - $variables->set('customer_'.$prefix.'country_id', strtolower(\Country::getIsoById($address->id_country)), '', false); - - $variables->set('customer_'.$prefix.'company', $address->company, '', false); - - $variables->set('customer_'.$prefix.'phone', $address->phone, '', false); - $variables->set('customer_'.$prefix.'mobile', $address->phone_mobile, '', false); - - if(strlen(trim($address->address1)) > 0) - { - $variables->set('customer_'.$prefix.'address', $address->address1 . ', ' . $address->address2, '', false); - } - else - { - $variables->set('customer_'.$prefix.'address', $address->address1, '', false); - } - - $variables->set('customer_'.$prefix.'postcode', $address->postcode, '', false); - $variables->set('customer_'.$prefix.'city', $address->city, '', false); - - $variables->set('customer_'.$prefix.'country', $address->country, '', false); - - ((int) $address->id_state !== 0) && $variables->set('customer_'.$prefix.'state', \State::getNameById((int) $address->id_state), '', false); - - $variables->set('customer_'.$prefix.'vat_number', $address->vat_number, '', false); - } - - public function orderStatus(Variables $variables) - { - if($variables->get('order_status_id')) - { - $state = new \OrderState($variables->get('order_status_id'), $variables->get('lang_id', null)); - - $variables->set('order_status', $state->name, $variables->get('order_status_id')); - } - } - - public function shop(Variables $variables) - { - $configuration = \Configuration::getMultiple(array('PS_SHOP_EMAIL', 'PS_SHOP_PHONE'), $variables->get('lang_id', null)); - - $variables->set('shop_email', isset($configuration['PS_SHOP_EMAIL']) ? $configuration['PS_SHOP_EMAIL'] : ''); - $variables->set('shop_phone', isset($configuration['PS_SHOP_PHONE']) ? $configuration['PS_SHOP_PHONE'] : ''); - - $shop = new \Shop($variables->get('store_id'), $variables->get('lang_id', null)); - - $variables->set('shop_name', $shop->name); - $variables->set('shop_domain', $shop->domain); - } - - public function product(Variables $variables) - { - if($variables->get('product_id')) - { - $product = new \Product($variables->get('product_id'), true, $variables->get('lang_id', null)); - - $variables->set('product_name', $product->name); - $variables->set('product_description', strip_tags($product->description_short)); - $variables->set('product_manufacturer', $product->manufacturer_name); - $variables->set('product_supplier', $product->supplier_name); - - $variables->set('product_price', number_format($product->price, 2)); - $variables->set('product_price_locale', $this->locale->price($product->price, $this->context->currency->iso_code)); - - $variables->set('product_quantity', (int) $product->quantity); - $variables->set('product_minimal_quantity', (int) $product->minimal_quantity); - - $variables->set('product_ref', $product->reference); - $variables->set('product_supplier_ref', $product->supplier_reference); - $variables->set('product_ean13', $product->ean13); - $variables->set('product_upc', $product->upc); - $variables->set('product_supplier_id', $product->id_supplier); - $variables->set('product_isbn', $product->isbn); - } - } - - public function employee(Variables $variables) - { - if($this->context->employee && $this->context->employee->id) - { - $employee = new \Employee($this->context->employee->id, $variables->get('lang_id', null)); - - $variables->set('employee_id', $employee->id); - $variables->set('employee_email', $employee->email); - $variables->set('employee_firstname', $employee->firstname); - $variables->set('employee_lastname', $employee->lastname); - } - } - - public function extension(Variables $variables) - { - if(class_exists('BulkGate\PrestaSMS\HookExtension')) - { - $hook = new HookExtension(); - $hook->extend($this->db, $variables); - } - } - - public function load(Variables $variables) - { - $this->language($variables); - $this->order($variables); - $this->orderStatus($variables); - $this->customer($variables); - $this->product($variables); - $this->shop($variables); - $this->employee($variables); - $this->extension($variables); - } -} diff --git a/prestasms/src/PrestaSMS.php b/prestasms/src/PrestaSMS.php deleted file mode 100644 index aa765e8..0000000 --- a/prestasms/src/PrestaSMS.php +++ /dev/null @@ -1,163 +0,0 @@ - 'PrestaShop', - 'name' => _BG_PRESTASMS_NAME_, - 'url' => 'http://www.presta-sms.com', - 'developer' => _BG_PRESTASMS_AUTHOR_, - 'developer_url' => 'http://www.topefekt.com/', - 'description' => 'PrestaSMS module extends your PrestaShop store capabilities and creates new opportunities for your business. You can promote your products and sales via personalized bulk SMS. Make your customers happy by notifying them about order status change via SMS notifications. Receive an SMS whenever a new order is placed, a product is out of stock, and much more.', - ); - - /** @var ISettings */ - private $settings; - - /** @var Database */ - private $db; - - /** @var array */ - private $plugin_data = array(); - - public $id; - - public function __construct(ISettings $settings, Database $db) - { - $this->settings = $settings; - $this->db = $db; - } - - public function getUrl($path = '') - { - if(defined('BULKGATE_DEBUG')) - { - return Escape::url(BULKGATE_DEBUG.$path); - } - else - { - return Escape::url('https://portal.bulkgate.com'.$path); - } - } - - public function statusLoad() - { - $actual = array(); - $context = \Context::getContext(); - - /** @var Result $result */ - $result = $this->db->execute("SELECT `".$this->db->table('order_state')."`.`id_order_state`, `".$this->db->table('order_state_lang')."`.`name` FROM `".$this->db->table('order_state')."` INNER JOIN `".$this->db->table('order_state_lang')."` ON `".$this->db->table('order_state')."`.`id_order_state` = `".$this->db->table('order_state_lang')."`.`id_order_state` WHERE `".$this->db->table('order_state_lang')."`.`id_lang` = '". (int)$context->language->id."'"); - - foreach ($result as $store) - { - $actual[$store->id_order_state] = $store->name; - } - - $status_list = (array) $this->settings->load(':order_status_list', null); - - if(json_encode($status_list) !== json_encode($actual)) - { - $this->settings->set(':order_status_list', Json::encode($actual), array('type' => 'json')); - return true; - } - return false; - } - - public function languageLoad() - { - if((bool) $this->settings->load('main:language_mutation')) - { - $languages = (array) $this->settings->load(':languages', null); - $actual = array(); - - foreach(\Language::getLanguages(true) as $language) - { - if(isset($language['id_lang']) && isset($language['name'])) - { - $actual[$language['id_lang']] = $language['name']; - } - } - - if(json_encode($languages) !== json_encode($actual)) - { - $this->settings->set(':languages', Json::encode($actual), array('type' => 'json')); - return true; - } - return false; - } - else - { - $this->settings->set(':languages', Json::encode(array('default' => 'Default')), array('type' => 'json')); - return true; - } - } - - public function storeLoad() - { - $actual = array(); - - /** @var Result $stores */ - $result = $this->db->execute("SELECT `id_shop`, `name` FROM `".$this->db->table('shop')."` WHERE `active` = '1' LIMIT 200"); - - foreach ($result as $store) - { - $actual[$store->id_shop] = $store->name; - } - - $stores = (array) $this->settings->load(':stores', null); - - if(json_encode($stores) !== json_encode($actual)) - { - $this->settings->set(':stores', Json::encode($actual), array('type' => 'json')); - return true; - } - return false; - } - - public function product() - { - return self::PRODUCT; - } - - public function url() - { - return _PS_BASE_URL_; - } - - public function info($key = null) - { - if(empty($this->plugin_data)) - { - $this->plugin_data = array_merge( - array( - 'store_version' => defined('_BG_PRESTASMS_PS_MIN_VERSION_') ? (_BG_PRESTASMS_PS_MIN_VERSION_.'+') : '1.7', - 'version' => _BG_PRESTASMS_VERSION_, - 'application_id' => $this->settings->load('static:application_id', -1), - 'application_product' => $this->product(), - 'delete_db' => $this->settings->load('main:delete_db', 0), - 'language_mutation' => $this->settings->load('main:language_mutation', 0) - ), - $this->info - ); - } - if($key === null) - { - return $this->plugin_data; - } - return isset($this->plugin_data[$key]) ? $this->plugin_data[$key] : null; - } -} diff --git a/prestasms/src/ProxyGenerator.php b/prestasms/src/ProxyGenerator.php deleted file mode 100644 index c19f232..0000000 --- a/prestasms/src/ProxyGenerator.php +++ /dev/null @@ -1,53 +0,0 @@ -controller = (string) $controller; - $this->token = (string) $token; - } - - public function add($action, $proxy_action, $reducer = '_generic', $url = 'ajax-tab.php') - { - $proxy = array( - 'url' => $url, - 'params' => array( - 'action' => $proxy_action, - 'ajax' => true, - 'controller' => $this->controller, - 'token' => $this->token - ) - ); - - if(isset($this->proxy[$reducer])) - { - $this->proxy[$reducer][$action] = $proxy; - } - else - { - $this->proxy[$reducer] = array($action => $proxy); - } - } - - public function get() - { - return $this->proxy; - } -} diff --git a/prestasms/src/_extension.php b/prestasms/src/_extension.php deleted file mode 100644 index 1ec1f0e..0000000 --- a/prestasms/src/_extension.php +++ /dev/null @@ -1,17 +0,0 @@ -settings = $settings; + $this->sign = $sign; + } + + /** + * @return never + */ + public function run(string $invalid_redirect): void + { + JsonResponse::send( + $this->settings->load('static:application_token') === null ? + ['redirect' => $invalid_redirect] : + ['token' => $this->sign->authenticate(false, ['expire' => \time() + 300])] + ); + } +} diff --git a/src/Ajax/PluginSettingsChange.php b/src/Ajax/PluginSettingsChange.php new file mode 100644 index 0000000..e8f6328 --- /dev/null +++ b/src/Ajax/PluginSettingsChange.php @@ -0,0 +1,87 @@ +settings = $settings; + $this->synchronizer = $synchronizer; + } + + /** + * @param array $unsafe_post_data + * + * @return array{data: array{layout: array{server: array{application_settings: array}}}}|array{data: array{redirect: string}} + */ + public function run(array $unsafe_post_data, callable $on_language_change): array + { + $output = []; + + $actual_language = $this->settings->load('main:language'); + + if (isset($unsafe_post_data['marketing_message_opt_in_url']) && \is_scalar($unsafe_post_data['marketing_message_opt_in_url'])) { + $unsafe_post_data['marketing_message_opt_in_url'] = self::formatUrl((string) $unsafe_post_data['marketing_message_opt_in_url']); + } + + $this->change('dispatcher', $unsafe_post_data, $output); + $this->change('synchronization', $unsafe_post_data, $output); + $this->change('language', $unsafe_post_data, $output); + $this->change('language_mutation', $unsafe_post_data, $output, 'bool'); + $this->change('delete_db', $unsafe_post_data, $output, 'bool'); + $this->change('address_preference', $unsafe_post_data, $output); + $this->change('marketing_message_opt_in_enabled', $unsafe_post_data, $output, 'bool'); + $this->change('marketing_message_opt_in_label', $unsafe_post_data, $output); + $this->change('marketing_message_opt_in_default', $unsafe_post_data, $output, 'bool'); + $this->change('marketing_message_opt_in_url', $unsafe_post_data, $output); + + $this->synchronizer->synchronize(true); + + if (isset($unsafe_post_data['language']) && $actual_language !== $unsafe_post_data['language']) { + // language change requires hard reload for re-initialization languages. + // we are using reload_lang parameter to handle url change and effectively reloads the page + return ['data' => ['redirect' => $on_language_change($unsafe_post_data['language'])]]; + } + + return ['data' => ['layout' => ['server' => ['application_settings' => $output]]], 'success' => ['saved']]; + } + + /** + * @param array $unsafe_data + * @param array $output + * + * @param-out array $output + */ + private function change(string $key, array $unsafe_data, array &$output, string $type = 'string'): void + { + if (isset($unsafe_data[$key]) && \is_scalar($unsafe_data[$key])) { + $value = Helpers::deserializeValue((string) $unsafe_data[$key], $type); + + $this->settings->set("main:$key", $output[$key] = $value, ['type' => $type]); + } + } + + private static function formatUrl(string $url): string + { + return ((int) \preg_match('~^[A-Za-z]+?://~', $url)) !== 0 ? $url : "https://$url"; + } +} diff --git a/src/Controller/AdminController.php b/src/Controller/AdminController.php new file mode 100644 index 0000000..89832d3 --- /dev/null +++ b/src/Controller/AdminController.php @@ -0,0 +1,103 @@ +getBulkGateContainer()->getByClass(Plugin\User\Sign::class); + $url = $this->getBulkGateContainer()->getByClass(Plugin\IO\Url::class); + $settings_synchronizer = $this->getBulkGateContainer()->getByClass(Plugin\Settings\Synchronizer::class); + $shop_synchronizer = $this->getBulkGateContainer()->getByClass(Plugin\Eshop\EshopSynchronizer::class); + $settings = $this->getBulkGateContainer()->getByClass(Plugin\Settings\Settings::class); + + $shop_synchronizer->run(); + + $token = $sign->authenticate(false, ['expire' => time() + 300]); + + return $this->render('@Modules/bg_prestasms/views/templates/admin/index.html.twig', [ + 'layoutTitle' => BulkGateWhiteLabel . ' SMS', + 'showContentHeader' => false, + 'token' => $token, + 'url' => $url, + 'synchronizer' => $settings_synchronizer, + 'settings' => $settings, + ]); + } + + public function debugAction(): Response + { + $requirements = $this->getBulkGateContainer()->getByClass(Plugin\Debug\Requirements::class); + $url = $this->getBulkGateContainer()->getByClass(Plugin\IO\Url::class); + $logger = $this->getBulkGateContainer()->getByClass(Plugin\Debug\Logger::class); + + $requirements = $requirements->run([ + $requirements->same('{"message":"BulkGate API"}', file_get_contents($url->get('api/welcome')), 'Api Connection'), + $requirements->same(true, version_compare($logger->platform_version, BulkGateMinimalPrestashopVersion, '>='), 'Prestashop ver. >= ' . BulkGateMinimalPrestashopVersion), + ]); + + return $this->render('@Modules/bg_prestasms/views/templates/admin/debug.html.twig', [ + 'layoutTitle' => 'BulkGate SMS - debug', + 'php_version' => phpversion(), + 'prestashop_version' => _PS_VERSION_, + 'platform_version' => $logger->platform_version, + 'module_version' => $logger->module_version, + 'url' => $url->get(), + 'requirements' => $requirements, + 'errors' => array_reverse($logger->getList()), + ]); + } + + public function proxyAction(string $action, Request $request, UrlGeneratorInterface $router): JsonResponse + { + $settings_change = $this->getBulkGateContainer()->getByClass(Ajax\PluginSettingsChange::class); + $authenticate = $this->getBulkGateContainer()->getByClass(Ajax\Authenticate::class); + $sign = $this->getBulkGateContainer()->getByClass(Plugin\User\Sign::class); + + $base_url = $request->getSchemeAndHttpHost(); + + if ($action === 'login') { + ['email' => $email, 'password' => $password] = Plugin\Utils\Json::decode((string) $request->getContent()); + + return $this->json($sign->in($email, $password, $base_url . $router->generate('bulkgate_main_app', [ + 'reload' => time(), + '_fragment' => '/dashboard', + ], UrlGeneratorInterface::ABSOLUTE_PATH))); + } elseif ($action === 'logout') { + return $this->json($sign->out('/sign/in')); + } elseif ($action === 'authenticate') { + return $this->json($authenticate->run('/sign/in')); + } elseif ($action === 'module-settings') { + $data = Plugin\Utils\Json::decode((string) $request->getContent()); + + return $this->json($settings_change->run($data, fn (string $lang): string => $base_url . $router->generate('bulkgate_main_app', [ + 'reload' => $lang, + '_fragment' => '/dashboard', + ], UrlGeneratorInterface::ABSOLUTE_PATH))); + } else { + return $this->json([ + 'status' => 'error', + 'message' => 'Unknown action', + ]); + } + } +} diff --git a/src/DI/Container.php b/src/DI/Container.php new file mode 100644 index 0000000..6b387cb --- /dev/null +++ b/src/DI/Container.php @@ -0,0 +1,53 @@ +bulkgate_container !== null) { + return $this->bulkgate_container; + } + + /** + * @phpstan-ignore function.alreadyNarrowedType + */ + $symfony_di = SymfonyContainer::getInstance() ?? (method_exists($this, 'getContainer') ? $this->getContainer() : $this->container); + + Factory::setup(fn () => [ + 'symfony_di' => $symfony_di, + 'api_version' => BulkGateApiVersion, + 'module_version' => BulkGateModuleVersion, + 'platform_version' => _PS_VERSION_, + 'gate_url' => BulkGateWhiteLabelUrl, + 'referer_id' => BulkGateRefererId, + 'default_settings' => [ + 'main:dispatcher' => 'asset', + 'main:synchronization' => 'all', + 'main:language' => 'auto', + 'main:language_mutation' => false, + 'main:delete_db' => false, + 'main:address_preference' => 'delivery', + 'main:marketing_message_opt_in_enabled' => false, + 'main:marketing_message_opt_in_label' => '', + 'main:marketing_message_opt_in_default' => false, + 'main:marketing_message_opt_in_url' => '', + ], + ]); + + return $this->bulkgate_container = Factory::get(); + } +} diff --git a/src/DI/Factory.php b/src/DI/Factory.php new file mode 100644 index 0000000..891977e --- /dev/null +++ b/src/DI/Factory.php @@ -0,0 +1,146 @@ + $parameters + */ + protected static function createContainer(array $parameters = []): Plugin\DI\Container + { + /** + * @var SymfonyContainer $symfony_di + */ + ['symfony_di' => $symfony_di] = $parameters; + + $container = new Plugin\DI\Container($parameters['mode'] ?? 'strict'); + + // Database + $container['database.connection'] = ['factory' => Connection::class, 'parameters' => ['db' => $symfony_di->get('doctrine.dbal.default_connection')]]; + + // Debug + /* + * @phpstan-ignore offsetAssign.valueType + */ + $container['debug.repository.logger'] = ['factory' => Plugin\Debug\Repository\LoggerSettings::class, 'factory_method' => function () use ($container, $parameters): Plugin\Debug\Repository\LoggerSettings { + $logger = new Plugin\Debug\Repository\LoggerSettings($container->getByClass(Plugin\Settings\Settings::class)); + $logger->setup(is_int($parameters['logger_limit'] ?? null) ? $parameters['logger_limit'] : 100); + + return $logger; + }]; + $container['debug.logger'] = ['factory' => Plugin\Debug\Logger::class, 'parameters' => ['platform_version' => $parameters['platform_version'], 'module_version' => $parameters['module_version']]]; + $container['debug.requirements'] = Plugin\Debug\Requirements::class; + + // Ajax + $container['ajax.authenticate'] = Ajax\Authenticate::class; + $container['ajax.plugin_settings'] = Ajax\PluginSettingsChange::class; + + // Eshop + $container['eshop.configuration'] = ['factory' => Eshop\Configuration::class, 'factory_method' => fn () => new Eshop\Configuration( + $parameters['module_version'], + $symfony_di->get(BaseUrlProvider::class), + $symfony_di->get('prestashop.adapter.shop.context') + )]; + $container['eshop.synchronizer'] = Plugin\Eshop\EshopSynchronizer::class; + $container['eshop.order_status'] = ['factory' => Eshop\OrderStatus::class, 'factory_method' => fn () => new Eshop\OrderStatus( + $symfony_di->get(OrderStateDataProvider::class), + $symfony_di->get(ContextEmployeeProvider::class) + )]; + $container['eshop.return_status'] = ['factory' => Eshop\ReturnStatus::class, 'factory_method' => fn () => new Eshop\ReturnStatus( + $symfony_di->get(OrderReturnStateDataProvider::class), + $symfony_di->get(ContextEmployeeProvider::class) + )]; + $container['eshop.language'] = Eshop\Language::class; + $container['eshop.multistore'] = ['factory' => Eshop\MultiStore::class, 'factory_method' => fn () => new Eshop\MultiStore( + $symfony_di->get('prestashop.adapter.shop.context') + )]; + + // Event loaders + $container['event.loader.extension'] = ['factory' => Event\Loader\Extension::class, 'auto_wiring' => false]; + $container['event.loader.shop'] = ['factory' => Event\Loader\Shop::class, 'auto_wiring' => false]; + $container['event.loader.order'] = ['factory' => Event\Loader\Order::class, 'auto_wiring' => false]; + $container['event.loader.order_status'] = ['factory' => Event\Loader\OrderStatus::class, 'auto_wiring' => false]; + $container['event.loader.customer'] = ['factory' => Event\Loader\Customer::class, 'auto_wiring' => false]; + $container['event.loader.product'] = ['factory' => Event\Loader\Product::class, 'auto_wiring' => false]; + $container['event.loader.post'] = ['factory' => Event\Loader\Post::class, 'auto_wiring' => false]; + $container['event.loader'] = ['factory' => Plugin\Event\Loader::class, 'factory_method' => fn () => new Plugin\Event\Loader([ + $container->getByClass(Event\Loader\Order::class), + $container->getByClass(Event\Loader\OrderStatus::class), + $container->getByClass(Event\Loader\Customer::class), + $container->getByClass(Event\Loader\Shop::class), + $container->getByClass(Event\Loader\Product::class), + $container->getByClass(Event\Loader\Post::class), + $container->getByClass(Event\Loader\Extension::class), + ])]; + + // Event + $container['event.hook'] = ['factory' => Plugin\Event\Hook::class, 'parameters' => ['version' => $parameters['api_version'] ?? '1.0']]; + $container['event.asynchronous.repository'] = Plugin\Event\Repository\AsynchronousDatabase::class; + $container['event.asynchronous'] = Plugin\Event\Asynchronous::class; + + // IO + $container['io.connection.factory'] = ['factory' => Plugin\IO\ConnectionFactory::class, 'factory_method' => function () use ($container): Plugin\IO\ConnectionFactory { + $configuration = $container->getByClass(Eshop\Configuration::class); + + return new Plugin\IO\ConnectionFactory($configuration->url(), $configuration->product(), $container->getByClass(Plugin\Settings\Settings::class)); + }]; + $container['io.connection'] = ['factory' => Plugin\IO\Connection::class, 'factory_method' => fn () => $container->getByClass(Plugin\IO\ConnectionFactory::class)->create()]; + $container['io.url'] = ['factory' => Plugin\IO\Url::class, 'parameters' => ['url' => $parameters['gate_url'] ?? 'https://portal.bulkgate.com']]; + + // Localization + $iso = $container->getByClass(Eshop\Language::class)->get($symfony_di->get('prestashop.adapter.legacy.context')->getLanguage()->getId()); // pozor! bezi v ruznych kontextech FO a BO + $container['localization.language'] = ['factory' => Plugin\Localization\LanguageSettings::class, 'parameters' => ['iso' => $iso]]; + $container['localization.translator'] = Plugin\Localization\TranslatorSettings::class; + $container['localization.formatter'] = extension_loaded('intl') ? ['factory' => Plugin\Localization\FormatterIntl::class, 'factory_method' => fn () => new Plugin\Localization\FormatterIntl($iso)] : Plugin\Localization\FormatterBasic::class; + + // Settings + $container['settings.repository.database'] = Plugin\Settings\Repository\SettingsDatabase::class; + $container['settings.repository.synchronizer'] = Plugin\Settings\Repository\SynchronizationDatabase::class; + $container['settings.settings'] = ['factory' => Plugin\Settings\Settings::class, 'factory_method' => function () use ($container, $parameters): Plugin\Settings\Settings { + $settings = new Plugin\Settings\Settings($container->getByClass(Plugin\Settings\Repository\SettingsDatabase::class)); + $settings->setDefaultSettings($parameters['default_settings']); + + return $settings; + }]; + $container['settings.synchronizer'] = Plugin\Settings\Synchronizer::class; + + // User + $container['user.sign'] = ['factory' => Plugin\User\Sign::class, 'factory_method' => function () use ($container, $parameters): Plugin\User\Sign { + $sign = new Plugin\User\Sign( + $container->getByClass(Plugin\Settings\Settings::class), + $container->getByClass(Plugin\IO\Connection::class), + $container->getByClass(Plugin\IO\Url::class), + $container->getByClass(Eshop\Configuration::class), + $container->getByClass(Plugin\Localization\Language::class), + $container->getByClass(Plugin\Debug\Logger::class), + ); + $sign->setDefaultParameters(['referer_id' => $parameters['referer_id'] ?? null]); + + return $sign; + }]; + + return $container; + } +} diff --git a/src/Database/Connection.php b/src/Database/Connection.php new file mode 100644 index 0000000..b5e31cf --- /dev/null +++ b/src/Database/Connection.php @@ -0,0 +1,116 @@ + + */ + private array $prepare_parameters = []; + + /** + * @var list + */ + private array $sql = []; + + public function __construct(DBAL\Connection $db) + { + $this->db = $db; + } + + public function execute(string $sql): ?ResultCollection + { + $output = new ResultCollection(); + + $this->sql[] = $sql; + + $query = $this->db->executeQuery($sql, $this->prepare_parameters); + + $result = \method_exists($query, 'fetchAllAssoc') ? $query->fetchAllAssoc() : $query->fetchAll(\PDO::FETCH_ASSOC); + + $this->prepare_parameters = []; + + foreach ($result as $key => $item) { + $output[$key] = (array) $item; + } + + return $output; + } + + public function lastId() + { + /** + * @var mixed $id + */ + $id = $this->db->lastInsertId(); + + if (!is_string($id) && !is_int($id)) { + return 0; + } + + return $id; + } + + public function prefix(): string + { + /** + * @var literal-string $prefix + */ + $prefix = _DB_PREFIX_; + + return $prefix; + } + + public function getSqlList(): array + { + return $this->sql; + } + + public function table(string $table): string + { + return $this->prefix() . $table; + } + + /** + * @param mixed ...$parameters + */ + public function prepare(string $sql, ...$parameters): string + { + $this->prepare_parameters = $parameters; + + // plugin's SQL queries are using %s for placeholder values, but Doctrine uses "?" character as placeholder + /** + * @var literal-string $s + */ + $s = str_replace('%s', '?', $sql); + + return $s; + } + + public function escape(string $string): string + { + /** + * @var literal-string $s + */ + $s = (string) $this->db->quote($string); + + return $s; + } +} diff --git a/src/Eshop/Configuration.php b/src/Eshop/Configuration.php new file mode 100644 index 0000000..a7323f3 --- /dev/null +++ b/src/Eshop/Configuration.php @@ -0,0 +1,52 @@ +version_number = $version_number; + $this->site_url = $url_provider->getUrl(); + $this->site_name = $shop->getShopName(); + } + + public function url(): string + { + return $this->site_url; + } + + public function product(): string + { + return 'ps'; + } + + public function version(): string + { + return $this->version_number; + } + + public function name(): string + { + return $this->site_name; + } +} diff --git a/src/Eshop/Language.php b/src/Eshop/Language.php new file mode 100644 index 0000000..2b5f026 --- /dev/null +++ b/src/Eshop/Language.php @@ -0,0 +1,51 @@ + $iso, 'name' => $name]) { + $output[$iso] = $name; + } + + return $output; + } + + public function get(?int $id = null): string + { + if ($id === null) { + return 'en'; + } + + /** @var mixed $iso */ + $iso = PrestaShopLanguage::getIsoById($id); + + if (\is_string($iso)) { + return $iso; + } + + return 'en'; + } + + public function hasMultiLanguageSupport(): bool + { + return true; + } +} diff --git a/src/Eshop/MultiStore.php b/src/Eshop/MultiStore.php new file mode 100644 index 0000000..76fd496 --- /dev/null +++ b/src/Eshop/MultiStore.php @@ -0,0 +1,40 @@ +multistore = $multistore; + } + + public function load(): array + { + $output = []; + + /* + * @phpstan-ignore-next-line + */ + foreach ($this->multistore->getShops() as ['id_shop' => $id, 'name' => $name]) { + $output[$id] = $name; + } + + return $output; + } +} diff --git a/src/Eshop/OrderStatus.php b/src/Eshop/OrderStatus.php new file mode 100644 index 0000000..8340a71 --- /dev/null +++ b/src/Eshop/OrderStatus.php @@ -0,0 +1,42 @@ +order_state = $order_state; + $this->employee = $employee; + } + + public function load(): array + { + $output = []; + $list = $this->order_state->getOrderStates($this->employee->getLanguageId()); + + foreach ($list as ['id_order_state' => $state_id, 'name' => $name]) { + $output[$state_id] = $name; + } + + return $output; + } +} diff --git a/src/Eshop/ReturnStatus.php b/src/Eshop/ReturnStatus.php new file mode 100644 index 0000000..03f5b89 --- /dev/null +++ b/src/Eshop/ReturnStatus.php @@ -0,0 +1,42 @@ +order_state = $order_state; + $this->employee = $employee; + } + + public function load(): array + { + $output = []; + $list = $this->order_state->getOrderReturnStates($this->employee->getLanguageId()); + + foreach ($list as ['id_order_return_state' => $state_id, 'name' => $name]) { + $output[$state_id] = $name; + } + + return $output; + } +} diff --git a/src/Event/Helpers.php b/src/Event/Helpers.php new file mode 100644 index 0000000..f256c5b --- /dev/null +++ b/src/Event/Helpers.php @@ -0,0 +1,35 @@ + $priority + * @param \ArrayAccess $values + * @param mixed $default + * + * @return mixed + */ + public static function priorityValues(array $priority, \ArrayAccess $values, $default = null) + { + foreach ($priority as $key) { + if (isset($values[$key])) { + return $values[$key]; + } + } + + return $default; + } +} diff --git a/src/Event/Loader/Customer.php b/src/Event/Loader/Customer.php new file mode 100644 index 0000000..dcf889c --- /dev/null +++ b/src/Event/Loader/Customer.php @@ -0,0 +1,77 @@ +firstname; + $variables['customer_lastname'] = $customer->lastname; + $variables['customer_email'] = $customer->email; + + $address_id = (int) \Address::getFirstCustomerAddressId($customer->id); + $id_address_delivery = $variables['id_address_delivery'] ?? null; + $id_address_invoice = $variables['id_address_invoice'] ?? null; + + if ($address_id) { + if ($id_address_delivery && $id_address_delivery !== $address_id) { + // shipping address has precedence + $this->address($variables, new \Address((int) $id_address_delivery, (int) $variables['lang_id'])); + } else { + $this->address($variables, new \Address((int) $address_id, (int) $variables['lang_id'])); + } + } + + if ($id_address_invoice) { + $this->address($variables, new \Address((int) $id_address_invoice, (int) $variables['lang_id']), true); + } + } + + private function address(Variables $variables, \Address $address, bool $invoice = false): void + { + $prefix = $invoice ? 'customer_invoice' : 'customer'; + + $variables["{$prefix}_firstname"] = $address->firstname; + $variables["{$prefix}_lastname"] = $address->lastname; + $variables["{$prefix}_country_id"] = Strings::lower((string) \Country::getIsoById($address->id_country)); + $variables["{$prefix}_company"] = $address->company; + $variables["{$prefix}_phone"] = $address->phone; + $variables["{$prefix}_mobile"] = $address->phone_mobile; + $variables["{$prefix}_address"] = Helpers::joinStreet('address1', 'address2', ['address1' => $address->address1, 'address2' => $address->address2], []); + $variables["{$prefix}_postcode"] = $address->postcode; + $variables["{$prefix}_city"] = $address->city; + $variables["{$prefix}_country"] = $address->country; + $variables["{$prefix}_vat_number"] = $address->vat_number; + } +} diff --git a/src/Event/Loader/Extension.php b/src/Event/Loader/Extension.php new file mode 100644 index 0000000..d4cde2b --- /dev/null +++ b/src/Event/Loader/Extension.php @@ -0,0 +1,35 @@ +database = $database; + } + + public function load(Variables $variables, array $parameters = []): void + { + \Hook::exec('actionPrestaSmsExtendsVariables', [ + 'variables' => $variables, + 'database' => $this->database, + ], null, false, true, false, (int) $variables['shop_id']); + } +} diff --git a/src/Event/Loader/Order.php b/src/Event/Loader/Order.php new file mode 100644 index 0000000..6d9acc7 --- /dev/null +++ b/src/Event/Loader/Order.php @@ -0,0 +1,154 @@ +formatter = $formatter; + } + + public function load(Variables $variables, array $parameters = []): void + { + if (!isset($variables['order_id'])) { + return; + } + + $order = isset($parameters['order']) && $parameters['order'] instanceof \Order ? $parameters['order'] : new \Order((int) $variables['order_id']); + + $currency = (array) \Currency::getCurrency($order->id_currency); + + $variables['id_address_delivery'] = (int) $order->id_address_delivery; + $variables['id_address_invoice'] = (int) $order->id_address_invoice; + + $variables['long_order_id'] = \sprintf('%06d', $variables['order_id']); + $variables['cart_id'] = (int) $order->id_cart; + $variables['carrier_id'] = (int) $order->id_carrier; + $variables['order_payment'] = $order->payment; + $variables['order_currency'] = $currency['iso_code'] ?? null; + $variables['order_total_paid'] = $this->formatter->format('number', $order->total_paid); + $variables['order_total_locale'] = $this->formatter->format('price', $order->total_paid, $variables['order_currency']); + $variables['order_reference'] = $order->reference; + + $variables['order_datetime'] = $this->formatter->format('datetime', $order->date_add); + $variables['order_date'] = $this->formatter->format('date', $order->date_add); + $date = new \DateTime($order->date_add); + $variables['order_date1'] = $date->format('d.m.Y'); + $variables['order_date2'] = $date->format('d/m/Y'); + $variables['order_date3'] = $date->format('d-m-Y'); + $variables['order_date4'] = $date->format('Y-m-d'); + $variables['order_date5'] = $date->format('m.d.Y'); + $variables['order_date6'] = $date->format('m/d/Y'); + $variables['order_date7'] = $date->format('m-d-Y'); + $variables['order_time'] = $this->formatter->format('time', $order->date_add); + $variables['order_time1'] = $date->format('H:i'); + + if ($variables['carrier_id']) { + $carrier = new \Carrier((int) $variables['carrier_id'], (int) $variables['lang_id']); + $order_carrier = new \OrderCarrier((int) $variables['order_id'], (int) $variables['lang_id']); + $variables['order_carrier_name'] = $carrier->name; + $variables['order_carrier_url'] = str_replace('@', $order_carrier->tracking_number, $carrier->url); + $variables['order_carrier_delay'] = $carrier->delay; + $variables['order_carrier_tracking_number'] = $order_carrier->tracking_number; + $variables['order_carrier_tracking_date'] = $this->formatter->format('datetime', $order_carrier->date_add); + $variables['order_carrier_price'] = $this->formatter->format('number', $order_carrier->shipping_cost_tax_incl); + $variables['order_carrier_weight'] = $this->formatter->format('number', $order_carrier->weight); + $variables['order_carrier_price_locale'] = $this->formatter->format('price', $order_carrier->shipping_cost_tax_incl, $variables['order_currency']); + } + + $message = \Message::getMessagesByOrderId((int) $variables['order_id']); + + if (isset($message['message'])) { + $variables['order_message'] = $message['message']; + } + + $this->products($variables); + + if (isset($variables['return_id'])) { + $this->returnProducts($variables, $order); + } + } + + private function products(Variables $variables): void + { + $p1 = $p2 = $p3 = $p4 = $pr1 = $pr2 = $pr3 = $pr4 = []; + + $list = \OrderDetail::getList((int) $variables['order_id']); + + $filter = $variables['filter_products'] ?? []; + + foreach ($list as $row) { + if (empty($filter) || in_array((int) $row['id_order_detail'], (array) $filter)) { + $p1[] = $row['product_quantity'] . 'x ' . $row['product_name'] . ' ' . $row['product_reference']; + $p2[] = $row['product_quantity'] . 'x ' . $row['product_name']; + $p3[] = $row['product_quantity'] . 'x (' . $row['product_id'] . ')' . $row['product_name'] . ' ' . $row['product_reference']; + $p4[] = $row['product_quantity'] . 'x ' . $row['product_reference']; + + $price = $this->formatter->format('price', $row['product_price'], $variables['order_currency']); + + $pr1[] = $row['product_quantity'] . ',' . $row['product_name'] . ',' . $price; + $pr2[] = $row['product_quantity'] . ';' . $row['product_name'] . ';' . $price; + $pr3[] = $row['product_quantity'] . ',' . $row['product_reference'] . ',' . $price; + $pr4[] = $row['product_quantity'] . ';' . $row['product_reference'] . ';' . $price; + } + } + + $variables['order_products1'] = implode('; ', $p1); + $variables['order_products2'] = implode('; ', $p2); + $variables['order_products3'] = implode('; ', $p3); + $variables['order_products4'] = implode('; ', $p4); + + $variables['order_products5'] = implode("\n", $p1); + $variables['order_products6'] = implode("\n", $p2); + $variables['order_products7'] = implode("\n", $p3); + $variables['order_products8'] = implode("\n", $p4); + + $variables['order_smsprinter1'] = implode(';', $pr1); + $variables['order_smsprinter2'] = implode(';', $pr2); + $variables['order_smsprinter3'] = implode(';', $pr3); + $variables['order_smsprinter4'] = implode(';', $pr4); + } + + private function returnProducts(Variables $variables, \Order $order): void + { + $return = new \OrderReturn((int) $variables['return_id'], (int) $variables['lang_id'], (int) $order->id_shop); + $return_detail = \OrderReturn::getOrdersReturnProducts($return->id, $order); + + $p1 = $p2 = $p3 = $p4 = []; + + foreach ($return_detail as $row) { + $p1[] = $row['product_quantity'] . 'x ' . $row['product_name'] . ' ' . $row['product_reference']; + $p2[] = $row['product_quantity'] . 'x ' . $row['product_name']; + $p3[] = $row['product_quantity'] . 'x (' . $row['product_id'] . ')' . $row['product_name'] . ' ' . $row['product_reference']; + $p4[] = $row['product_quantity'] . 'x ' . $row['product_reference']; + } + + $variables['return_question'] = $return->question; + $variables['return_products1'] = implode('; ', $p1); + $variables['return_products2'] = implode('; ', $p2); + $variables['return_products3'] = implode('; ', $p3); + $variables['return_products4'] = implode('; ', $p4); + + $variables['return_products5'] = implode("\n", $p1); + $variables['return_products6'] = implode("\n", $p2); + $variables['return_products7'] = implode("\n", $p3); + $variables['return_products8'] = implode("\n", $p4); + } +} diff --git a/src/Event/Loader/OrderStatus.php b/src/Event/Loader/OrderStatus.php new file mode 100644 index 0000000..13a0ed9 --- /dev/null +++ b/src/Event/Loader/OrderStatus.php @@ -0,0 +1,29 @@ +name; + } +} diff --git a/src/Event/Loader/Post.php b/src/Event/Loader/Post.php new file mode 100644 index 0000000..50483f8 --- /dev/null +++ b/src/Event/Loader/Post.php @@ -0,0 +1,48 @@ + 'customer_firstname', + 'last_name' => 'customer_lastname', + 'phone' => 'customer_phone', + 'mobile' => 'customer_mobile', + 'phone_number' => 'customer_phone', + 'phone_mobile' => 'customer_mobile', + 'email' => 'customer_email', + + 'shipping_first_name' => 'customer_firstname', + 'shipping_last_name' => 'customer_lastname', + 'shipping_phone' => 'customer_phone', + 'shipping_company' => 'customer_company', + 'shipping_country' => 'customer_country', + + 'billing_first_name' => 'customer_firstname', + 'billing_last_name' => 'customer_lastname', + 'billing_phone' => 'customer_mobile', + 'billing_company' => 'customer_company', + 'billing_country' => 'customer_country', + 'bulkgate_marketing_message_opt_in' => 'marketing_message_opt_in', + ]; + + public function load(Variables $variables, array $parameters = []): void + { + foreach (self::Mapper as $key => $value) { + if (isset($_POST[$key])) { + $variables[$value] = \Tools::getValue($key); + } + } + } +} diff --git a/src/Event/Loader/Product.php b/src/Event/Loader/Product.php new file mode 100644 index 0000000..ff609dd --- /dev/null +++ b/src/Event/Loader/Product.php @@ -0,0 +1,51 @@ +formatter = $formatter; + } + + public function load(Variables $variables, array $parameters = []): void + { + if (!isset($variables['product_id'])) { + return; + } + + $product = isset($parameters['product']) && $parameters['product'] instanceof \Product ? $parameters['product'] : new \Product((int) $variables['product_id'], false, null, (int) $variables['shop_id']); + + $variables['product_name'] = \Product::getProductName((int) $product->id); + $variables['product_description'] = \strip_tags(\is_array($product->description_short) ? $product->description_short[$variables['lang_id']] : $product->description); + $variables['product_manufacturer'] = $product->manufacturer_name; + $variables['product_price'] = $this->formatter->format('number', $product->price); + $variables['product_price_locale'] = $this->formatter->format('price', $product->price, $variables['shop_currency']); + $variables['product_quantity'] = \Product::getQuantity((int) $product->id); + $variables['product_minimal_quantity'] = (int) $product->minimal_quantity; + $variables['product_ref'] = $product->reference; + $variables['product_supplier'] = \Supplier::getNameById($product->id_supplier) ?: null; + $variables['product_supplier_ref'] = $product->supplier_reference; + $variables['product_supplier_id'] = $product->id_supplier; + $variables['product_ean13'] = $product->ean13; + $variables['product_upc'] = $product->upc; + $variables['product_isbn'] = $product->isbn; + } +} diff --git a/src/Event/Loader/Shop.php b/src/Event/Loader/Shop.php new file mode 100644 index 0000000..46d9d63 --- /dev/null +++ b/src/Event/Loader/Shop.php @@ -0,0 +1,44 @@ +language = $language; + } + + public function load(Variables $variables, array $parameters = []): void + { + if (!isset($variables['shop_id'])) { + return; + } + + $shop = new \Shop((int) $variables['shop_id']); + + $variables['shop_email'] = \Configuration::get('PS_SHOP_EMAIL', null, null, $shop->id) ?: null; + $variables['shop_phone'] = \Configuration::get('PS_SHOP_PHONE', null, null, $shop->id) ?: null; + $variables['shop_currency'] = \Currency::getIsoCodeById((int) \Configuration::get('PS_CURRENCY_DEFAULT', null, null, $shop->id)); + $variables['shop_name'] = $shop->name; + $variables['shop_domain'] = $shop->getBaseURL(); + $variables['lang_id'] ??= \Configuration::get('PS_LANG_DEFAULT', null, null, $shop->id); // $shop->getAssociatedLanguage()->getId(); + $variables['lang_iso'] = $this->language->get((int) $variables['lang_id']); + } +} diff --git a/templates/.htaccess b/templates/.htaccess deleted file mode 100644 index 22b9ed2..0000000 --- a/templates/.htaccess +++ /dev/null @@ -1,2 +0,0 @@ -Order Allow,Deny -Deny from all \ No newline at end of file diff --git a/templates/base.tpl b/templates/base.tpl deleted file mode 100644 index 50d68fb..0000000 --- a/templates/base.tpl +++ /dev/null @@ -1,76 +0,0 @@ -
- -
- -
-
-
-
-

{'Loading content'|prestaSmsTranslate}

-
-
- - - - -
- \ No newline at end of file diff --git a/templates/panel.tpl b/templates/panel.tpl deleted file mode 100644 index c35eeff..0000000 --- a/templates/panel.tpl +++ /dev/null @@ -1,54 +0,0 @@ -
-
- PrestaSMS -
-
-
-
-

{'Loading content'|prestaSmsTranslate}

-
- - - -
-
\ No newline at end of file diff --git a/tests/Ajax/AuthenticateTest.phpt b/tests/Ajax/AuthenticateTest.phpt new file mode 100644 index 0000000..52f6757 --- /dev/null +++ b/tests/Ajax/AuthenticateTest.phpt @@ -0,0 +1,58 @@ +shouldReceive('load')->with('static:application_token')->once()->andReturn('token'); + $response->shouldReceive('send')->with(['token' => 'jwt'])->once(); + + $sign->shouldReceive('authenticate')->once()->andReturn('jwt'); + + $authenticate->run('redirect'); + + Assert::true(true); + } + + + public function testGuest(): void + { + $authenticate = new Authenticate( + $settings = Mockery::mock(Settings::class), + Mockery::mock(Sign::class) + ); + $response = Mockery::mock('overload:' . JsonResponse::class); + $settings->shouldReceive('load')->with('static:application_token')->once()->andReturnNull(); + $response->shouldReceive('send')->with(['redirect' => 'invalid_redirect'])->once(); + + $authenticate->run('invalid_redirect'); + + Assert::true(true); + } + + + public function tearDown(): void + { + Mockery::close(); + } +} + +(new AuthenticateTest())->run(); \ No newline at end of file diff --git a/tests/Ajax/PluginSettingsChangeTest.phpt b/tests/Ajax/PluginSettingsChangeTest.phpt new file mode 100644 index 0000000..5b231f3 --- /dev/null +++ b/tests/Ajax/PluginSettingsChangeTest.phpt @@ -0,0 +1,102 @@ +shouldReceive('load')->with('main:language')->once()->andReturn('en'); + $settings->shouldReceive('set')->with('main:dispatcher', 'cron', ['type' => 'string'])->once(); + $settings->shouldReceive('set')->with('main:synchronization', 'all', ['type' => 'string'])->once(); + $settings->shouldReceive('set')->with('main:language', 'en', ['type' => 'string'])->once(); + $settings->shouldReceive('set')->with('main:language_mutation', false, ['type' => 'bool'])->once(); + $settings->shouldReceive('set')->with('main:delete_db', false, ['type' => 'bool'])->once(); + $settings->shouldReceive('set')->with('main:address_preference', 'invoice', ['type' => 'string'])->once(); + $settings->shouldReceive('set')->with('main:marketing_message_opt_in_enabled', true, ['type' => 'bool'])->once(); + $settings->shouldReceive('set')->with('main:marketing_message_opt_in_label', 'xxx', ['type' => 'string'])->once(); + $settings->shouldReceive('set')->with('main:marketing_message_opt_in_default', true, ['type' => 'bool'])->once(); + $settings->shouldReceive('set')->with('main:marketing_message_opt_in_url', 'https://xxx', ['type' => 'string'])->once(); + $synchronize->shouldReceive('synchronize')->with(true)->once(); + + Assert::same([ + 'data' => [ + 'layout' => [ + 'server' => [ + 'application_settings' => [ + 'dispatcher' => 'cron', + 'synchronization' => 'all', + 'language' => 'en', + 'language_mutation' => false, + 'delete_db' => false, + 'address_preference' => 'invoice', + 'marketing_message_opt_in_enabled' => true, + 'marketing_message_opt_in_label' => 'xxx', + 'marketing_message_opt_in_default' => true, + 'marketing_message_opt_in_url' => 'https://xxx', + ], + ], + ], + ], + 'success' => ['saved'], + ], $plugin_settings->run([ + 'dispatcher' => 'cron', + 'synchronization' => 'all', + 'language' => 'en', + 'language_mutation' => 0, + 'delete_db' => 0, + 'address_preference' => 'invoice', + 'marketing_message_opt_in_enabled' => true, + 'marketing_message_opt_in_label' => 'xxx', + 'marketing_message_opt_in_default' => 'true', + 'marketing_message_opt_in_url' => 'xxx', + 'invalid' => 'xxx', + ], fn () => 'eshop.com')); + } + + + public function testLanguageRedirect(): void + { + $plugin_settings = new PluginSettingsChange($settings = Mockery::mock(Settings::class), $synchronize = Mockery::mock(Synchronizer::class)); + + $settings->shouldReceive('load')->with('main:language')->once()->andReturn('en'); + $settings->shouldReceive('set')->with('main:dispatcher', 'cron', ['type' => 'string'])->once(); + $settings->shouldReceive('set')->with('main:synchronization', 'all', ['type' => 'string'])->once(); + $settings->shouldReceive('set')->with('main:language', 'cs', ['type' => 'string'])->once(); + $settings->shouldReceive('set')->with('main:language_mutation', false, ['type' => 'bool'])->once(); + $settings->shouldReceive('set')->with('main:delete_db', false, ['type' => 'bool'])->once(); + $synchronize->shouldReceive('synchronize')->with(true)->once(); + + Assert::same([ + 'data' => ['redirect' => 'eshop.com'] + ], $plugin_settings->run([ + 'dispatcher' => 'cron', + 'synchronization' => 'all', + 'language' => 'cs', + 'language_mutation' => 0, + 'delete_db' => 0, + 'invalid' => 'xxx' + ], fn () => 'eshop.com')); + } + + + public function tearDown(): void + { + Mockery::close(); + } +} + +(new PluginSettingsChangeTest())->run(); \ No newline at end of file diff --git a/tests/Database/ConnectionTest.phpt b/tests/Database/ConnectionTest.phpt new file mode 100644 index 0000000..8e8af1f --- /dev/null +++ b/tests/Database/ConnectionTest.phpt @@ -0,0 +1,73 @@ +shouldReceive('executeQuery')->with('SELECT * FROM test WHERE id = ?', [451])->once()->andReturn($result = Mockery::mock(Result::class)); + $result->shouldReceive('fetchAll')->with(2)->once()->andReturn([['id' => 1, 'name' => 'test1'], ['id' => 2, 'name' => 'test2']]); + + $collection = $connection->execute($connection->prepare('SELECT * FROM test WHERE id = %s', 451)); + + Assert::same(['id' => 1, 'name' => 'test1'], $collection[0]->toArray()); + Assert::same(['id' => 2, 'name' => 'test2'], $collection[1]->toArray()); + + Assert::same(['SELECT * FROM test WHERE id = ?'], $connection->getSqlList()); + } + + + public function testTableAndPrefix(): void + { + define('_DB_PREFIX_', 'ps_'); + $connection = new Connection(Mockery::mock(DBALConnection::class)); + + Assert::same('ps_xxx', $connection->table('xxx')); + + Assert::same('ps_', $connection->prefix()); + } + + + public function testEscape(): void + { + $connection = new Connection($doctrine = Mockery::mock(DBALConnection::class)); + $doctrine->shouldReceive('quote')->with('test')->once()->andReturn('ok'); + + Assert::same('ok', $connection->escape('test')); + } + + + public function testLastId(): void + { + $connection = new Connection($doctrine = Mockery::mock(DBALConnection::class)); + $doctrine->shouldReceive('lastInsertId')->withNoArgs()->once()->andReturn(5); + $doctrine->shouldReceive('lastInsertId')->withNoArgs()->once()->andReturn('ok'); + $doctrine->shouldReceive('lastInsertId')->withNoArgs()->once()->andReturn([]); + + Assert::same(5, $connection->lastId()); + Assert::same('ok', $connection->lastId()); + Assert::same(0, $connection->lastId()); + } + + public function tearDown(): void + { + Mockery::close(); + } +} + +(new ConnectionTest())->run(); + diff --git a/tests/Eshop/ConfigurationTest.phpt b/tests/Eshop/ConfigurationTest.phpt new file mode 100644 index 0000000..403e524 --- /dev/null +++ b/tests/Eshop/ConfigurationTest.phpt @@ -0,0 +1,41 @@ +shouldReceive('getUrl')->once()->andReturn('https://eshop.cz/'); + $shop->shouldReceive('getShopName')->once()->andReturn('Můj Eshop'); + + $config = new Configuration('8.1.0', $url_provider, $shop); + + Assert::same('https://eshop.cz/', $config->url()); + Assert::same('ps', $config->product()); + Assert::same('8.1.0', $config->version()); + Assert::same('Můj Eshop', $config->name()); + } + + public function tearDown(): void + { + Mockery::close(); + } +} + +(new ConfigurationTest())->run(); + diff --git a/tests/Eshop/ExtensionTest.phpt b/tests/Eshop/ExtensionTest.phpt new file mode 100644 index 0000000..8503ceb --- /dev/null +++ b/tests/Eshop/ExtensionTest.phpt @@ -0,0 +1,42 @@ +shouldReceive('exec')->with('actionPrestaSmsExtendsVariables', [ + 'variables' => $variables = new Variables(['shop_id' => 451]), + 'database' => $connection = Mockery::mock(Connection::class), + ], null, false, true, false, 451)->once()->andReturnNull(); + + $loader = new Extension($connection); + + $loader->load($variables); + + Assert::same(['shop_id' => 451], $variables->toArray()); + } + + public function tearDown(): void + { + Mockery::close(); + } +} + +(new ExtensionTest())->run(); + diff --git a/tests/Eshop/LanguageTest.phpt b/tests/Eshop/LanguageTest.phpt new file mode 100644 index 0000000..9c309c2 --- /dev/null +++ b/tests/Eshop/LanguageTest.phpt @@ -0,0 +1,60 @@ +shouldReceive('getLanguages')->once()->andReturn([ + ['iso_code' => 'cs', 'name' => 'Čeština'], + ['iso_code' => 'en', 'name' => 'English'], + ]); + + $language = new Language(); + + Assert::same([ + 'cs' => 'Čeština', + 'en' => 'English', + ], $language->load()); + } + + public function testGet(): void + { + $psLanguage = Mockery::mock('alias:Language'); + $psLanguage->shouldReceive('getIsoById')->with(8)->andReturn('cs'); + $psLanguage->shouldReceive('getIsoById')->with(99)->andReturn(null); + + $language = new Language(); + Assert::same('cs', $language->get(8)); + Assert::same('en', $language->get(99)); + Assert::same('en', $language->get(null)); + } + + public function testHasMultiLanguageSupport(): void + { + $language = new Language(); + + Assert::true($language->hasMultiLanguageSupport()); + } + + + public function tearDown(): void + { + Mockery::close(); + } +} + +(new LanguageTest())->run(); diff --git a/tests/Eshop/MultiStoreTest.phpt b/tests/Eshop/MultiStoreTest.phpt new file mode 100644 index 0000000..3174972 --- /dev/null +++ b/tests/Eshop/MultiStoreTest.phpt @@ -0,0 +1,41 @@ +shouldReceive('getShops')->once()->andReturn([ + ['id_shop' => 1, 'name' => 'Shop 1'], + ['id_shop' => 2, 'name' => 'Shop 2'], + ]); + + Assert::same([ + 1 => 'Shop 1', + 2 => 'Shop 2', + ], $multiStore->load()); + } + + + public function tearDown(): void + { + Mockery::close(); + } +} + +(new MultiStoreTest())->run(); + diff --git a/tests/Eshop/OrderStatusTest.phpt b/tests/Eshop/OrderStatusTest.phpt new file mode 100644 index 0000000..812b922 --- /dev/null +++ b/tests/Eshop/OrderStatusTest.phpt @@ -0,0 +1,41 @@ +shouldReceive('getLanguageId')->once()->andReturn(8); + $order_state->shouldReceive('getOrderStates')->with(8)->once()->andReturn([ + ['id_order_state' => 1, 'name' => 'Nová objednávka'], + ['id_order_state' => 2, 'name' => 'Odesláno'], + ]); + + Assert::same([ + 1 => 'Nová objednávka', + 2 => 'Odesláno', + ], $orderStatus->load()); + } + + public function tearDown(): void + { + Mockery::close(); + } +} + +(new OrderStatusTest())->run(); + diff --git a/tests/Eshop/ReturnStatusTest.phpt b/tests/Eshop/ReturnStatusTest.phpt new file mode 100644 index 0000000..ec37a5d --- /dev/null +++ b/tests/Eshop/ReturnStatusTest.phpt @@ -0,0 +1,42 @@ +shouldReceive('getLanguageId')->once()->andReturn(8); + $return_state->shouldReceive('getOrderReturnStates')->with(8)->once()->andReturn([ + ['id_order_return_state' => 1, 'name' => 'Čeká na schválení'], + ['id_order_return_state' => 2, 'name' => 'Vyřízeno'], + ]); + + Assert::same([ + 1 => 'Čeká na schválení', + 2 => 'Vyřízeno', + ], $returnStatus->load()); + } + + + public function tearDown(): void + { + Mockery::close(); + } +} + +(new ReturnStatusTest())->run(); + diff --git a/tests/Event/HelpersTest.phpt b/tests/Event/HelpersTest.phpt new file mode 100644 index 0000000..dd73834 --- /dev/null +++ b/tests/Event/HelpersTest.phpt @@ -0,0 +1,29 @@ + 1, 'v2' => 2, 'v3' => 3]); + + Assert::same('test', Helpers::priorityValues([], $variables, 'test')); + Assert::same(3, Helpers::priorityValues(['v3'], $variables, 'test')); + Assert::same(1, Helpers::priorityValues(['v1', 'v3'], $variables, 'test')); + Assert::same(2, Helpers::priorityValues(['v2', 'v1', 'v3'], $variables, 'test')); + Assert::same(2, Helpers::priorityValues(['cc', 'v2', 'v1', 'v3'], $variables, 'test')); + } +} + +(new HelpersTest())->run(); diff --git a/tests/Event/Loader/CustomerTest.phpt b/tests/Event/Loader/CustomerTest.phpt new file mode 100644 index 0000000..0eddf8b --- /dev/null +++ b/tests/Event/Loader/CustomerTest.phpt @@ -0,0 +1,75 @@ +shouldReceive('__construct')->with(123)->set('id', 123)->set('firstname', 'Jan')->set('lastname', 'Novák')->set('email', 'jan.novak@example.com'); + + $address = Mockery::mock('overload:Address'); + $address->shouldReceive('getFirstCustomerAddressId')->with(123)->andReturn(10); + $address->shouldReceive('__construct')->with(10, 8)->set('firstname', 'Jan')->set('lastname', 'Novák')->set('id_country', 56)->set('company', 'Firma')->set('phone', '123456789')->set('phone_mobile', '987654321')->set('address1', 'Ulice 1')->set('address2', 'Patro 2')->set('postcode', '11000')->set('city', 'Praha')->set('country', 'Česká republika')->set('vat_number', 'CZ12345678'); + + $country = Mockery::mock('overload:Country'); + $country->shouldReceive('getIsoById')->with(56)->andReturn('CZ'); + + $helpers = Mockery::mock('alias:BulkGate\Plugin\Event\Helpers'); + $helpers->shouldReceive('joinStreet')->with('address1', 'address2', ['address1' => 'Ulice 1', 'address2' => 'Patro 2'], [])->andReturn('Ulice 1, Patro 2'); + + $variables = new Variables([ + 'customer_id' => 123, + 'lang_id' => 8, + ]); + + $loader = new Customer(); + $loader->load($variables); + + Assert::same([ + 'customer_id' => 123, + 'lang_id' => 8, + 'customer_firstname' => 'Jan', + 'customer_lastname' => 'Novák', + 'customer_email' => 'jan.novak@example.com', + 'customer_country_id' => 'cz', + 'customer_company' => 'Firma', + 'customer_phone' => '123456789', + 'customer_mobile' => '987654321', + 'customer_address' => 'Ulice 1, Patro 2', + 'customer_postcode' => '11000', + 'customer_city' => 'Praha', + 'customer_country' => 'Česká republika', + 'customer_vat_number' => 'CZ12345678', + ], $variables->toArray()); + } + + public function testNotCustomerId(): void + { + $loader = new Customer(); + $variables = new Variables(); + $loader->load($variables); + Assert::same([], $variables->toArray()); + } + + + public function tearDown(): void + { + Mockery::close(); + } +} + +(new CustomerTest())->run(); + diff --git a/tests/Event/Loader/OrderStatusTest.phpt b/tests/Event/Loader/OrderStatusTest.phpt new file mode 100644 index 0000000..a8cc5de --- /dev/null +++ b/tests/Event/Loader/OrderStatusTest.phpt @@ -0,0 +1,55 @@ +shouldReceive('__construct')->with(5, 8)->once()->set('name', 'Shipped'); + + $variables = new Variables([ + 'order_status_id' => 5, + 'lang_id' => 8, + ]); + + $loader = new OrderStatus(); + $loader->load($variables); + + Assert::same([ + 'order_status_id' => 5, + 'lang_id' => 8, + 'order_status' => 'Shipped', + ], $variables->toArray()); + } + + public function testNotOrderStatusId(): void + { + $variables = new Variables(['lang_id' => 8]); + $loader = new OrderStatus(); + $loader->load($variables); + + Assert::same(['lang_id' => 8], $variables->toArray()); + } + + + public function tearDown(): void + { + Mockery::close(); + } +} + +(new OrderStatusTest())->run(); + diff --git a/tests/Event/Loader/OrderTest.phpt b/tests/Event/Loader/OrderTest.phpt new file mode 100644 index 0000000..373554b --- /dev/null +++ b/tests/Event/Loader/OrderTest.phpt @@ -0,0 +1,247 @@ +shouldReceive('__construct')->with(123)->set('id', 123)->set('id_currency', 7)->set('id_address_delivery', 1)->set('id_address_invoice', 2)->set('id_cart', 10)->set('id_carrier', 20)->set('payment', 'Bankwire')->set('total_paid', 999.99)->set('reference', 'ORD123')->set('date_add', '2025-07-04 12:34:56')->set('id_shop', 451); + + $currency = Mockery::mock('overload:Currency'); + $currency->shouldReceive('getCurrency')->with(7)->andReturn(['iso_code' => 'CZK']); + + $formatter = Mockery::mock(Formatter::class); + $formatter->shouldReceive('format')->with('number', 999.99)->andReturn('999,99'); + $formatter->shouldReceive('format')->with('price', 999.99, 'CZK')->andReturn('999,99 CZK'); + $formatter->shouldReceive('format')->with('datetime', '2025-07-04 12:34:56')->andReturn('4.7.2025 12:34'); + $formatter->shouldReceive('format')->with('date', '2025-07-04 12:34:56')->andReturn('4.7.2025'); + $formatter->shouldReceive('format')->with('time', '2025-07-04 12:34:56')->andReturn('12:34'); + + $carrier = Mockery::mock('overload:Carrier'); + $carrier->shouldReceive('__construct')->with(20, 8)->set('name', 'PPL')->set('url', 'https://track/@')->set('delay', 'Next day'); + $order_carrier = Mockery::mock('overload:OrderCarrier'); + $order_carrier->shouldReceive('__construct')->with(123, 8)->set('tracking_number', 'TRACK123')->set('date_add', '2025-07-04 13:00:00')->set('shipping_cost_tax_incl', 100.0)->set('weight', 2.5); + $formatter->shouldReceive('format')->with('datetime', '2025-07-04 13:00:00')->andReturn('4.7.2025 13:00'); + $formatter->shouldReceive('format')->with('number', 100.0)->andReturn('100,00'); + $formatter->shouldReceive('format')->with('number', 2.5)->andReturn('2,50'); + $formatter->shouldReceive('format')->with('price', 100.0, 'CZK')->andReturn('100,00 CZK'); + + $order_detail = Mockery::mock('overload:OrderDetail'); + $order_detail->shouldReceive('getList')->with(123)->andReturn([ + [ + 'id_order_detail' => 1, + 'product_quantity' => 2, + 'product_name' => 'T-shirt', + 'product_reference' => 'TSHIRT', + 'product_id' => 101, + 'product_price' => 500.0, + ], + ]); + $formatter->shouldReceive('format')->with('price', 500.0, 'CZK')->andReturn('500,00 CZK'); + + $message = Mockery::mock('overload:Message'); + $message->shouldReceive('getMessagesByOrderId')->with(123)->andReturn(['message' => 'Děkujeme za objednávku']); + + $variables = new Variables([ + 'order_id' => 123, + 'lang_id' => 8, + ]); + + $loader = new Order($formatter); + $loader->load($variables); + + Assert::same([ + 'order_id' => 123, + 'lang_id' => 8, + 'id_address_delivery' => 1, + 'id_address_invoice' => 2, + 'long_order_id' => '000123', + 'cart_id' => 10, + 'carrier_id' => 20, + 'order_payment' => 'Bankwire', + 'order_currency' => 'CZK', + 'order_total_paid' => '999,99', + 'order_total_locale' => '999,99 CZK', + 'order_reference' => 'ORD123', + 'order_datetime' => '4.7.2025 12:34', + 'order_date' => '4.7.2025', + 'order_date1' => '04.07.2025', + 'order_date2' => '04/07/2025', + 'order_date3' => '04-07-2025', + 'order_date4' => '2025-07-04', + 'order_date5' => '07.04.2025', + 'order_date6' => '07/04/2025', + 'order_date7' => '07-04-2025', + 'order_time' => '12:34', + 'order_time1' => '12:34', + 'order_carrier_name' => 'PPL', + 'order_carrier_url' => 'https://track/TRACK123', + 'order_carrier_delay' => 'Next day', + 'order_carrier_tracking_number' => 'TRACK123', + 'order_carrier_tracking_date' => '4.7.2025 13:00', + 'order_carrier_price' => '100,00', + 'order_carrier_weight' => '2,50', + 'order_carrier_price_locale' => '100,00 CZK', + 'order_message' => 'Děkujeme za objednávku', + 'order_products1' => '2x T-shirt TSHIRT', + 'order_products2' => '2x T-shirt', + 'order_products3' => '2x (101)T-shirt TSHIRT', + 'order_products4' => '2x TSHIRT', + 'order_products5' => "2x T-shirt TSHIRT", + 'order_products6' => "2x T-shirt", + 'order_products7' => "2x (101)T-shirt TSHIRT", + 'order_products8' => "2x TSHIRT", + 'order_smsprinter1' => '2,T-shirt,500,00 CZK', + 'order_smsprinter2' => '2;T-shirt;500,00 CZK', + 'order_smsprinter3' => '2,TSHIRT,500,00 CZK', + 'order_smsprinter4' => '2;TSHIRT;500,00 CZK', + ], $variables->toArray()); + } + + public function testNotOrderId(): void + { + $formatter = Mockery::mock(Formatter::class); + $loader = new Order($formatter); + $loader->load($variables = new Variables()); + Assert::same([], $variables->toArray()); + } + + public function testLoadWithReturn(): void + { + $order = Mockery::mock('overload:Order'); + $order->shouldReceive('__construct')->with(123)->set('id', 123)->set('id_currency', 7)->set('id_address_delivery', 1)->set('id_address_invoice', 2)->set('id_cart', 10)->set('id_carrier', 20)->set('payment', 'Bankwire')->set('total_paid', 999.99)->set('reference', 'ORD123')->set('date_add', '2025-07-04 12:34:56')->set('id_shop', 451); + + $currency = Mockery::mock('overload:Currency'); + $currency->shouldReceive('getCurrency')->with(7)->andReturn(['iso_code' => 'CZK']); + + $formatter = Mockery::mock(Formatter::class); + $formatter->shouldReceive('format')->with('number', 999.99)->andReturn('999,99'); + $formatter->shouldReceive('format')->with('price', 999.99, 'CZK')->andReturn('999,99 CZK'); + $formatter->shouldReceive('format')->with('datetime', '2025-07-04 12:34:56')->andReturn('4.7.2025 12:34'); + $formatter->shouldReceive('format')->with('date', '2025-07-04 12:34:56')->andReturn('4.7.2025'); + $formatter->shouldReceive('format')->with('time', '2025-07-04 12:34:56')->andReturn('12:34'); + + $carrier = Mockery::mock('overload:Carrier'); + $carrier->shouldReceive('__construct')->with(20, 8)->set('name', 'PPL')->set('url', 'https://track/@')->set('delay', 'Next day'); + $order_carrier = Mockery::mock('overload:OrderCarrier'); + $order_carrier->shouldReceive('__construct')->with(123, 8)->set('tracking_number', 'TRACK123')->set('date_add', '2025-07-04 13:00:00')->set('shipping_cost_tax_incl', 100.0)->set('weight', 2.5); + $formatter->shouldReceive('format')->with('datetime', '2025-07-04 13:00:00')->andReturn('4.7.2025 13:00'); + $formatter->shouldReceive('format')->with('number', 100.0)->andReturn('100,00'); + $formatter->shouldReceive('format')->with('number', 2.5)->andReturn('2,50'); + $formatter->shouldReceive('format')->with('price', 100.0, 'CZK')->andReturn('100,00 CZK'); + + $order_detail = Mockery::mock('overload:OrderDetail'); + $order_detail->shouldReceive('getList')->with(123)->andReturn([ + [ + 'id_order_detail' => 1, + 'product_quantity' => 2, + 'product_name' => 'T-shirt', + 'product_reference' => 'TSHIRT', + 'product_id' => 101, + 'product_price' => 500.0, + ], + ]); + $formatter->shouldReceive('format')->with('price', 500.0, 'CZK')->andReturn('500,00 CZK'); + + $message = Mockery::mock('overload:Message'); + $message->shouldReceive('getMessagesByOrderId')->with(123)->andReturn(['message' => 'Děkujeme za objednávku']); + + $order_return = Mockery::mock('overload:OrderReturn'); + $order_return->shouldReceive('__construct')->with(55, 8, 451)->set('id', 55)->set('question', 'Chci vrátit zboží'); + $order_return->shouldReceive('getOrdersReturnProducts')->with(55, Mockery::type('object'))->andReturn([ + [ + 'product_quantity' => 1, + 'product_name' => 'T-shirt', + 'product_reference' => 'TSHIRT', + 'product_id' => 101, + ], + ]); + + $variables = new Variables([ + 'order_id' => 123, + 'lang_id' => 8, + 'return_id' => 55, + ]); + + $loader = new Order($formatter); + $loader->load($variables); + + Assert::same([ + 'order_id' => 123, + 'lang_id' => 8, + 'return_id' => 55, + 'id_address_delivery' => 1, + 'id_address_invoice' => 2, + 'long_order_id' => '000123', + 'cart_id' => 10, + 'carrier_id' => 20, + 'order_payment' => 'Bankwire', + 'order_currency' => 'CZK', + 'order_total_paid' => '999,99', + 'order_total_locale' => '999,99 CZK', + 'order_reference' => 'ORD123', + 'order_datetime' => '4.7.2025 12:34', + 'order_date' => '4.7.2025', + 'order_date1' => '04.07.2025', + 'order_date2' => '04/07/2025', + 'order_date3' => '04-07-2025', + 'order_date4' => '2025-07-04', + 'order_date5' => '07.04.2025', + 'order_date6' => '07/04/2025', + 'order_date7' => '07-04-2025', + 'order_time' => '12:34', + 'order_time1' => '12:34', + 'order_carrier_name' => 'PPL', + 'order_carrier_url' => 'https://track/TRACK123', + 'order_carrier_delay' => 'Next day', + 'order_carrier_tracking_number' => 'TRACK123', + 'order_carrier_tracking_date' => '4.7.2025 13:00', + 'order_carrier_price' => '100,00', + 'order_carrier_weight' => '2,50', + 'order_carrier_price_locale' => '100,00 CZK', + 'order_message' => 'Děkujeme za objednávku', + 'order_products1' => '2x T-shirt TSHIRT', + 'order_products2' => '2x T-shirt', + 'order_products3' => '2x (101)T-shirt TSHIRT', + 'order_products4' => '2x TSHIRT', + 'order_products5' => "2x T-shirt TSHIRT", + 'order_products6' => "2x T-shirt", + 'order_products7' => "2x (101)T-shirt TSHIRT", + 'order_products8' => "2x TSHIRT", + 'order_smsprinter1' => '2,T-shirt,500,00 CZK', + 'order_smsprinter2' => '2;T-shirt;500,00 CZK', + 'order_smsprinter3' => '2,TSHIRT,500,00 CZK', + 'order_smsprinter4' => '2;TSHIRT;500,00 CZK', + 'return_question' => 'Chci vrátit zboží', + 'return_products1' => '1x T-shirt TSHIRT', + 'return_products2' => '1x T-shirt', + 'return_products3' => '1x (101)T-shirt TSHIRT', + 'return_products4' => '1x TSHIRT', + 'return_products5' => "1x T-shirt TSHIRT", + 'return_products6' => "1x T-shirt", + 'return_products7' => "1x (101)T-shirt TSHIRT", + 'return_products8' => "1x TSHIRT", + ], $variables->toArray()); + } + + + public function tearDown(): void + { + Mockery::close(); + } +} + +(new OrderTest())->run(); diff --git a/tests/Event/Loader/PostTest.phpt b/tests/Event/Loader/PostTest.phpt new file mode 100644 index 0000000..c403d91 --- /dev/null +++ b/tests/Event/Loader/PostTest.phpt @@ -0,0 +1,55 @@ +shouldReceive('getValue')->with('phone_number')->once()->andReturn('123456789'); + $tools->shouldReceive('getValue')->with('phone_mobile')->once()->andReturn('987654321'); + + $_POST = [ + 'phone_number' => '123456789', + 'phone_mobile' => '987654321', + ]; + + $variables = new Variables(); + $loader = new Post(); + $loader->load($variables); + + Assert::same([ + 'customer_phone' => '123456789', + 'customer_mobile' => '987654321', + ], $variables->toArray()); + } + + public function testEmptyPost(): void + { + $_POST = []; + $variables = new Variables(); + $loader = new Post(); + $loader->load($variables); + Assert::same([], $variables->toArray()); + } + + + public function tearDown(): void + { + Mockery::close(); + } +} + +(new PostTest())->run(); diff --git a/tests/Event/Loader/ProductTest.phpt b/tests/Event/Loader/ProductTest.phpt new file mode 100644 index 0000000..95e784f --- /dev/null +++ b/tests/Event/Loader/ProductTest.phpt @@ -0,0 +1,80 @@ +shouldReceive('__construct')->with(123, false, null, 451)->set('id', 123)->set('description_short', ['8' => 'Short desc'])->set('description', 'Long desc')->set('manufacturer_name', 'TestMan')->set('price', 99.99)->set('minimal_quantity', 2)->set('reference', 'REF123')->set('id_supplier', 5)->set('supplier_reference', 'SUPREF')->set('ean13', 'EAN123')->set('upc', 'UPC123')->set('isbn', 'ISBN123'); + $product->shouldReceive('getQuantity')->with(123)->andReturn(10); + $product->shouldReceive('getProductName')->with(123)->andReturn('Test Product'); + + $supplier = Mockery::mock('overload:Supplier'); + $supplier->shouldReceive('getNameById')->with(5)->once()->andReturn('SupplierName'); + + $variables = new Variables([ + 'product_id' => 123, + 'shop_id' => 451, + 'lang_id' => 8, + 'shop_currency' => 'CZK', + ]); + + $loader = new Product($formatter = Mockery::mock(Formatter::class)); + $formatter->shouldReceive('format')->with('number', 99.99)->once()->andReturn('99,99'); + $formatter->shouldReceive('format')->with('price', 99.99, 'CZK')->once()->andReturn('99,99 CZK'); + + $loader->load($variables); + + Assert::same([ + 'product_id' => 123, + 'shop_id' => 451, + 'lang_id' => 8, + 'shop_currency' => 'CZK', + 'product_name' => 'Test Product', + 'product_description' => 'Short desc', + 'product_manufacturer' => 'TestMan', + 'product_price' => '99,99', + 'product_price_locale' => '99,99 CZK', + 'product_quantity' => 10, + 'product_minimal_quantity' => 2, + 'product_ref' => 'REF123', + 'product_supplier' => 'SupplierName', + 'product_supplier_ref' => 'SUPREF', + 'product_supplier_id' => 5, + 'product_ean13' => 'EAN123', + 'product_upc' => 'UPC123', + 'product_isbn' => 'ISBN123', + ], $variables->toArray()); + } + + + public function testNotProductId(): void + { + $loader = new Product(Mockery::mock(Formatter::class)); + $loader->load($variables = new Variables()); + + Assert::same([], $variables->toArray()); + } + + + public function tearDown(): void + { + Mockery::close(); + } +} + +(new ProductTest())->run(); + diff --git a/tests/Event/Loader/ShopTest.phpt b/tests/Event/Loader/ShopTest.phpt new file mode 100644 index 0000000..b798662 --- /dev/null +++ b/tests/Event/Loader/ShopTest.phpt @@ -0,0 +1,68 @@ +shouldReceive('__construct')->set('id', 451)->set('name', 'PrestaShop Development'); + $prestashop_shop->shouldReceive('getBaseURL')->withNoArgs()->once()->andReturn('https://bulkgate.com/'); + + $configuration = Mockery::mock('overload:Configuration'); + $configuration->shouldReceive('get')->with('PS_SHOP_EMAIL', null, null, 451)->once()->andReturn('xxx@bulkgate.com'); + $configuration->shouldReceive('get')->with('PS_SHOP_PHONE', null, null, 451)->once()->andReturn('+420777777777'); + $configuration->shouldReceive('get')->with('PS_CURRENCY_DEFAULT', null, null, 451)->once()->andReturn('7'); + $configuration->shouldReceive('get')->with('PS_LANG_DEFAULT', null, null, 451)->once()->andReturn('8'); + + $currency = Mockery::mock('overload:Currency'); + $currency->shouldReceive('getIsoCodeById')->with(7)->once()->andReturn('CZK'); + + $variables = new Variables(['shop_id' => 451]); + + $shop = new Shop($language = Mockery::mock(Language::class)); + $language->shouldReceive('get')->with(8)->once()->andReturn('cs'); + + $shop->load($variables); + + Assert::same([ + 'shop_id' => 451, + 'shop_email' => 'xxx@bulkgate.com', + 'shop_phone' => '+420777777777', + 'shop_currency' => 'CZK', + 'shop_name' => 'PrestaShop Development', + 'shop_domain' => 'https://bulkgate.com/', + 'lang_id' => '8', + 'lang_iso' => 'cs', + ], $variables->toArray()); + } + + + public function testNotShopId(): void + { + $shop = new Shop(Mockery::mock(Language::class)); + $shop->load($variables = new Variables()); + + Assert::same([], $variables->toArray()); + } + + + public function tearDown(): void + { + Mockery::close(); + } +} + +(new ShopTest())->run(); diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..78c93ec --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,15 @@ + +

 BulkGate Debug

+

This page serves as a comprehensive tool for users to monitor, analyze, and troubleshoot the plugin, including tracking errors in the log. It also provides essential information and troubleshooting capabilities.

+ +

Requirements test

+ + + + + + + + + + + + + + + + + + + {% for requirement in requirements %} + + + + + {% endfor %} + +
+ PHP Version + + {{ php_version }} +
+ Platform Version (Prestashop) + + {{ platform_version }} +
+ Module Version + + {{ module_version }} +
+ URL + + {{ url }} +
+ {{ requirement.description }} + + {{ requirement.passed ? "OK" : "FAIL " ~requirement.error }} +
+ +

Error log

+ {% if errors %} +
+ + + + + + + + + {% for error in errors %} + + + + + + {% endfor %} + +
DateMessageVersion
+ {{ error.created | date('d.m. Y H:i:s') }} + + {{ error.message }} + + {% if error.parameters %} + platform +
+ module + {% else %} + - + {% endif %} +
+
+ {% else %} +

No errors found.

+ {% endif %} + +{% endblock %} \ No newline at end of file diff --git a/views/templates/admin/index.html.twig b/views/templates/admin/index.html.twig new file mode 100644 index 0000000..4d3f057 --- /dev/null +++ b/views/templates/admin/index.html.twig @@ -0,0 +1,305 @@ +{% extends '@!PrestaShop/Admin/layout.html.twig' %} + +{% block javascripts %} + {{ parent() }} + + +{% endblock %} + +{% block stylesheets %} + +{% endblock %} + +{% block content %} +
+ +
+
+ BulkGate +
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/views/templates/admin/send-message.html.twig b/views/templates/admin/send-message.html.twig new file mode 100644 index 0000000..44389a2 --- /dev/null +++ b/views/templates/admin/send-message.html.twig @@ -0,0 +1,23 @@ + + + +
+
+

+ BulkGate SMS +

+
+
+ +
+