diff --git a/.docker/Dockerfile b/.docker/Dockerfile
new file mode 100644
index 0000000..2363111
--- /dev/null
+++ b/.docker/Dockerfile
@@ -0,0 +1,57 @@
+FROM php:8.2-apache
+ARG XDEBUG_VERSION=3.4.0
+ARG OC_VERSION=4.1.0.0
+
+LABEL org.opencontainers.image.source=https://github.com/bulkgate/cartsms
+LABEL opencart_version=${OC_VERSION}
+
+ENV OC_VERSION=${OC_VERSION}
+
+RUN echo "Opencart version: ${OC_VERSION}"
+
+WORKDIR /tmp
+
+RUN curl -fL "https://github.com/opencart/opencart/archive/refs/tags/${OC_VERSION}.tar.gz" -o opencart.tar.gz && \
+ curl -sS https://raw.githubusercontent.com/composer/getcomposer.org/f3108f64b4e1c1ce6eb462b159956461592b3e3e/web/installer | php && \
+ mv composer.phar /usr/local/bin/composer
+
+RUN tar -xzf opencart.tar.gz
+
+RUN rm opencart.tar.gz
+
+USER www-data:www-data
+
+RUN cp -r opencart-*/upload/* /var/www/html
+
+WORKDIR /var/www/html
+
+RUN cp config-dist.php config.php && \
+ cp admin/config-dist.php admin/config.php
+
+USER root
+
+VOLUME /var/www/html
+
+RUN apt-get update \
+ && apt-get install -y \
+ wait-for-it \
+ unzip \
+ libicu-dev \
+ libfreetype6-dev \
+ libjpeg62-turbo-dev \
+ libpng-dev \
+ libzip-dev \
+ libcurl3-dev \
+ libwebp-dev \
+ && pecl install xdebug-${XDEBUG_VERSION} \
+ && docker-php-ext-enable xdebug \
+ && docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp \
+ && docker-php-ext-install -j$(nproc) gd zip mysqli curl intl \
+ && docker-php-ext-enable gd zip mysqli curl intl
+
+RUN a2enmod rewrite
+
+COPY --chmod=777 entrypoint.sh /usr/sbin
+COPY install-extension.php /tmp
+
+ENTRYPOINT ["entrypoint.sh"]
diff --git a/.docker/entrypoint.sh b/.docker/entrypoint.sh
new file mode 100644
index 0000000..008fd4a
--- /dev/null
+++ b/.docker/entrypoint.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+if [ ! -f /var/www/html/install.lock ]; then
+ wait-for-it db:3306 -t 60 &&
+ php /var/www/html/install/cli_install.php install \
+ --username admin \
+ --password admin \
+ --email admin@example.com \
+ --http_server http://localhost:8083/ \
+ --db_driver mysqli \
+ --db_hostname db \
+ --db_username root \
+ --db_password admin \
+ --db_database opencart \
+ --db_port 3306 \
+ --db_prefix oc_ &&
+ touch /var/www/html/install.lock &&
+ php /tmp/install-extension.php
+fi
+
+exec apache2-foreground
\ No newline at end of file
diff --git a/.docker/install-extension.php b/.docker/install-extension.php
new file mode 100644
index 0000000..7f93725
--- /dev/null
+++ b/.docker/install-extension.php
@@ -0,0 +1,77 @@
+register('Opencart\\' . APPLICATION, DIR_APPLICATION);
+$autoloader->register('Opencart\Extension', DIR_EXTENSION);
+$autoloader->register('Opencart\System', DIR_SYSTEM);
+
+require_once DIR_SYSTEM . 'vendor.php';
+
+// Registry
+$registry = new \Opencart\System\Engine\Registry();
+$registry->set('autoloader', $autoloader);
+
+// Config
+$config = new \Opencart\System\Engine\Config();
+$config->addPath(DIR_CONFIG);
+// Load the default config
+$config->load('default');
+$config->load(strtolower(APPLICATION));
+$registry->set('config', $config);
+
+// Set the default application
+$config->set('application', APPLICATION);
+
+define('IS_4_1_x', class_exists('Opencart\System\Engine\Factory'));
+
+// Factory - opencart 4.1.x
+if (IS_4_1_x) {
+ $registry->set('factory', new \Opencart\System\Engine\Factory($registry));
+}
+
+// Loader
+$loader = new \Opencart\System\Engine\Loader($registry);
+$registry->set('load', $loader);
+
+// Event
+$event = new \Opencart\System\Engine\Event($registry);
+$registry->set('event', $event);
+
+// Cache
+//$registry->set('cache', new \Opencart\System\Library\Cache($config->get('cache_engine'), $config->get('cache_expire')));
+
+$db = new \Opencart\System\Library\DB($config->get('db_engine'), $config->get('db_hostname'), $config->get('db_username'), $config->get('db_password'), $config->get('db_database'), $config->get('db_port'), $config->get('db_ssl_key'), $config->get('db_ssl_cert'), $config->get('db_ssl_ca'));
+$registry->set('db', $db);
+
+
+$response = new \Opencart\System\Library\Response();
+$response->addHeader('Content-Type: text/plain; charset=utf-8');
+$registry->set('response', $response);
+
+$registry->load->model('setting/extension');
+
+/** @var \Opencart\Admin\Model\Setting\Extension $install*/
+$install = $registry->get('model_setting_extension');
+$install_info = json_decode(file_get_contents('/var/www/html/extension/oc_cartsms/install.json'), true);
+
+$id = $install->addInstall([
+ 'extension_id' => 0,
+ 'extension_download_id' => 0,
+ 'name' => $install_info['name'],
+ 'code' => 'oc_cartsms',
+ 'description' => $install_info['description'],
+ 'version' => $install_info['version'],
+ 'author' => $install_info['author'],
+ 'link' => $install_info['link']
+]);
+
+$install->addPath($id, 'oc_cartsms/admin/controller/module/cartsms.php');
+$install->editStatus($id, true);
+
+$response->setOutput('Extension ' . $install_info['name'] . ' installed successfully.');
+
+$response->output();
\ No newline at end of file
diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml
new file mode 100644
index 0000000..f16d80e
--- /dev/null
+++ b/.github/workflows/php.yml
@@ -0,0 +1,25 @@
+name: Run tests
+
+on: [push]
+
+jobs:
+ tests:
+ runs-on: ubuntu-latest
+
+ container: ghcr.io/bulkgate/cartsms:4.1.0.0
+
+ steps:
+ - uses: actions/checkout@v1
+
+ - run: composer install --prefer-dist --no-progress --no-suggest
+
+ - run: composer run tester
+
+ - run: composer run coverage
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: coverage-report
+ path: coverage.html
+
+ #- run: composer run phpstan
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4eb1183
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+.env
+.idea
+vendor
+opencart
+tests/**/output/*
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index 5833171..0000000
--- a/.gitmodules
+++ /dev/null
@@ -1,3 +0,0 @@
-[submodule "upload/system/library/cartsms/extensions"]
- path = upload/system/library/cartsms/extensions
- url = https://github.com/BulkGate/extensions
diff --git a/README.md b/README.md
index fcfe729..bf3c550 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,22 @@
http://www.cart-sms.com/
+
+# Development
+
+https://webkul.com/blog/how-to-create-a-module-in-opencart/
+
+
+## Category extension
+URL: http://localhost:8083/admin/index.php?route=extension/opencart/module/category&user_token=886a6f6378b7a94cbccad4acd55abdf1
+FILE: /extension/opencart/admin/controller/module/category.php
+view: /extension/opencart/admin/view/template/module/category.twig
+NAMESPACE: Opencart\Admin\Controller\Extension\Opencart\Module;
+CLASS: Category
+
+## Cartsms extension
+URL: http://localhost:8083/admin/index.php?route=extension/cartsms/module/cartsms&user_token=886a6f6378b7a94cbccad4acd55abdf1
+FILE: /extension/cartsms/admin/controller/module/cartsms.php
+view: /extension/cartsms/admin/view/template/module/cartsms.twig
+NAMESPACE: Opencart\Admin\Controller\Extension\Cartsms\Module;
+CLASS: Cartsms
\ No newline at end of file
diff --git a/admin/controller/event/hook.php b/admin/controller/event/hook.php
new file mode 100644
index 0000000..c6ab083
--- /dev/null
+++ b/admin/controller/event/hook.php
@@ -0,0 +1,128 @@
+language->load('extension/oc_cartsms/module/cartsms');
+
+ $data['menus'][] = [
+ 'id' => 'menu-cartsms',
+ 'icon' => 'fas fa-envelope',
+ 'name' => $this->language->get('extension_name_menu'),
+ 'href' => $this->url->link('extension/oc_cartsms/module/cartsms', 'user_token=' . $this->session->data['user_token']),
+ 'children' => []
+ ];
+ }
+
+ public function hookRenderSendMessageBox(string $route, array &$data)
+ {
+ $order_id = (int) $data['order_id'];
+
+ if ($order_id === 0 || !$this->di_container->getByClass(Plugin\Settings\Settings::class)->load('static:application_token')) {
+ return;
+ }
+
+ $data['extensions'][] = $this->load->controller('extension/oc_cartsms/module/send_message', $order_id);
+ }
+
+ public function hookSendSms(array $params)
+ {
+ $number = $params['number'] ?? null;
+ $template = $params['template'] ?? null;
+ $variables = $params['variables'] ?? [];
+ $settings = $params['settings'] ?? [];
+
+ $hook = $this->di_container->getByClass(Plugin\Event\Hook::class);
+
+ $hook->send('/api/2.0/advanced/transactional', [
+ 'number' => $number,
+ 'application_product' => 'oc',
+ '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,
+ ],
+ ],
+ ]);
+ }
+
+ //OK
+ public function hookAddCustomer(string $route, array $params, int $id_customer)
+ {
+ $this->runHook('customer', 'new', new Plugin\Event\Variables([
+ 'customer_id' => $id_customer,
+ //'data' => $params,
+ ]));
+ }
+
+ public function hookProductOutOfStockBefore(string $route, array $params)
+ {
+ [$id_product] = $params;
+ $this->load->model('catalog/product');
+
+ $this->product_out_of_stock_state = (new State(fn() => (int) $this->model_catalog_product->getProduct($id_product)['quantity']))
+ ->captureInitial()
+ ->setExpected(0);
+ }
+
+ //OK
+ public function hookProductOutOfStockAfter(string $route, array $params)
+ {
+ [$id_product, $data] = $params;
+
+ $this->product_out_of_stock_state->captureActual();
+
+ if (!$this->product_out_of_stock_state->shouldRunHook()) {
+ return;
+ }
+
+ $this->runHook('product', 'out-of-stock', new Plugin\Event\Variables([
+ 'product_id' => $id_product,
+ 'shop_id' => $data['product_store'][0],
+ //'data' => $params,
+ ]));
+ }
+
+ public function hookChangeReturnStatusBefore(string $route, array $params)
+ {
+ [$id_return, $id_return_status] = $params;
+
+ $this->load->model('sale/returns');
+
+ $this->return_status_state = (new State(fn() => (int) $this->model_sale_returns->getReturn($id_return)['return_status_id']))
+ ->captureInitial()
+ ->setExpected((int) $id_return_status);
+ }
+
+ public function hookChangeReturnStatusAfter(string $route, array $params)
+ {
+ [$id_return, $id_return_status] = $params;
+
+ $this->return_status_state->captureActual();
+
+ if (!$this->return_status_state->shouldRunHook()) {
+ return;
+ }
+
+ $this->runHook('return', 'change-status', new Plugin\Event\Variables([
+ 'return_id' => $id_return,
+ 'return_status_id' => $id_return_status,
+ ]));
+ }
+}
\ No newline at end of file
diff --git a/admin/controller/module/cartsms.php b/admin/controller/module/cartsms.php
new file mode 100644
index 0000000..2188c37
--- /dev/null
+++ b/admin/controller/module/cartsms.php
@@ -0,0 +1,317 @@
+di_container->getByClass(Plugin\Eshop\EshopSynchronizer::class)->run();
+
+ $jwt = $this->di_container->getByClass(Plugin\User\Sign::class)->authenticate(false, ['expire' => time() + 300]);
+
+ $this->load->language('extension/oc_cartsms/module/cartsms');
+ $this->document->setTitle($this->language->get('heading_title'));
+
+ $this->response->setOutput($this->load->view('extension/oc_cartsms/module/cartsms', [
+ 'header' => $this->load->controller('common/header'),
+ 'column_left' => $this->load->controller('common/column_left'),
+ 'footer' => $this->load->controller('common/footer'),
+ 'synchronizer' => $this->di_container->getByClass(Plugin\Settings\Synchronizer::class),
+ 'settings' => $this->di_container->getByClass(Plugin\Settings\Settings::class),
+ 'url' => $this->di_container->getByClass(Plugin\IO\Url::class),
+ 'path' => new class($this->url, $this->session->data['user_token']) {
+ public function __construct(private $url, private $token)
+ {
+ }
+
+ public function get(string $route, array $args = [])
+ {
+ return $this->url->link($route, [...['user_token' => $this->token], ...$args], true);
+ }
+ },
+ 'token' => $jwt,
+ ]));
+ }
+
+ public function install()
+ {
+ $this->di_container->getByClass(Plugin\Settings\Settings::class)->install();
+
+ // enable access to module backoffice UI
+ $this->load->model('user/user_group');
+ $this->model_user_user_group->addPermission($this->user->getGroupId(), 'access', 'extension/oc_cartsms/module/cartsms');
+ $this->model_user_user_group->addPermission($this->user->getGroupId(), 'modify', 'extension/oc_cartsms/module/cartsms');
+
+ // we need to enable phone number fields
+ $this->load->model('setting/setting');
+ $this->model_setting_setting->editValue('config', 'config_telephone_display', 1);
+ $this->model_setting_setting->editValue('config', 'config_telephone_required', 1);
+
+ // we register cron script to process async tasks
+ $this->load->model('setting/cron');
+ $this->model_setting_cron->addCron('cartsms_asynchronous', '', 'minute', 'extension/oc_cartsms/asynchronous/task', true);
+
+
+ $this->load->model('setting/event');
+
+ // register custom fields for marketing messages opt-in
+ $this->model_setting_event->addEvent([
+ 'code' => 'cartsms_custom_fields',
+ 'description' => '',
+ 'trigger' => 'catalog/model/account/custom_field.getCustomFields/after',
+ 'action' => 'extension/oc_cartsms/event/hook.hookCustomFields',
+ 'status' => '1',
+ 'sort_order' => '1'
+ ]);
+
+ // register asynchronous script to process async tasks
+ $this->model_setting_event->addEvent([
+ 'code' => 'cartsms_asynchronous',
+ 'description' => '',
+ 'trigger' => 'catalog/view/common/header/before',
+ 'action' => 'extension/oc_cartsms/event/hook.hookAsynchronousAsset',
+ 'status' => '1',
+ 'sort_order' => '1'
+ ]);
+
+ // register module into menu
+ $this->model_setting_event->addEvent([
+ 'code' => 'cartsms_menu',
+ 'description' => '',
+ 'trigger' => 'admin/view/common/column_left/before',
+ 'action' => 'extension/oc_cartsms/event/hook.hookMenu',
+ 'status' => '1',
+ 'sort_order' => '1'
+ ]);
+
+ $this->model_setting_event->addEvent([
+ 'code' => 'cartsms_send_message_box',
+ 'description' => '',
+ 'trigger' => 'admin/view/sale/order_info/before',
+ 'action' => 'extension/oc_cartsms/event/hook.hookRenderSendMessageBox',
+ 'status' => '1',
+ 'sort_order' => '1'
+ ]);
+
+ // user can programmatically send sms
+ $this->model_setting_event->addEvent([
+ 'code' => 'cartsms_send_sms',
+ 'description' => '',
+ 'trigger' => 'system/cartsms.send_sms',
+ 'action' => 'extension/oc_cartsms/event/hook.hookSendSms',
+ 'status' => '1',
+ 'sort_order' => '1'
+ ]);
+
+
+ // RETURN:NEW
+ // todo: z back office nelze vytvorit return, protoze to hlasi chybu product_id ... z nejakeho duvodu je to 0
+ $this->model_setting_event->addEvent([
+ 'code' => 'cartsms_add_return',
+ 'description' => '',
+ 'trigger' => 'catalog/model/account/returns.addReturn/after',
+ 'action' => 'extension/oc_cartsms/event/hook.hookAddReturn',
+ 'status' => '1',
+ 'sort_order' => '1'
+ ]);
+
+
+ // RETURN:CHANGE-STATUS
+ $this->model_setting_event->addEvent([
+ 'code' => 'cartsms_change_return_status',
+ 'description' => '',
+ 'trigger' => 'admin/model/sale/returns.addHistory/before',
+ 'action' => 'extension/oc_cartsms/event/hook.hookChangeReturnStatusBefore',
+ 'status' => '1',
+ 'sort_order' => '1'
+ ]);
+ $this->model_setting_event->addEvent([
+ 'code' => 'cartsms_change_return_status',
+ 'description' => '',
+ 'trigger' => 'admin/model/sale/returns.addHistory/after',
+ 'action' => 'extension/oc_cartsms/event/hook.hookChangeReturnStatusAfter',
+ 'status' => '1',
+ 'sort_order' => '1'
+ ]);
+
+
+ // ORDER:NEW
+ $this->model_setting_event->addEvent([
+ 'code' => 'cartsms_add_order',
+ 'description' => '',
+ 'trigger' => 'catalog/controller/checkout/success/before',
+ 'action' => 'extension/oc_cartsms/event/hook.hookAddOrderCheckoutSuccess',
+ 'status' => '1',
+ 'sort_order' => '1'
+ ]);
+ $this->model_setting_event->addEvent([
+ 'code' => 'cartsms_add_order',
+ 'description' => '',
+ 'trigger' => 'catalog/controller/api/order/after',
+ 'action' => 'extension/oc_cartsms/event/hook.hookAddOrderApi',
+ 'status' => '1',
+ 'sort_order' => '1'
+ ]);
+
+
+ // PRODUCT:OUT-OF-STOCK
+ // catalog
+ $this->model_setting_event->addEvent([
+ 'code' => 'cartsms_add_order_history',
+ 'description' => '',
+ 'trigger' => 'catalog/model/checkout/order.addHistory/before',
+ 'action' => 'extension/oc_cartsms/event/hook.hookProductOutOfStockBefore',
+ 'status' => '1',
+ 'sort_order' => '1'
+ ]);
+ $this->model_setting_event->addEvent([
+ 'code' => 'cartsms_add_order_history',
+ 'description' => '',
+ 'trigger' => 'catalog/model/checkout/order.addHistory/after',
+ 'action' => 'extension/oc_cartsms/event/hook.hookProductOutOfStockAfter',
+ 'status' => '1',
+ 'sort_order' => '1'
+ ]);
+ // admin
+ $this->model_setting_event->addEvent([
+ 'code' => 'cartsms_edit_product',
+ 'description' => '',
+ 'trigger' => 'admin/model/catalog/product.editProduct/before',
+ 'action' => 'extension/oc_cartsms/event/hook.hookProductOutOfStockBefore',
+ 'status' => '1',
+ 'sort_order' => '1'
+ ]);
+ $this->model_setting_event->addEvent([
+ 'code' => 'cartsms_edit_product',
+ 'description' => '',
+ 'trigger' => 'admin/model/catalog/product.editProduct/after',
+ 'action' => 'extension/oc_cartsms/event/hook.hookProductOutOfStockAfter',
+ 'status' => '1',
+ 'sort_order' => '1'
+ ]);
+
+
+ // ORDER:CHANGE-STATUS
+ $this->model_setting_event->addEvent([
+ 'code' => 'cartsms_change_order_status',
+ 'description' => '',
+ 'trigger' => 'catalog/controller/api/order/before',
+ 'action' => 'extension/oc_cartsms/event/hook.hookChangeOrderStatusBefore',
+ 'status' => '1',
+ 'sort_order' => '1'
+ ]);
+ $this->model_setting_event->addEvent([
+ 'code' => 'cartsms_change_order_status',
+ 'description' => '',
+ 'trigger' => 'catalog/controller/api/order/after',
+ 'action' => 'extension/oc_cartsms/event/hook.hookChangeOrderStatusAfter',
+ 'status' => '1',
+ 'sort_order' => '1'
+ ]);
+
+
+ // CUSTOMER:NEW
+ $this->model_setting_event->addEvent([
+ 'code' => 'cartsms_add_customer',
+ 'description' => '',
+ 'trigger' => 'catalog/model/account/customer.addCustomer/after',
+ 'action' => 'extension/oc_cartsms/event/hook.hookAddCustomer',
+ 'status' => '1',
+ 'sort_order' => '1'
+ ]);
+ $this->model_setting_event->addEvent([
+ 'code' => 'cartsms_add_customer',
+ 'description' => '',
+ 'trigger' => 'admin/model/customer/customer.addCustomer/after',
+ 'action' => 'extension/oc_cartsms/event/hook.hookAddCustomer',
+ 'status' => '1',
+ 'sort_order' => '1'
+ ]);
+
+
+ // CONTACT:FORM
+ $this->model_setting_event->addEvent([
+ 'code' => 'cartsms_contact_form',
+ 'description' => '',
+ 'trigger' => 'catalog/controller/information/contact.send/after',
+ 'action' => 'extension/oc_cartsms/event/hook.hookContactForm',
+ 'status' => '1',
+ 'sort_order' => '1'
+ ]);
+ }
+
+ public function uninstall()
+ {
+ $this->di_container->getByClass(Plugin\Settings\Settings::class)->uninstall();
+
+ $this->load->model('user/user_group');
+ $this->model_user_user_group->removePermission($this->user->getGroupId(), 'access', 'extension/oc_cartsms/module/cartsms');
+ $this->model_user_user_group->removePermission($this->user->getGroupId(), 'modify', 'extension/oc_cartsms/module/cartsms');
+
+ $this->load->model('setting/cron');
+ $this->model_setting_cron->deleteCronByCode('cartsms_asynchronous');
+
+ $this->load->model('setting/event');
+ $this->model_setting_event->deleteEventByCode('cartsms_custom_fields');
+ $this->model_setting_event->deleteEventByCode('cartsms_asynchronous');
+ $this->model_setting_event->deleteEventByCode('cartsms_menu');
+ $this->model_setting_event->deleteEventByCode('cartsms_send_message_box');
+ $this->model_setting_event->deleteEventByCode('cartsms_send_sms');
+ $this->model_setting_event->deleteEventByCode('cartsms_add_order');
+ $this->model_setting_event->deleteEventByCode('cartsms_add_return');
+ $this->model_setting_event->deleteEventByCode('cartsms_change_return_status');
+ $this->model_setting_event->deleteEventByCode('cartsms_add_order_history');
+ $this->model_setting_event->deleteEventByCode('cartsms_change_order_status');
+ $this->model_setting_event->deleteEventByCode('cartsms_add_customer');
+ $this->model_setting_event->deleteEventByCode('cartsms_edit_product');
+ $this->model_setting_event->deleteEventByCode('cartsms_contact_form');
+ }
+
+ public function proxy()
+ {
+ $this->response->addHeader('Content-Type: application/json');
+
+ switch ($this->request->get('action')) {
+ case "login":
+ ['email' => $email, 'password' => $password] = $this->request->post;
+
+ return $this->response->setOutput(json_encode($this->di_container->getByClass(Plugin\User\Sign::class)->in($email, $password, '/dashboard')));
+ case "logout":
+ return $this->response->setOutput(json_encode($this->di_container->getByClass(Plugin\User\Sign::class)->out('/sign/in')));
+ case "authenticate":
+ return $this->response->setOutput(json_encode($this->di_container->getByClass(Ajax\Authenticate::class)->run('/sign/in')));
+ case "module-settings":
+ return $this->response->setOutput(json_encode($this->di_container->getByClass(Ajax\PluginSettings::class)->run($this->request->post, fn(string $lang) => $this->url->link('extension/oc_cartsms/module/cartsms', ['user_token' => $this->session->data['user_token'], 'reload' => $lang], true) . '#/dashboard')));
+ }
+ }
+
+ public function debug()
+ {
+ $requirements = $this->di_container->getByClass(Plugin\Debug\Requirements::class);
+ $logger = $this->di_container->getByClass(Plugin\Debug\Logger::class);
+ $url = $this->di_container->getByClass(Plugin\IO\Url::class);
+
+ $requirements = $requirements->run([
+ $requirements->same('{"message":"BulkGate API"}', file_get_contents($url->get('api/welcome')), 'Api Connection'),
+ $requirements->same(true, version_compare(VERSION, '4.0.0', '>='), 'Opencart ver. >= 4.0.0'),
+ ]);
+
+ $this->response->setOutput($this->load->view('extension/oc_cartsms/module/debug', [
+ 'header' => $this->load->controller('common/header'),
+ 'column_left' => $this->load->controller('common/column_left'),
+ 'footer' => $this->load->controller('common/footer'),
+ 'requirements' => $requirements,
+ 'errors' => array_reverse($logger->getList()),
+ 'opencart_version' => VERSION,
+ 'php_version' => phpversion(),
+ 'url' => $url->get(),
+ ]));
+ }
+}
+
diff --git a/admin/controller/module/send_message.php b/admin/controller/module/send_message.php
new file mode 100644
index 0000000..f639fcc
--- /dev/null
+++ b/admin/controller/module/send_message.php
@@ -0,0 +1,41 @@
+di_container->getByClass(Plugin\User\Sign::class);
+ $url = $this->di_container->getByClass(Plugin\IO\Url::class);
+ $loader = $this->di_container->getByClass(Plugin\Event\Loader::class);
+
+ $this->load->model('sale/order');
+
+ $order = $this->model_sale_order->getOrder($order_id);
+
+ $variables = new Plugin\Event\Variables([
+ 'order_id' => $order_id,
+ 'customer_id' => $order['customer_id'],
+ 'lang_id' => $order['language_id'],
+ ]);
+
+ $loader->load($variables);
+
+ return $this->load->view('extension/oc_cartsms/module/send_message', [
+ 'token' => $sign->authenticate(),
+ '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)
+ ]
+ ]);
+ }
+}
\ No newline at end of file
diff --git a/admin/language/en-gb/module/cartsms.php b/admin/language/en-gb/module/cartsms.php
new file mode 100644
index 0000000..c368f8e
--- /dev/null
+++ b/admin/language/en-gb/module/cartsms.php
@@ -0,0 +1,7 @@
+
+ @keyframes logo {
+ 0% {
+ filter: grayscale(1) opacity(.2);
+ transform: scale(.6);
+ }
+ 25% {
+ filter: none;
+ transform: scale(.65);
+ }
+ 70% {
+ transform: none;
+ }
+ }
+
+ @keyframes heading {
+ 0% {
+ opacity: .1;
+ }
+ 50% {
+ opacity: .8;
+ }
+ 100% {
+ opacity: 1;
+ }
+ }
+
+ @keyframes progress {
+ 100% {
+ opacity: 1
+ }
+ }
+
+ @keyframes progress-processing {
+ 0% {
+ transform: translateX(-300px)
+ }
+ 5% {
+ transform: translateX(-240px)
+ }
+ 15% {
+ transform: translateX(-30px)
+ }
+ 25% {
+ transform: translateX(-30px)
+ }
+ 30% {
+ transform: translateX(-20px)
+ }
+ 45% {
+ transform: translateX(-20px)
+ }
+ 50% {
+ transform: translateX(-15px)
+ }
+ 65% {
+ transform: translateX(-15px)
+ }
+ 70% {
+ transform: translateX(-10px)
+ }
+ 95% {
+ transform: translateX(-10px)
+ }
+ 100% {
+ transform: translateX(-5px)
+ }
+ }
+
+ #bulkgate-plugin {
+ will-change: transform;
+ z-index: 0;
+ }
+
+ #bulkgate-plugin #loading {
+ position: fixed;
+ contain: layout;
+ height: calc(100vh - 64px);
+ left: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ background: #fff;
+ z-index: 2999;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ }
+
+ #bulkgate-plugin #loading img {
+ width: 160px;
+ animation: logo 1.5s .3s both;
+ margin: 24px 0;
+ }
+
+ #bulkgate-plugin #loading h3 {
+ font-size: 32px;
+ color: #606469;
+ animation: heading .5s .675s both;
+ }
+
+ #bulkgate-plugin #progress {
+ animation: progress .5s 2.5s 1 both;
+ height: 4px;
+ width: 100%;
+ opacity: 0;
+ background: #ddd;
+ position: relative;
+ overflow: hidden;
+ }
+
+ #bulkgate-plugin #progress:before {
+ animation: progress-processing 20s 3s linear both;
+ background-color: var(--secondary);
+ content: '';
+ display: block;
+ height: 100%;
+ position: absolute;
+ transform: translateX(-300px);
+ width: 100%;
+ }
+
+ gate-ecommerce-plugin {
+ box-sizing: border-box; /* realne se tyka pouze web-componenty */
+ }
+
+{{ column_left }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{{ footer }}
diff --git a/admin/view/template/module/debug.twig b/admin/view/template/module/debug.twig
new file mode 100644
index 0000000..25d6a97
--- /dev/null
+++ b/admin/view/template/module/debug.twig
@@ -0,0 +1,71 @@
+{{ header }}
+{{ column_left }}
+
+
+
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
+
+
+
+
+ PHP Version
+
+
+ {{ php_version }}
+
+
+
+
+ OpenCart Version
+
+
+ {{ opencart_version }}
+
+
+
+
+ URL
+
+
+ {{ url }}
+
+
+ {% for requirement in requirements %}
+
+
+ {{ requirement.description }}
+
+
+ {{ requirement.passed ? "OK" : "FAIL " ~requirement.error }}
+
+
+ {% endfor %}
+
+
+
+
Error log
+ {% if errors %}
+
+
+
+ {% for error in errors %}
+
+
+ {{ error.created | date('d.m. Y H:i:s') }}
+
+
+ {{ error.message }}
+
+
+ {% endfor %}
+
+
+
+ {% else %}
+
No errors found.
+ {% endif %}
+
+
+{{ footer }}
\ No newline at end of file
diff --git a/admin/view/template/module/send_message.twig b/admin/view/template/module/send_message.twig
new file mode 100644
index 0000000..9b93d2d
--- /dev/null
+++ b/admin/view/template/module/send_message.twig
@@ -0,0 +1,14 @@
+
+
+
+ New message
diff --git a/catalog/controller/asynchronous/task.php b/catalog/controller/asynchronous/task.php
new file mode 100644
index 0000000..78c7690
--- /dev/null
+++ b/catalog/controller/asynchronous/task.php
@@ -0,0 +1,23 @@
+di_container->getByClass(Plugin\Settings\Settings::class);
+
+ if (in_array($settings->load('main:dispatcher'), [Plugin\Event\Dispatcher::Cron, Plugin\Event\Dispatcher::Asset])) {
+ $count = $this->di_container->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';
+ }
+ }
+}
\ No newline at end of file
diff --git a/catalog/controller/event/hook.php b/catalog/controller/event/hook.php
new file mode 100644
index 0000000..25856ff
--- /dev/null
+++ b/catalog/controller/event/hook.php
@@ -0,0 +1,181 @@
+url->link('extension/oc_cartsms/asynchronous/task') .'">'; //todo: udelat twig template?
+ }
+
+ // OK
+ public function hookCustomFields(string $route, array $params, array &$data)
+ {
+ $settings = $this->di_container->getByClass(Plugin\Settings\Settings::class);
+
+ if (!$settings->load('main:marketing_message_opt_in_enabled')) {
+ return;
+ }
+
+ $data[] = [
+ 'custom_field_id' => 'bulkgate_marketing_message_opt_in',
+ 'location' => 'account',
+ 'type' => 'checkbox',
+ 'required' => false,
+ //'name' => 'abc',
+ 'custom_field_value' => [[
+ 'custom_field_value_id' => 'bulkgate_marketing_message_opt_in', // ==
+ 'custom_field_id' => 'bulkgate_marketing_message',
+ 'name' => 'I consent to receiving marketing communications via SMS, Viber, RCS, WhatsApp, and other similar channels.'
+ ]]
+ ];
+ }
+
+ //OK
+ public function hookAddOrderCheckoutSuccess(string $route)
+ {
+ if (!isset($this->session->data['order_id'])) {
+ return;
+ }
+
+ $this->runHook('order', 'new', new Plugin\Event\Variables([
+ 'order_id' => $this->session->data['order_id'],
+ //'data' => $this->session->data, todo: tady jeste muzeme ziskat data o adrese v pripade, ze uzivatel neni prihlaseny. Prednastavime promenne? Chtelo by to asi nejaky helper...
+ ]));
+ }
+
+ public function hookAddOrderApi(string $route)
+ {
+ if ($this->request->get['call'] !== 'confirm') {
+ return;
+ }
+
+ $output = Plugin\Utils\JsonArray::decode($this->response->getOutput());
+
+ if (!$output || (int) $this->request->post['order_id'] === (int) $output['order_id']) {
+ return;
+ }
+
+ $this->runHook('order', 'new', new Plugin\Event\Variables([
+ 'order_id' => $output['order_id'],
+ //'response' => $output,
+ //'_GET' => $this->request->get,
+ //'_POST' => $this->request->post,
+ //'data' => $data, //todo: api/order.index neprebira zadne parametry...
+ //'output' => $output, todo: provolava se pres API, takze $outptut neni nastaveny, protoze api/order.index nic nevraci. Muzeme maximalne vyuzit response objektu
+ //'store_url' => $shop_domain,
+ ]));
+ }
+
+ //OK
+ public function hookAddReturn(string $route, array $params, int $id_return)
+ {
+ $this->runHook('return', 'new', new Plugin\Event\Variables([
+ 'return_id' => $id_return,
+ ]));
+ }
+
+ public function hookProductOutOfStockBefore(string $route, array $params)
+ {
+ [$id_order] = $params;
+
+ $this->load->model('checkout/order');
+ $this->load->model('catalog/product');
+
+ $products = $this->model_checkout_order->getProducts($id_order);
+
+ $this->product_out_of_stock_state = (new State(fn () => array_map(fn($item) => (int) $this->model_catalog_product->getProduct($item['product_id'])['quantity'] ?? 0, array_combine(array_column($products, 'product_id'), $products))))
+ ->captureInitial();
+ }
+
+ //OK
+ public function hookProductOutOfStockAfter(string $route, array $params)
+ {
+ [$id_order] = $params;
+
+ $this->product_out_of_stock_state->captureActual();
+
+ if (!$this->product_out_of_stock_state->isChanged()) {
+ return;
+ }
+
+ $initial_products = $this->product_out_of_stock_state->getInitial();
+ $actual_products = $this->product_out_of_stock_state->getActual();
+
+ foreach($actual_products as $product_id => $quantity) {
+ $initial_quantity = $initial_products[$product_id];
+
+ if ($initial_quantity && $initial_quantity !== $quantity && $quantity === 0) {
+ $this->runHook('product', 'out-of-stock', new Plugin\Event\Variables([
+ 'order_id' => $id_order,
+ 'product_id' => $product_id,
+ //'data' => $params,
+ ]));
+ }
+ }
+ }
+
+ //OK
+ public function hookChangeOrderStatusBefore(string $route, array $params)
+ {
+ if ($this->request->get['call'] !== 'history_add') {
+ return;
+ }
+
+ $this->load->model('checkout/order');
+
+ $this->order_status_state = (new State(fn() => (int) $this->model_checkout_order->getOrder($this->request->post['order_id'])['order_status_id']))
+ ->captureInitial()
+ ->setExpected((int) $this->request->post['order_status_id']);
+ }
+
+ public function hookChangeOrderStatusAfter(string $route, array $params)
+ {
+ if ($this->order_status_state === null) {
+ return;
+ }
+
+ $this->order_status_state->captureActual();
+
+ if (!$this->order_status_state->shouldRunHook()) {
+ return;
+ }
+
+ $this->runHook('order', 'change-status', new Plugin\Event\Variables([
+ 'order_id' => $this->request->post['order_id'],
+ 'order_status_id' => $this->request->post['order_status_id'],
+ //'debug' => $this->order_status_state->debug(),
+ ]));
+ }
+
+ //OK
+ public function hookAddCustomer(string $route, array $params, int $id_customer)
+ {
+ $this->runHook('customer', 'new', new Plugin\Event\Variables([
+ 'customer_id' => $id_customer,
+ //'data' => $params,
+ ]));
+ }
+
+ //OK
+ public function hookContactForm(string $route)
+ {
+ $this->runHook('contact', 'form', new Plugin\Event\Variables([
+ 'shop_id' => $this->config->get('config_store_id'),
+ 'customer_email' => $this->request->post['email'],
+ 'customer_name' => $this->request->post['name'],
+ 'customer_message' => $this->request->post['enquiry'],
+ //'data' => $this->request->post,
+ ]));
+ }
+}
\ No newline at end of file
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..4f4a5c7
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,32 @@
+{
+ "name": "bulkgate/cartsms",
+ "type": "opencart-module",
+ "minimum-stability": "RC",
+ "require": {
+ "php": ">= 8.0",
+ "bulkgate/plugin": "dev-master"
+ },
+ "require-dev": {
+ "tracy/tracy": "^2.9",
+ "nette/tester": "^2.4.3",
+ "phpstan/phpstan": "^1.10",
+ "mockery/mockery": "^1.6"
+ },
+ "autoload": {
+ "psr-4": {
+ "BulkGate\\CartSms\\": "src/"
+ },
+ "classmap": [
+ "src/"
+ ]
+ },
+ "scripts": {
+ "tester": "tester -C tests --colors=1",
+ "coverage": "tester -C --coverage=coverage.html --coverage-src=src --stop-on-fail",
+ "phpstan": "phpstan analyse -c phpstan.neon --memory-limit=1G",
+ "prestashop-lint": "php-cs-fixer fix"
+ },
+ "config": {
+ "prepend-autoloader": false
+ }
+}
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000..3f7a883
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,416 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "53d0de7a304084b8818d70d44295ce87",
+ "packages": [
+ {
+ "name": "bulkgate/plugin",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/BulkGate/plugin.git",
+ "reference": "f1fbe7b48261462bfc87456c71d74c5824169377"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/BulkGate/plugin/zipball/f1fbe7b48261462bfc87456c71d74c5824169377",
+ "reference": "f1fbe7b48261462bfc87456c71d74c5824169377",
+ "shasum": ""
+ },
+ "require": {
+ "ext-curl": "*",
+ "ext-intl": "*",
+ "ext-json": "*",
+ "ext-mbstring": "*",
+ "ext-zlib": "*",
+ "php": ">=7.4"
+ },
+ "require-dev": {
+ "mockery/mockery": "1.4.2",
+ "nette/tester": "^2",
+ "phpstan/phpstan": "^1.9",
+ "rector/rector": "^0.16",
+ "tracy/tracy": "^2.9"
+ },
+ "default-branch": true,
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "authors": [
+ {
+ "name": "Lukáš Piják",
+ "email": "lukaspijak@gmail.com"
+ }
+ ],
+ "description": "Meta package for BulkGate plugins",
+ "support": {
+ "issues": "https://github.com/BulkGate/plugin/issues",
+ "source": "https://github.com/BulkGate/plugin/tree/1.0.1"
+ },
+ "time": "2023-12-06T12:19:36+00:00"
+ }
+ ],
+ "packages-dev": [
+ {
+ "name": "hamcrest/hamcrest-php",
+ "version": "v2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hamcrest/hamcrest-php.git",
+ "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3",
+ "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3|^7.0|^8.0"
+ },
+ "replace": {
+ "cordoval/hamcrest-php": "*",
+ "davedevelopment/hamcrest-php": "*",
+ "kodova/hamcrest-php": "*"
+ },
+ "require-dev": {
+ "phpunit/php-file-iterator": "^1.4 || ^2.0",
+ "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "hamcrest"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "description": "This is the PHP port of Hamcrest Matchers",
+ "keywords": [
+ "test"
+ ],
+ "support": {
+ "issues": "https://github.com/hamcrest/hamcrest-php/issues",
+ "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1"
+ },
+ "time": "2020-07-09T08:09:16+00:00"
+ },
+ {
+ "name": "mockery/mockery",
+ "version": "1.6.12",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/mockery/mockery.git",
+ "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/mockery/mockery/zipball/1f4efdd7d3beafe9807b08156dfcb176d18f1699",
+ "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699",
+ "shasum": ""
+ },
+ "require": {
+ "hamcrest/hamcrest-php": "^2.0.1",
+ "lib-pcre": ">=7.0",
+ "php": ">=7.3"
+ },
+ "conflict": {
+ "phpunit/phpunit": "<8.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^8.5 || ^9.6.17",
+ "symplify/easy-coding-standard": "^12.1.14"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "library/helpers.php",
+ "library/Mockery.php"
+ ],
+ "psr-4": {
+ "Mockery\\": "library/Mockery"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Pádraic Brady",
+ "email": "padraic.brady@gmail.com",
+ "homepage": "https://github.com/padraic",
+ "role": "Author"
+ },
+ {
+ "name": "Dave Marshall",
+ "email": "dave.marshall@atstsolutions.co.uk",
+ "homepage": "https://davedevelopment.co.uk",
+ "role": "Developer"
+ },
+ {
+ "name": "Nathanael Esayeas",
+ "email": "nathanael.esayeas@protonmail.com",
+ "homepage": "https://github.com/ghostwriter",
+ "role": "Lead Developer"
+ }
+ ],
+ "description": "Mockery is a simple yet flexible PHP mock object framework",
+ "homepage": "https://github.com/mockery/mockery",
+ "keywords": [
+ "BDD",
+ "TDD",
+ "library",
+ "mock",
+ "mock objects",
+ "mockery",
+ "stub",
+ "test",
+ "test double",
+ "testing"
+ ],
+ "support": {
+ "docs": "https://docs.mockery.io/",
+ "issues": "https://github.com/mockery/mockery/issues",
+ "rss": "https://github.com/mockery/mockery/releases.atom",
+ "security": "https://github.com/mockery/mockery/security/advisories",
+ "source": "https://github.com/mockery/mockery"
+ },
+ "time": "2024-05-16T03:13:13+00:00"
+ },
+ {
+ "name": "nette/tester",
+ "version": "v2.5.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nette/tester.git",
+ "reference": "c11863785779e87b40adebf150364f2e5938c111"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nette/tester/zipball/c11863785779e87b40adebf150364f2e5938c111",
+ "reference": "c11863785779e87b40adebf150364f2e5938c111",
+ "shasum": ""
+ },
+ "require": {
+ "php": "8.0 - 8.4"
+ },
+ "require-dev": {
+ "ext-simplexml": "*",
+ "phpstan/phpstan": "^1.0"
+ },
+ "bin": [
+ "src/tester"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.5-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause",
+ "GPL-2.0-only",
+ "GPL-3.0-only"
+ ],
+ "authors": [
+ {
+ "name": "David Grudl",
+ "homepage": "https://davidgrudl.com"
+ },
+ {
+ "name": "Miloslav Hůla",
+ "homepage": "https://github.com/milo"
+ },
+ {
+ "name": "Nette Community",
+ "homepage": "https://nette.org/contributors"
+ }
+ ],
+ "description": "Nette Tester: enjoyable unit testing in PHP with code coverage reporter. 🍏🍏🍎🍏",
+ "homepage": "https://tester.nette.org",
+ "keywords": [
+ "Xdebug",
+ "assertions",
+ "clover",
+ "code coverage",
+ "nette",
+ "pcov",
+ "phpdbg",
+ "phpunit",
+ "testing",
+ "unit"
+ ],
+ "support": {
+ "issues": "https://github.com/nette/tester/issues",
+ "source": "https://github.com/nette/tester/tree/v2.5.4"
+ },
+ "time": "2024-10-23T23:57:10+00:00"
+ },
+ {
+ "name": "phpstan/phpstan",
+ "version": "1.12.16",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpstan.git",
+ "reference": "e0bb5cb78545aae631220735aa706eac633a6be9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e0bb5cb78545aae631220735aa706eac633a6be9",
+ "reference": "e0bb5cb78545aae631220735aa706eac633a6be9",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2|^8.0"
+ },
+ "conflict": {
+ "phpstan/phpstan-shim": "*"
+ },
+ "bin": [
+ "phpstan",
+ "phpstan.phar"
+ ],
+ "type": "library",
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PHPStan - PHP Static Analysis Tool",
+ "keywords": [
+ "dev",
+ "static analysis"
+ ],
+ "support": {
+ "docs": "https://phpstan.org/user-guide/getting-started",
+ "forum": "https://github.com/phpstan/phpstan/discussions",
+ "issues": "https://github.com/phpstan/phpstan/issues",
+ "security": "https://github.com/phpstan/phpstan/security/policy",
+ "source": "https://github.com/phpstan/phpstan-src"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/ondrejmirtes",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/phpstan",
+ "type": "github"
+ }
+ ],
+ "time": "2025-01-21T14:50:05+00:00"
+ },
+ {
+ "name": "tracy/tracy",
+ "version": "v2.10.9",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nette/tracy.git",
+ "reference": "e7af75205b184ca8895bc57fafd331f8d5022d26"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nette/tracy/zipball/e7af75205b184ca8895bc57fafd331f8d5022d26",
+ "reference": "e7af75205b184ca8895bc57fafd331f8d5022d26",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "ext-session": "*",
+ "php": "8.0 - 8.4"
+ },
+ "conflict": {
+ "nette/di": "<3.0"
+ },
+ "require-dev": {
+ "latte/latte": "^2.5 || ^3.0",
+ "nette/di": "^3.0",
+ "nette/http": "^3.0",
+ "nette/mail": "^3.0 || ^4.0",
+ "nette/tester": "^2.2",
+ "nette/utils": "^3.0 || ^4.0",
+ "phpstan/phpstan": "^1.0",
+ "psr/log": "^1.0 || ^2.0 || ^3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.10-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/Tracy/functions.php"
+ ],
+ "classmap": [
+ "src"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "David Grudl",
+ "homepage": "https://davidgrudl.com"
+ },
+ {
+ "name": "Nette Community",
+ "homepage": "https://nette.org/contributors"
+ }
+ ],
+ "description": "😎 Tracy: the addictive tool to ease debugging PHP code for cool developers. Friendly design, logging, profiler, advanced features like debugging AJAX calls or CLI support. You will love it.",
+ "homepage": "https://tracy.nette.org",
+ "keywords": [
+ "Xdebug",
+ "debug",
+ "debugger",
+ "nette",
+ "profiler"
+ ],
+ "support": {
+ "issues": "https://github.com/nette/tracy/issues",
+ "source": "https://github.com/nette/tracy/tree/v2.10.9"
+ },
+ "time": "2024-11-07T14:48:00+00:00"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "RC",
+ "stability-flags": {
+ "bulkgate/plugin": 20
+ },
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {
+ "php": ">= 7.4"
+ },
+ "platform-dev": {},
+ "plugin-api-version": "2.6.0"
+}
diff --git a/dist/CartSMS_6.00_RC1_for_OpenCart_30xx_1535027082.zip b/dist/CartSMS_6.00_RC1_for_OpenCart_30xx_1535027082.zip
deleted file mode 100644
index b60d487..0000000
Binary files a/dist/CartSMS_6.00_RC1_for_OpenCart_30xx_1535027082.zip and /dev/null differ
diff --git a/distribution.php b/distribution.php
new file mode 100644
index 0000000..4921331
--- /dev/null
+++ b/distribution.php
@@ -0,0 +1,16 @@
+ Affiliate program.
+const BulkGateAffiliateId = '';
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..728ac38
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,34 @@
+services:
+ opencart:
+ image: ghcr.io/bulkgate/cartsms:${OC_VERSION:-latest}
+ depends_on:
+ - db
+ ports:
+ - "8083:80"
+ volumes:
+ - ./admin:/var/www/html/extension/oc_cartsms/admin
+ - ./catalog:/var/www/html/extension/oc_cartsms/catalog
+ - ./src:/var/www/html/extension/oc_cartsms/src
+ - ./tests:/var/www/html/extension/oc_cartsms/tests
+ - ./vendor:/var/www/html/extension/oc_cartsms/vendor
+ - ./install.json:/var/www/html/extension/oc_cartsms/install.json
+ - ./distribution.php:/var/www/html/extension/oc_cartsms/distribution.php
+
+ db:
+ image: mysql:5.7
+ environment:
+ MYSQL_DATABASE: opencart
+ MYSQL_ROOT_PASSWORD: admin
+ volumes:
+ - db:/var/lib/mysql
+
+ adminer:
+ image: adminer
+ environment:
+ ADMINER_DESIGN: "nette"
+ ADMINER_DEFAULT_SERVER: "db"
+ ports:
+ - "8090:8080"
+
+volumes:
+ db:
\ No newline at end of file
diff --git a/install.json b/install.json
new file mode 100644
index 0000000..27887e6
--- /dev/null
+++ b/install.json
@@ -0,0 +1,7 @@
+{
+ "name": "BulkGate SMS",
+ "description": "Make your SMS messages stand out from the crowd! In a world of overflowing inboxes, SMS, RCS, and WhatsApp offer a unique way to grab your customers' attention and ensure your important notifications are seen.",
+ "version": "4.0",
+ "author": "BulkGate",
+ "link": "https://www.bulkgate.com/en/integrations/cartsms-sms-module-for-opencart/"
+}
\ No newline at end of file
diff --git a/install.php b/install.php
deleted file mode 100644
index 415ec25..0000000
--- a/install.php
+++ /dev/null
@@ -1,17 +0,0 @@
-registry);
- @$modification_controller->install();
-
- $this->load->model('extension/extension');
- $this->model_extension_extension->uninstall('module', "cartsms");
- @$this->model_extension_extension->install('module', "cartsms");
-
-die;
-
-
diff --git a/phpstan.neon b/phpstan.neon
new file mode 100644
index 0000000..95fb9c4
--- /dev/null
+++ b/phpstan.neon
@@ -0,0 +1,6 @@
+parameters:
+ level: 8
+ paths:
+ - src
+ scanDirectories:
+ - /var/www/html # opencart installation
\ No newline at end of file
diff --git a/src/Ajax/Authenticate.php b/src/Ajax/Authenticate.php
new file mode 100644
index 0000000..52b78a3
--- /dev/null
+++ b/src/Ajax/Authenticate.php
@@ -0,0 +1,27 @@
+
+ */
+ public function run(string $invalid_redirect): array
+ {
+
+ return $this->settings->load('static:application_token') === null
+ ?
+ ['redirect' => $invalid_redirect]
+ :
+ ['token' => $this->sign->authenticate(false, ['expire' => time() + 300])];
+ }
+}
\ No newline at end of file
diff --git a/src/Ajax/PluginSettings.php b/src/Ajax/PluginSettings.php
new file mode 100644
index 0000000..26f8228
--- /dev/null
+++ b/src/Ajax/PluginSettings.php
@@ -0,0 +1,67 @@
+ $unsafe_post_data
+ * @return array{data:array}
+ */
+ 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
+ */
+ 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 = Plugin\Settings\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";
+ }
+}
\ No newline at end of file
diff --git a/src/Controller/Controller.php b/src/Controller/Controller.php
new file mode 100644
index 0000000..8ff8d8d
--- /dev/null
+++ b/src/Controller/Controller.php
@@ -0,0 +1,77 @@
+ [
+ 'registry' => $this->registry,
+ 'debug' => true,
+ 'dispatcher' => Plugin\Event\Dispatcher::Asset,
+ 'api_version' => '1.0',
+ 'module_version' => '4.0',
+ /** @phpstan-ignore property.notFound */
+ 'name' => $this->model_setting_setting->getValue('config_name'),
+ 'url' => '',
+ 'gate_url' => 'http://192.168.16.1', //BulkGateWhiteLabelUrl,
+ '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" => ''
+ ]
+ ]);
+
+ $this->di_container = Factory::get();
+ }
+
+ /**
+ * @param array $parameters
+ */
+ protected function runHook(string $category, string $endpoint, Plugin\Event\Variables $variables, array $parameters = [], ?callable $success_callback = null): void
+ {
+ $hook = join('.', [$category, $endpoint]);
+ $run = true;
+
+ // automatically get admin
+ if ($this->registry->has('user') && $this->user->isLogged()) {
+ $variables['employee_id'] = $this->user->getId();
+ }
+
+ // we give chance to stop hook
+ $this->event->trigger('cartsms.hook.run', [$hook, $variables->toArray(), &$run]);
+
+ if ($run === false)
+ {
+ $this->di_container->getByClass(Plugin\Debug\Logger::class)->log("Hook event filter: 'cartsms.hook.run' stop execution of hook '$hook'", 'warning');
+ return;
+ }
+
+ $dispatcher = $this->di_container->getByClass(Plugin\Event\Dispatcher::class);
+
+ $dispatcher->dispatch($category, $endpoint, $variables, $parameters, $success_callback);
+ }
+}
\ No newline at end of file
diff --git a/src/DI/Factory.php b/src/DI/Factory.php
new file mode 100644
index 0000000..052ef98
--- /dev/null
+++ b/src/DI/Factory.php
@@ -0,0 +1,244 @@
+ $parameters
+ */
+ protected static function createContainer(array $parameters = []): Plugin\DI\Container
+ {
+ ['registry' => $registry] = $parameters;
+ //bdump($parameters);
+ //bdump($registry->has('user'), 'HAS_USER');
+
+ $iso = $registry->language->get('code');
+
+ $container = new Plugin\DI\Container($parameters['mode'] ?? 'strict');
+
+ if (($parameters['debug'] ?? false) && class_exists(Debugger::class))
+ {
+ Debugger::$strictMode = true;
+ Debugger::$maxDepth = 10;
+ Debugger::$logDirectory = DIR_LOGS;
+ Debugger::enable(Debugger::Development);
+ }
+
+ // Database
+ $container['database.connection'] = ['factory' => Database\Connection::class, 'parameters' => ['db' => $registry->get('db')]];
+
+ // Debug
+ $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'] = Plugin\Debug\Logger::class;
+ $container['debug.requirements'] = Plugin\Debug\Requirements::class;
+
+ // Ajax
+ $container['ajax.authenticate'] = Ajax\Authenticate::class;
+ $container['ajax.plugin_settings'] = Ajax\PluginSettings::class;
+
+ // Eshop
+ $container['eshop.configuration'] = ['factory' => Eshop\Configuration::class, 'factory_method' => fn () => new Eshop\Configuration($parameters['module_version'], $parameters['url'], $parameters['name'] ?? 'Store')];
+ $container['eshop.synchronizer'] = Plugin\Eshop\EshopSynchronizer::class;
+ $container['eshop.order_status'] = ['factory' => Eshop\OrderStatus::class, 'factory_method' => function() use ($registry)
+ {
+ $registry->load->model('localisation/order_status');
+
+ return new Eshop\OrderStatus($registry->model_localisation_order_status);
+ }];
+ $container['eshop.return_status'] = ['factory' => Eshop\ReturnStatus::class, 'factory_method' => function() use ($registry)
+ {
+ $registry->load->model('localisation/return_status');
+
+ return new Eshop\ReturnStatus($registry->model_localisation_return_status);
+ }];
+ $container['eshop.language'] = ['factory' => Eshop\Language::class, 'factory_method' => function() use ($registry)
+ {
+ $registry->load->model('localisation/language');
+
+ return new Eshop\Language($registry->model_localisation_language);
+ }];
+ $container['eshop.multistore'] = ['factory' => Eshop\MultiStore::class, 'factory_method' => function() use ($registry, $container)
+ {
+ $registry->load->model('setting/store');
+
+ return new Eshop\MultiStore($container->getByClass(Eshop\Configuration::class), $registry->model_setting_store);
+ }];
+
+ // Event loaders
+ $container['event.loader.extension'] = ['factory' => Event\Loader\Extension::class, 'auto_wiring' => false, 'parameters' => ['event' => $registry->event]/*, 'factory_method' => fn () => new Event\Loader\Extension($registry->event)*/];
+ $container['event.loader.shop'] = ['factory' => Event\Loader\Shop::class, 'auto_wiring' => false, 'factory_method' => function() use ($registry)
+ {
+ $registry->load->model('setting/setting');
+ $registry->load->model('localisation/language');
+
+ return new Event\Loader\Shop($registry->model_setting_setting, $registry->model_localisation_language, $registry->request);
+ }];
+ $container['event.loader.order'] = ['factory' => Event\Loader\Order::class, 'auto_wiring' => false, 'factory_method' => function() use ($registry, $container)
+ {
+ if ($registry->has('user')) //admin
+ {
+ $registry->load->model('sale/order');
+ $order_model = $registry->model_sale_order;
+ }
+ else
+ {
+ $registry->load->model('checkout/order');
+ $order_model = $registry->model_checkout_order;
+ }
+
+ return new Event\Loader\Order($order_model, $container->getByClass(Plugin\Localization\Formatter::class));
+ }];
+ $container['event.loader.order_return'] = ['factory' => Event\Loader\OrderReturn::class, 'auto_wiring' => false, 'factory_method' => function() use ($registry, $container)
+ {
+ if ($registry->has('user')) //admin
+ {
+ $registry->load->model('sale/returns');
+ $return_model = $registry->model_sale_returns;
+ }
+ else
+ {
+ $registry->load->model('account/returns');
+ $return_model = $registry->model_account_returns;
+ }
+
+ return new Event\Loader\OrderReturn($return_model, $container->getByClass(Plugin\Localization\Formatter::class));
+ }];
+ $container['event.loader.order_return_status'] = ['factory' => Event\Loader\OrderReturnStatus::class, 'auto_wiring' => false, 'factory_method' => function() use ($registry)
+ {
+ $registry->load->model('localisation/return_status');
+
+ return new Event\Loader\OrderReturnStatus($registry->model_localisation_return_status);
+ }];
+ $container['event.loader.order_status'] = ['factory' => Event\Loader\OrderStatus::class, 'auto_wiring' => false, 'factory_method' => function() use ($registry)
+ {
+ $registry->load->model('localisation/order_status');
+
+ return new Event\Loader\OrderStatus($registry->model_localisation_order_status);
+ }];
+ $container['event.loader.customer'] = ['factory' => Event\Loader\Customer::class, 'auto_wiring' => false, 'factory_method' => function() use ($registry)
+ {
+ if ($registry->has('user')) //admin
+ {
+ $registry->load->model('customer/customer');
+ $customer_model = $registry->model_customer_customer;
+ }
+ else
+ {
+ $registry->load->model('account/customer');
+ $registry->load->model('account/address');
+
+ $customer_model = new class ($registry->model_account_customer, $registry->model_account_address) {
+ private $customer_id;
+
+ public function __construct(private $customer_model, private $address_model)
+ {
+ }
+
+ public function getCustomer(int $customer_id): array
+ {
+ $this->customer_id = $customer_id;
+ return $this->customer_model->getCustomer($customer_id);
+ }
+
+ public function getAddress(int $address_id): array
+ {
+ return $this->address_model->getAddress($this->customer_id, $address_id);
+ }
+ };
+ }
+
+ return new Event\Loader\Customer($customer_model);
+ }];
+ $container['event.loader.admin'] = ['factory' => Event\Loader\Admin::class, 'auto_wiring' => false, 'factory_method' => function() use ($registry)
+ {
+ $registry->load->model('user/user');
+
+ return new Event\Loader\Admin($registry->model_user_user);
+ }];
+ $container['event.loader.product'] = ['factory' => Event\Loader\Product::class, 'auto_wiring' => false, 'factory_method' => function() use ($registry, $container)
+ {
+ $registry->load->model('catalog/product');
+ $registry->load->model('catalog/manufacturer');
+
+ return new Event\Loader\Product($registry->model_catalog_product, $registry->model_catalog_manufacturer, $container->getByClass(Plugin\Localization\Formatter::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;
+
+ $container['event.loader'] = ['factory' => Plugin\Event\Loader::class, 'factory_method' => function () use ($registry, $container)
+ {
+ $loaders = [
+ $container->getByClass(Event\Loader\OrderReturn::class),
+ $container->getByClass(Event\Loader\Order::class),
+ $container->getByClass(Event\Loader\OrderStatus::class),
+ $container->getByClass(Event\Loader\Customer::class),
+ $container->getByClass(Event\Loader\Product::class),
+ $container->getByClass(Event\Loader\Shop::class),
+ ];
+
+ if ($registry->has('user')) //admin
+ {
+ $loaders[] = $container->getByClass(Event\Loader\OrderReturnStatus::class);
+ $loaders[] = $container->getByClass(Event\Loader\Admin::class);
+ }
+
+ $loaders[] = $container->getByClass(Event\Loader\Extension::class);
+
+ return new Plugin\Event\Loader($loaders);
+ }];
+ $container['event.dispatcher'] = Plugin\Event\Dispatcher::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
+ $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.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.repository.synchronizer'] = Plugin\Settings\Repository\SynchronizationDatabase::class;
+ $container['settings.synchronizer'] = Plugin\Settings\Synchronizer::class;
+
+ // User
+ $container['user.sign'] = Plugin\User\Sign::class;
+
+ return $container;
+ }
+}
\ No newline at end of file
diff --git a/src/Database/Connection.php b/src/Database/Connection.php
new file mode 100644
index 0000000..5abf94e
--- /dev/null
+++ b/src/Database/Connection.php
@@ -0,0 +1,74 @@
+
+ */
+ private array $sql = [];
+
+ public function __construct(OpencartDB $db)
+ {
+ $this->db = $db;
+ }
+
+ public function execute(string $sql): Plugin\Database\ResultCollection
+ {
+ $output = new Plugin\Database\ResultCollection();
+
+ $this->sql[] = $sql;
+
+ $result = (array) $this->db->query($sql);
+
+ foreach ($result['rows'] ?? [] as $key => $item) {
+ $output[$key] = (array) $item;
+ }
+
+ return $output;
+ }
+
+ public function lastId(): int
+ {
+ return $this->db->getLastId();
+ }
+
+ public function prefix(): string
+ {
+ return DB_PREFIX;
+ }
+
+ public function getSqlList(): array
+ {
+ return $this->sql;
+ }
+
+ public function table(string $table): string
+ {
+ return $this->prefix() . $table;
+ }
+
+ public function prepare(string $sql, ...$parameters): string
+ {
+ return sprintf($sql, ...array_map(fn ($value) => "'$value'", array_map([$this->db, 'escape'], $parameters)));
+ }
+
+ public function escape(string $string): string
+ {
+ return $this->db->escape($string);
+ }
+}
diff --git a/src/Eshop/Configuration.php b/src/Eshop/Configuration.php
new file mode 100644
index 0000000..42527d8
--- /dev/null
+++ b/src/Eshop/Configuration.php
@@ -0,0 +1,53 @@
+version_number = $version_number;
+ $this->site_url = $site_url;
+ $this->site_name = $site_name;
+ }
+
+
+ public function url(): string
+ {
+ return $this->site_url;
+ }
+
+
+ public function product(): string
+ {
+ return 'oc';
+ }
+
+
+ public function version(): string
+ {
+ return $this->version_number;
+ }
+
+
+ public function name(): string
+ {
+ return $this->site_name;
+ }
+}
\ No newline at end of file
diff --git a/src/Eshop/Language.php b/src/Eshop/Language.php
new file mode 100644
index 0000000..91d3042
--- /dev/null
+++ b/src/Eshop/Language.php
@@ -0,0 +1,46 @@
+language->getLanguages() as $language)
+ {
+ $language_list[$language['code']] = $language['name'];
+ }
+
+ return $language_list;
+ }
+
+
+ public function get(?int $id = 1): string
+ {
+ return $this->language->getLanguage((int) $id)['code'];
+ }
+
+
+ public function hasMultiLanguageSupport(): bool
+ {
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/Eshop/MultiStore.php b/src/Eshop/MultiStore.php
new file mode 100644
index 0000000..02855ea
--- /dev/null
+++ b/src/Eshop/MultiStore.php
@@ -0,0 +1,35 @@
+ $this->configuration->name()];
+
+ foreach($this->store->getStores() as $store)
+ {
+ $store_list[$store['store_id']] = $store['name'];
+ }
+
+ return $store_list;
+ }
+}
\ No newline at end of file
diff --git a/src/Eshop/OrderStatus.php b/src/Eshop/OrderStatus.php
new file mode 100644
index 0000000..4e11bc6
--- /dev/null
+++ b/src/Eshop/OrderStatus.php
@@ -0,0 +1,32 @@
+order_status->getOrderStatuses() as $status)
+ {
+ $status_list[$status['order_status_id']] = $status['name'];
+ }
+
+ return $status_list;
+ }
+}
\ No newline at end of file
diff --git a/src/Eshop/ReturnStatus.php b/src/Eshop/ReturnStatus.php
new file mode 100644
index 0000000..8c5670b
--- /dev/null
+++ b/src/Eshop/ReturnStatus.php
@@ -0,0 +1,33 @@
+return_status->getReturnStatuses() as $return_status) {
+ $return_status_list[$return_status['return_status_id']] = $return_status['name'];
+ }
+
+ return $return_status_list;
+ }
+}
\ No newline at end of file
diff --git a/src/Event/Helpers.php b/src/Event/Helpers.php
new file mode 100644
index 0000000..e29357f
--- /dev/null
+++ b/src/Event/Helpers.php
@@ -0,0 +1,33 @@
+ $priority
+ * @param ArrayAccess|array $values
+ */
+ public static function priorityValues(array $priority, ArrayAccess|array $values, mixed $default = null): mixed
+ {
+ foreach ($priority as $key)
+ {
+ if ($values[$key] ?? false)
+ {
+ return $values[$key];
+ }
+ }
+
+ return $default;
+ }
+}
diff --git a/src/Event/Loader/Admin.php b/src/Event/Loader/Admin.php
new file mode 100644
index 0000000..2762895
--- /dev/null
+++ b/src/Event/Loader/Admin.php
@@ -0,0 +1,28 @@
+admin_model->getUser((int) $variables['employee_id']);
+
+ $variables['employee_email'] = $admin['email'];
+ $variables['employee_firstname'] = $admin['firstname'];
+ $variables['employee_lastname'] = $admin['lastname'];
+ }
+}
\ No newline at end of file
diff --git a/src/Event/Loader/Customer.php b/src/Event/Loader/Customer.php
new file mode 100644
index 0000000..bbf46d7
--- /dev/null
+++ b/src/Event/Loader/Customer.php
@@ -0,0 +1,54 @@
+customer_model->getCustomer((int) $variables['customer_id']);
+
+ $variables['shop_id'] ??= $customer['store_id'] ?? null;
+ $variables['lang_id'] ??= $customer['language_id'] ?? null;
+
+ $variables['customer_mobile'] ??= $customer['telephone'];
+ $variables['customer_email'] ??= $customer['email'];
+
+ /*$billing = $this->customer_model->getAddress((int) $variables['id_address_invoice']);
+ $shipping = $this->customer_model->getAddress((int) $variables['id_address_delivery']);
+
+ $variables['customer_firstname'] = Plugin\Event\Helpers::address('firstname', $shipping, $billing);
+ $variables['customer_lastname'] = Plugin\Event\Helpers::address('lastname', $shipping, $billing);
+ $variables['customer_company'] = Plugin\Event\Helpers::address('company', $shipping, $billing);
+ $variables['customer_address'] = Plugin\Event\Helpers::joinStreet('address_1', 'address_2', $shipping, $billing);
+ $variables['customer_city'] = Plugin\Event\Helpers::address('city', $shipping, $billing);
+ $variables['customer_state'] = Plugin\Event\Helpers::address('zone', $shipping, $billing);
+ $variables['customer_postcode'] = Plugin\Event\Helpers::address('postcode', $shipping, $billing);
+ $variables['customer_country'] = Plugin\Event\Helpers::address('country', $shipping, $billing);
+ $variables['customer_country_id'] = Plugin\Utils\Strings::lower(Plugin\Event\Helpers::address('iso_code_2', $shipping, $billing) ?? "");
+
+ $variables['customer_invoice_firstname'] = Plugin\Event\Helpers::address('firstname', $billing, $shipping);
+ $variables['customer_invoice_lastname'] = Plugin\Event\Helpers::address('lastname', $billing, $shipping);
+ $variables['customer_invoice_company'] = Plugin\Event\Helpers::address('company', $billing, $shipping);
+ $variables['customer_invoice_address'] = Plugin\Event\Helpers::joinStreet('address_1', 'address_2', $billing, $shipping);
+ $variables['customer_invoice_city'] = Plugin\Event\Helpers::address('city', $billing, $shipping);
+ $variables['customer_invoice_state'] = Plugin\Event\Helpers::address('zone', $billing, $shipping);
+ $variables['customer_invoice_postcode'] = Plugin\Event\Helpers::address('postcode', $billing, $shipping);
+ $variables['customer_invoice_country'] = Plugin\Event\Helpers::address('country', $billing, $shipping);
+ $variables['customer_invoice_country_id'] = Plugin\Utils\Strings::lower(Plugin\Event\Helpers::address('iso_code_2', $billing, $shipping) ?? "");*/
+
+ }
+}
\ No newline at end of file
diff --git a/src/Event/Loader/Extension.php b/src/Event/Loader/Extension.php
new file mode 100644
index 0000000..8fe5aa4
--- /dev/null
+++ b/src/Event/Loader/Extension.php
@@ -0,0 +1,20 @@
+event->trigger('cartsms.hook.extension', [$variables, $parameters]);
+ }
+}
\ No newline at end of file
diff --git a/src/Event/Loader/Order.php b/src/Event/Loader/Order.php
new file mode 100644
index 0000000..7d5c3e0
--- /dev/null
+++ b/src/Event/Loader/Order.php
@@ -0,0 +1,134 @@
+order_model->getOrder((int) $variables['order_id']);
+
+ $shipping_address = $this->address($order, 'shipping_');
+ $billing_address = $this->address($order, 'payment_');
+
+ foreach ($shipping_address as $key => $value) {
+ if ($key === 'address_2') {
+ continue;
+ }
+ if ($key === 'address_1') {
+ $variables["customer_address"] = Plugin\Event\Helpers::joinStreet('address_1', 'address_2', $shipping_address, $billing_address);
+ $variables["customer_invoice_address"] = Plugin\Event\Helpers::joinStreet('address_1', 'address_2', $billing_address, $shipping_address);
+ continue;
+ }
+ $variables["customer_{$key}"] = Plugin\Event\Helpers::address($key, $shipping_address, $billing_address);
+ $variables["customer_invoice_{$key}"] = Plugin\Event\Helpers::address($key, $billing_address, $shipping_address);
+ }
+
+ $variables['customer_mobile'] = $order['telephone'];
+ $variables['customer_email'] = $order['email'];
+
+ $variables['shop_id'] ??= $order['store_id'] ?? null;
+ $variables['lang_id'] ??= $order['language_id'] ?? null;
+ $variables['customer_id'] ??= $order['customer_id'] ?? null;
+ $variables['id_address_delivery'] ??= $order['shipping_address_id'] ?? null;
+ $variables['id_address_invoice'] ??= $order['payment_address_id'] ?? null;
+ $variables['order_status_id'] ??= $order['order_status_id'] ?? null;
+
+ $variables['order_currency'] = $order['currency_code'];
+ $variables['long_order_id'] = sprintf("%06d", $variables['order_id']);
+ $variables['order_total_locale'] = $this->formatter->format('price', $order['total'], $variables['order_currency']);
+ $variables['order_total_paid'] = $order['total'];
+ $variables['order_payment'] = $order['payment_method']['name'];
+ $variables['order_tracking'] = $order['tracking'];
+ $variables['order_message'] = $order['comment'];
+
+ $timestamp = strtotime($order['date_added']) ?: time();
+
+ $variables['order_date'] = $this->formatter->format('date', $order['date_added']);
+ $variables['order_date1'] = date('d.m.Y', $timestamp);
+ $variables['order_date2'] = date('d/m/Y', $timestamp);
+ $variables['order_date3'] = date('d-m-Y', $timestamp);
+ $variables['order_date4'] = date('Y-m-d', $timestamp);
+ $variables['order_date5'] = date('m.d.Y', $timestamp);
+ $variables['order_date6'] = date('m/d/Y', $timestamp);
+ $variables['order_date7'] = date('m-d-Y', $timestamp);
+ $variables['order_datetime'] = $this->formatter->format('datetime', $order['date_added']);
+ $variables['order_time'] = $this->formatter->format('time', $order['date_added']);
+ $variables['order_time1'] = date('H:i:s', $timestamp);
+
+ $variables['order_carrier_name'] = $order['shipping_method']['name'] ?? null;
+ $variables['order_carrier_price'] = $order['shipping_method']['cost'] ?? null;
+ $variables['order_carrier_price_locale'] = $this->formatter->format('price', $variables['order_carrier_price'], $variables['order_currency']);
+ $variables['order_carrier_code'] = $order['shipping_method']['code'] ?? null;
+
+ $v1 = $v2 = $v3 = $v4 = $p1 = $p2 = [];
+
+ foreach ($order['products'] as $product)
+ {
+ $qty = $product['quantity'];
+ $name = $product['name'];
+ $model = $product['model'];
+ $total = $product['total'] + $product['tax'];
+
+ $product_id = $product['order_product_id'];
+ $total_formatted = $this->formatter->format('price', $total, $variables['order_currency']);
+
+ $v1[] = "{$qty}x $name $model $total_formatted";
+ $v2[] = "{$qty}x $name $total_formatted";
+ $v3[] = "{$qty}x ($product_id) $name $model $total_formatted";
+ $v4[] = "{$qty}x $model $total_formatted";
+
+ $p1[] = "$qty,$name,$total";
+ $p2[] = "$qty;$name;$total";
+ }
+
+ $variables['order_products1'] = implode('; ', $v1);
+ $variables['order_products2'] = implode('; ', $v2);
+ $variables['order_products3'] = implode('; ', $v3);
+ $variables['order_products4'] = implode('; ', $v4);
+
+ $variables['order_products5'] = implode("\n", $v1);
+ $variables['order_products6'] = implode("\n", $v2);
+ $variables['order_products7'] = implode("\n", $v3);
+ $variables['order_products8'] = implode("\n", $v4);
+
+ $variables['order_smsprinter1'] = implode(';', $p1);
+ $variables['order_smsprinter2'] = implode(';', $p2);
+ }
+
+ /**
+ * @param array $order
+ * @return array
+ */
+ private function address(array $order, string $order_prefix): array
+ {
+ $address = [];
+
+ $address['firstname'] = $order["{$order_prefix}firstname"];
+ $address['lastname'] = $order["{$order_prefix}lastname"];
+ $address['company'] = $order["{$order_prefix}company"];
+ $address['address_1'] = $order["{$order_prefix}address_1"];
+ $address['address_2'] = $order["{$order_prefix}address_2"] ?? '';
+ $address['city'] = $order["{$order_prefix}city"];
+ $address['state'] = $order["{$order_prefix}zone"];
+ $address['postcode'] = $order["{$order_prefix}postcode"];
+ $address['country'] = $order["{$order_prefix}country"];
+ $address['country_id'] = Plugin\Utils\Strings::lower($order["{$order_prefix}iso_code_2"] ?? "");
+
+ return $address;
+
+ }
+}
\ No newline at end of file
diff --git a/src/Event/Loader/OrderReturn.php b/src/Event/Loader/OrderReturn.php
new file mode 100644
index 0000000..e629382
--- /dev/null
+++ b/src/Event/Loader/OrderReturn.php
@@ -0,0 +1,44 @@
+order_return_model->getReturn((int) $variables['return_id']);
+
+ $variables['lang_id'] ??= $order_return['language_id'] ?? null;
+ $variables['order_id'] ??= $order_return['order_id'] ?? null;
+ $variables['customer_id'] ??= $order_return['customer_id'] ?? null;
+ $variables['return_status_id'] ??= $order_return['return_status_id'] ?? null;
+
+ $variables['return_customer_message'] = $order_return['comment'];
+ $variables['return_date'] = $this->formatter->format('date', $order_return['date_added']);
+
+ // pro customera + administratora
+ $variables['return_action'] = $order_return['action'] ?? null;
+ $variables['return_action_id'] = $order_return['return_action_id'];
+ $variables['return_reason_id'] = $order_return['return_reason_id'];
+ $variables['return_reason'] = $order_return['reason'] ?? null;
+
+ //pouze pro administratora
+ $variables['return_status'] = $order_return['return_status'] ?? null;
+
+ $variables['return_products1'] = $order_return['quantity'] . 'x ' . $order_return['product'] . ' ' . $order_return['product_id'];
+ $variables['return_products2'] = $order_return['quantity'] . 'x ' . $order_return['product'];
+ $variables['return_products3'] = $order_return['quantity'] . 'x (' . $order_return['product_id'] . ') ' . $order_return['product'];
+ $variables['return_products4'] = $order_return['quantity'] . 'x ' . $order_return['product_id'];
+ }
+}
\ No newline at end of file
diff --git a/src/Event/Loader/OrderReturnStatus.php b/src/Event/Loader/OrderReturnStatus.php
new file mode 100644
index 0000000..ab23c60
--- /dev/null
+++ b/src/Event/Loader/OrderReturnStatus.php
@@ -0,0 +1,26 @@
+order_return_status_model->getReturnStatus((int) $variables['return_status_id']);
+
+ $variables['return_status'] = $return_status['name'];
+ }
+}
\ No newline at end of file
diff --git a/src/Event/Loader/OrderStatus.php b/src/Event/Loader/OrderStatus.php
new file mode 100644
index 0000000..60c4e64
--- /dev/null
+++ b/src/Event/Loader/OrderStatus.php
@@ -0,0 +1,26 @@
+order_status_model->getOrderStatus((int) $variables['order_status_id']);
+
+ $variables['order_status'] = $status['name'];
+ }
+}
\ No newline at end of file
diff --git a/src/Event/Loader/Product.php b/src/Event/Loader/Product.php
new file mode 100644
index 0000000..37a07c6
--- /dev/null
+++ b/src/Event/Loader/Product.php
@@ -0,0 +1,50 @@
+product_model->getProduct((int) $variables['product_id']);
+
+ $variables['shop_id'] ??= $product['store_id'] ?? null;
+ $variables['lang_id'] ??= $product['language_id'] ?? null;
+
+ $variables['product_quantity'] = $product['quantity'];
+ $variables['product_minimal_quantity'] = $product['minimum'];
+ $variables['product_name'] = $product['name'];
+ $variables['product_model'] = $product['model'];
+ $variables['product_description'] = \strip_tags(html_entity_decode($product['description']));
+ $manufacturer_id = (int) $product['manufacturer_id'];
+
+ if ($manufacturer_id) {
+ $variables['product_manufacturer'] = $this->manufacturer_model->getManufacturer($manufacturer_id)['name'];
+ }
+
+ //todo: TAX - ceny jsou uvedeny bez DPH
+ $variables['product_price'] = $product['price'];
+ $variables['product_price_locale'] = $this->formatter->format('price', (float) $product['price'], $variables['order_currency'] ?? $variables['shop_currency']);
+
+ $variables['product_ean'] = $product['ean'];
+ $variables['product_upc'] = $product['upc'];
+ $variables['product_isbn'] = $product['isbn'];
+ $variables['product_jan'] = $product['jan'];
+ $variables['product_mpn'] = $product['mpn'];
+ $variables['product_sku'] = $product['sku'];
+ }
+}
\ No newline at end of file
diff --git a/src/Event/Loader/Shop.php b/src/Event/Loader/Shop.php
new file mode 100644
index 0000000..21b3460
--- /dev/null
+++ b/src/Event/Loader/Shop.php
@@ -0,0 +1,35 @@
+settings_model->getSetting('config', (int) $variables['shop_id']);
+
+ $variables['shop_email'] = $settings['config_email'];
+ $variables['shop_name'] = $settings['config_name'];
+ $variables['shop_domain'] ??= $settings['config_url'] ?? ""; //?? HTTP_CATALOG; //todo: shop 0 tuto polozku nema ... tady musime vzit aktualni url?
+ $variables['shop_currency'] = $settings['config_currency'];
+ $variables['shop_phone'] = $settings['config_telephone'];
+ $variables['lang_id'] ??= ($this->language_model->getLanguageByCode($this->request->get['language'] ?? $this->request->cookie['language'] ?? $settings['config_language_catalog']))['language_id'];
+ $variables['lang_iso'] = $this->language_model->getLanguage((int) $variables['lang_id'])['code'];
+ }
+}
\ No newline at end of file
diff --git a/src/Event/State.php b/src/Event/State.php
new file mode 100644
index 0000000..576b9a4
--- /dev/null
+++ b/src/Event/State.php
@@ -0,0 +1,91 @@
+loader = $loader;
+ }
+
+ public function setExpected(mixed $expected): self
+ {
+ $this->expected = $expected;
+
+ return $this;
+ }
+
+ public function captureInitial(): self
+ {
+ $this->initial = $this->callLoader();
+
+ return $this;
+ }
+
+ public function captureActual(): self
+ {
+ $this->actual = $this->callLoader();
+
+ return $this;
+ }
+
+ public function shouldRunHook(): bool
+ {
+ return $this->isExpected() && $this->isChanged();
+ }
+
+ public function getActual(): mixed
+ {
+ return $this->actual;
+ }
+
+ public function getInitial(): mixed
+ {
+ return $this->initial;
+ }
+
+ public function getExpected(): mixed
+ {
+ return $this->expected;
+ }
+
+ private function callLoader(mixed ...$loader_params): mixed
+ {
+ return call_user_func($this->loader, ...$loader_params);
+ }
+
+ public function isExpected(): bool
+ {
+ return $this->expected === $this->actual;
+ }
+
+ public function isChanged(): bool
+ {
+ return $this->initial !== $this->actual;
+ }
+
+ /** @return array{initial: mixed, expected: mixed, actual: mixed} */
+ public function debug(): array
+ {
+ return [
+ 'initial' => $this->initial,
+ 'expected' => $this->expected,
+ 'actual' => $this->actual,
+ ];
+ }
+}
\ No newline at end of file
diff --git a/tests/Database/ConnectionTest.phpt b/tests/Database/ConnectionTest.phpt
new file mode 100644
index 0000000..f40fdbe
--- /dev/null
+++ b/tests/Database/ConnectionTest.phpt
@@ -0,0 +1,92 @@
+shouldReceive('query')->with('SQL')->once()->andReturn([
+ 'rows' => [
+ ['id' => 4],
+ ['id' => 5]
+ ]
+ ]);
+
+ [$e1, $e2] = $connection->execute('SQL')->toArray();
+
+ Assert::same([['id' => 4], ['id' => 5]], [$e1->toArray(), $e2->toArray()]);
+ Assert::same(['SQL'], $connection->getSqlList());
+ }
+
+
+ public function testPrepare(): void
+ {
+ $connection = new Connection($db = Mockery::mock(DB::class));
+
+ $db->shouldReceive('escape')->with('hello')->once()->andReturn('hello');
+
+ Assert::same("SQL ('hello')", $connection->prepare('SQL (%s)', 'hello'));
+ }
+
+
+ public function testLastId(): void
+ {
+ $connection = new Connection($db = Mockery::mock(DB::class));
+ $db->shouldReceive('getLastId')->withNoArgs()->once()->andReturn(451);
+
+ Assert::same(451, $connection->lastId());
+ }
+
+
+ public function testEscape(): void
+ {
+ $connection = new Connection($db = Mockery::mock(DB::class));
+ $db->shouldReceive('escape')->with('dangerous string')->once()->andReturn('safe string');
+
+ Assert::same('safe string', $connection->escape('dangerous string'));
+ }
+
+
+ public function testPrefix(): void
+ {
+ $connection = new Connection(Mockery::mock(DB::class));
+
+ Assert::same('oc_', $connection->prefix());
+ }
+
+
+ public function testTable(): void
+ {
+ $connection = new Connection(Mockery::mock(DB::class));
+
+ Assert::same('oc_users', $connection->table('users'));
+ }
+
+
+ public function tearDown(): void
+ {
+ Mockery::close();
+ }
+}
+
+(new ConnectionTest())->run();
diff --git a/tests/Eshop/Configuration.phpt b/tests/Eshop/Configuration.phpt
new file mode 100644
index 0000000..f4bda9c
--- /dev/null
+++ b/tests/Eshop/Configuration.phpt
@@ -0,0 +1,24 @@
+version());
+ Assert::same('https://www.example.com', $configuration->url());
+ Assert::same('Example Store', $configuration->name());
+ Assert::same('oc', $configuration->product());
+ }
+}
+
+(new ConfigurationTest())->run();
\ No newline at end of file
diff --git a/tests/Eshop/Language.phpt b/tests/Eshop/Language.phpt
new file mode 100644
index 0000000..0601829
--- /dev/null
+++ b/tests/Eshop/Language.phpt
@@ -0,0 +1,51 @@
+shouldReceive('getLanguages')->withNoArgs()->andReturn([
+ ['code' => 'en-gb', 'name' => 'English'],
+ ['code' => 'cs-cz', 'name' => 'Czech'],
+ ]);
+
+ $language_loader = new Language($language_model);
+
+ Assert::same([
+ 'en-gb' => 'English',
+ 'cs-cz' => 'Czech',
+ ], $language_loader->load());
+ }
+
+ public function testMultiLanguageSupport(): void
+ {
+ $language_model = Mockery::mock(\Opencart\Admin\Model\Localisation\Language::class);
+ $language_loader = new Language($language_model);
+
+ Assert::true($language_loader->hasMultiLanguageSupport());
+ }
+
+ public function testGetLanguage(): void
+ {
+ $language_model = Mockery::mock(\Opencart\Admin\Model\Localisation\Language::class);
+ $language_model->shouldReceive('getLanguage')->with(Mockery::on(fn ($arg) => $arg === 1))->andReturn(['code' => 'en-gb']);
+
+ $language_loader = new Language($language_model);
+
+ Assert::same('en-gb', $language_loader->get());
+ Assert::same('en-gb', $language_loader->get(1));
+ Assert::same('en-gb', $language_loader->get("1"));
+ }
+}
+
+(new LanguageTest())->run();
\ No newline at end of file
diff --git a/tests/Eshop/MultiStore.phpt b/tests/Eshop/MultiStore.phpt
new file mode 100644
index 0000000..1ba7bd0
--- /dev/null
+++ b/tests/Eshop/MultiStore.phpt
@@ -0,0 +1,34 @@
+shouldReceive('name')->withNoArgs()->andReturn('Store');
+
+ $store_model = Mockery::mock(\Opencart\Admin\Model\Setting\Store::class);
+ $store_model->shouldReceive('getStores')->withNoArgs()->andReturn([
+ ['store_id' => 1, 'name' => 'Store 2'],
+ ]);
+
+ $multistore_loader = new MultiStore($configuration, $store_model);
+
+ Assert::same([
+ 0 => 'Store',
+ 1 => 'Store 2',
+ ], $multistore_loader->load());
+ }
+}
+
+(new MultiStoreTest())->run();
\ No newline at end of file
diff --git a/tests/Eshop/OrderStatus.phpt b/tests/Eshop/OrderStatus.phpt
new file mode 100644
index 0000000..9cc38b1
--- /dev/null
+++ b/tests/Eshop/OrderStatus.phpt
@@ -0,0 +1,31 @@
+shouldReceive('getOrderStatuses')->withNoArgs()->andReturn([
+ ['order_status_id' => 1, 'name' => 'Pending'],
+ ['order_status_id' => 2, 'name' => 'Completed'],
+ ]);
+
+ $order_status_loader = new OrderStatus($order_status_model);
+
+ Assert::same([
+ 1 => 'Pending',
+ 2 => 'Completed',
+ ], $order_status_loader->load());
+ }
+}
+
+(new OrderStatusTest())->run();
\ No newline at end of file
diff --git a/tests/Eshop/ReturnStatus.phpt b/tests/Eshop/ReturnStatus.phpt
new file mode 100644
index 0000000..4b07262
--- /dev/null
+++ b/tests/Eshop/ReturnStatus.phpt
@@ -0,0 +1,31 @@
+shouldReceive('getReturnStatuses')->withNoArgs()->andReturn([
+ ['return_status_id' => 1, 'name' => 'Pending'],
+ ['return_status_id' => 2, 'name' => 'Completed'],
+ ]);
+
+ $return_status_loader = new ReturnStatus($return_status_model);
+
+ Assert::same([
+ 1 => 'Pending',
+ 2 => 'Completed',
+ ], $return_status_loader->load());
+ }
+}
+
+(new ReturnStatusTest())->run();
\ No newline at end of file
diff --git a/tests/Event/Helpers.phpt b/tests/Event/Helpers.phpt
new file mode 100644
index 0000000..bb38c96
--- /dev/null
+++ b/tests/Event/Helpers.phpt
@@ -0,0 +1,34 @@
+ 'value1',
+ 'key2' => null,
+ ];
+ $value_array_access = new Plugin\Event\Variables($value_array);
+
+ Assert::same('value1', Helpers::priorityValues(['key1', 'key2'], $value_array));
+ Assert::same('value1', Helpers::priorityValues(['key2', 'key1'], $value_array));
+ Assert::same('value1', Helpers::priorityValues(['key3', 'key1'], $value_array));
+ Assert::same('default_value', Helpers::priorityValues(['key2', 'key3'], $value_array, 'default_value'));
+
+ Assert::same('value1', Helpers::priorityValues(['key1', 'key2'], $value_array_access));
+ Assert::same('value1', Helpers::priorityValues(['key2', 'key1'], $value_array_access));
+ Assert::same('value1', Helpers::priorityValues(['key3', 'key1'], $value_array_access));
+ Assert::same('default_value', Helpers::priorityValues(['key2', 'key3'], $value_array_access, 'default_value'));
+ }
+}
+
+(new HelpersTest())->run();
\ No newline at end of file
diff --git a/tests/Event/Loader/Admin.phpt b/tests/Event/Loader/Admin.phpt
new file mode 100644
index 0000000..0064c08
--- /dev/null
+++ b/tests/Event/Loader/Admin.phpt
@@ -0,0 +1,53 @@
+shouldReceive('getUser')->with(Mockery::on(fn ($arg) => $arg === 1))->andReturn([
+ 'firstname' => 'John',
+ 'lastname' => 'Doe',
+ 'email' => 'user@example.com'
+ ]);
+
+ $admin_loader = new \BulkGate\CartSms\Event\Loader\Admin($admin_model);
+ $admin_loader->load($variables = new Plugin\Event\Variables(['employee_id' => 1]));
+ $admin_loader->load(new Plugin\Event\Variables(['employee_id' => "1"]));
+
+ Assert::same([
+ 'employee_id' => 1,
+ 'employee_email' => 'user@example.com',
+ 'employee_firstname' => 'John',
+ 'employee_lastname' => 'Doe'
+ ], $variables->toArray());
+ }
+
+ public function testNoLoad(): void
+ {
+ $admin_model = Mockery::mock(\Opencart\Admin\Model\User\User::class);
+ $admin_model->shouldNotReceive('getUser');
+
+ $admin_loader = new \BulkGate\CartSms\Event\Loader\Admin($admin_model);
+ $admin_loader->load($variables = new Plugin\Event\Variables(['shop_id' => 2, 'lang_id' => 3]));
+
+ Assert::same(['shop_id' => 2, 'lang_id' => 3], $variables->toArray());
+ }
+
+ public function tearDown(): void
+ {
+ Mockery::close();
+ }
+}
+
+(new AdminTest())->run();
\ No newline at end of file
diff --git a/tests/Event/Loader/Customer.phpt b/tests/Event/Loader/Customer.phpt
new file mode 100644
index 0000000..46273f5
--- /dev/null
+++ b/tests/Event/Loader/Customer.phpt
@@ -0,0 +1,79 @@
+shouldReceive('getCustomer')->with(Mockery::on(fn ($arg) => $arg === 1))->andReturn([
+ 'store_id' => 1,
+ 'language_id' => 1,
+ 'telephone' => '777888999',
+ 'email' => 'user@example.com'
+ ]);
+
+ $customer_loader = new \BulkGate\CartSms\Event\Loader\Customer($customer_model);
+ $customer_loader->load($variables = new Plugin\Event\Variables(['customer_id' => 1]));
+ $customer_loader->load(new Plugin\Event\Variables(['customer_id' => "1"]));
+
+ Assert::same([
+ 'customer_id' => 1,
+ 'shop_id' => 1,
+ 'lang_id' => 1,
+ 'customer_mobile' => '777888999',
+ 'customer_email' => 'user@example.com'
+ ], $variables->toArray());
+ }
+
+ public function testOverwrite()
+ {
+ $customer_model = Mockery::mock(\Opencart\Catalog\Model\Account\Customer::class);
+ $customer_model->shouldReceive('getCustomer')->with(1)->once()->andReturn([
+ 'store_id' => 1,
+ 'language_id' => 1,
+ 'telephone' => '777888999',
+ 'email' => 'user@example.com'
+ ]);
+
+ $customer_loader = new \BulkGate\CartSms\Event\Loader\Customer($customer_model);
+ $customer_loader->load($variables = new Plugin\Event\Variables([
+ 'customer_id' => 1,
+ 'shop_id' => 2,
+ 'lang_id' => 3,
+ 'customer_mobile' => '111',
+ 'customer_email' => 'test@opencart.com'
+ ]));
+
+ Assert::same(1, $variables['customer_id']);
+ Assert::same(2, $variables['shop_id']);
+ Assert::same(3, $variables['lang_id']);
+ }
+
+ public function testNoLoad(): void
+ {
+ $customer_model = Mockery::mock(\Opencart\Catalog\Model\Account\Customer::class);
+ $customer_model->shouldNotReceive('getCustomer');
+
+ $customer_loader = new \BulkGate\CartSms\Event\Loader\Customer($customer_model);
+ $customer_loader->load($variables = new Plugin\Event\Variables(['shop_id' => 2, 'lang_id' => 3]));
+
+ Assert::same(['shop_id' => 2, 'lang_id' => 3], $variables->toArray());
+ }
+
+ public function tearDown(): void
+ {
+ Mockery::close();
+ }
+}
+
+(new CustomerTest())->run();
\ No newline at end of file
diff --git a/tests/Event/Loader/Extension.phpt b/tests/Event/Loader/Extension.phpt
new file mode 100644
index 0000000..0f9e442
--- /dev/null
+++ b/tests/Event/Loader/Extension.phpt
@@ -0,0 +1,35 @@
+shouldReceive('trigger')
+ ->with('cartsms.hook.extension', Mockery::on(fn ($args) => $args[0] instanceof Plugin\Event\Variables && $args[1] === []))
+ ->once();
+
+ $extension_loader = new Loader\Extension($event);
+ $extension_loader->load($variables = new Plugin\Event\Variables(['shop_id' => 1, 'lang_id' => 1]));
+
+ Assert::same(['shop_id' => 1, 'lang_id' => 1], $variables->toArray());
+ }
+
+ public function tearDown(): void
+ {
+ Mockery::close();
+ }
+}
+
+(new ExtensionTest())->run();
\ No newline at end of file
diff --git a/tests/Event/Loader/Order.phpt b/tests/Event/Loader/Order.phpt
new file mode 100644
index 0000000..6a88cec
--- /dev/null
+++ b/tests/Event/Loader/Order.phpt
@@ -0,0 +1,238 @@
+shouldReceive('getOrder')->with(Mockery::on(fn ($arg) => $arg === 1))->andReturn([
+ 'store_id' => 1,
+ 'language_id' => 1,
+ 'customer_id' => 1,
+ 'shipping_address_id' => 1,
+ 'payment_address_id' => 1,
+ 'order_status_id' => 1,
+
+ 'shipping_firstname' => 'firstname_shipping',
+ 'shipping_lastname' => 'lastname_shipping',
+ 'shipping_company' => 'company_shipping',
+ 'shipping_address_1' => 'address_1_shipping',
+ 'shipping_city' => 'city_shipping',
+ 'shipping_zone' => 'state_shipping',
+ 'shipping_postcode' => 'postcode_shipping',
+ 'shipping_country' => 'country_shipping',
+ 'shipping_iso_code_2' => 'iso_code_shipping',
+
+ 'payment_firstname' => 'firstname_payment',
+ 'payment_lastname' => 'lastname_payment',
+ 'payment_company' => 'company_payment',
+ 'payment_address_1' => 'address_1_payment',
+ 'payment_city' => 'city_payment',
+ 'payment_zone' => 'state_payment',
+ 'payment_postcode' => 'postcode_payment',
+ 'payment_country' => 'country_payment',
+ 'payment_iso_code_2' => 'iso_code_payment',
+
+ 'telephone' => 'telephone',
+ 'email' => 'email',
+ 'currency_code' => 'USD',
+ 'total' => 100,
+ 'payment_method' => ['name' => 'Payment'],
+ 'shipping_method' => ['name' => 'Shipping', 'cost' => 5, 'code' => 'shipping_code'],
+ 'products' => [
+ ['order_product_id' => 'product_id', 'name' => 'name', 'model' => 'model', 'quantity' => 2, 'total' => 40, 'tax' => 4.8],
+ ],
+ 'tracking' => 'tracking',
+ 'comment' => 'comment',
+ 'date_added' => '2025-03-10 13:00:00'
+ ]);
+
+ $formatter = Mockery::mock(\BulkGate\Plugin\Localization\Formatter::class);
+ $formatter->shouldReceive('format')->with('price', 100.0, 'USD')->andReturn('price_locale');
+ $formatter->shouldReceive('format')->with('date', '2025-03-10 13:00:00')->andReturn('date_locale');
+ $formatter->shouldReceive('format')->with('datetime', '2025-03-10 13:00:00')->andReturn('datetime_locale');
+ $formatter->shouldReceive('format')->with('time', '2025-03-10 13:00:00')->andReturn('time_locale');
+ $formatter->shouldReceive('format')->with('price', 5.0, 'USD')->andReturn('carrier_price_locale');
+ $formatter->shouldReceive('format')->with('price', 44.8, 'USD')->andReturn('product_price_locale');
+
+ $order_loader = new \BulkGate\CartSms\Event\Loader\Order($order_model, $formatter);
+ $order_loader->load($variables = new Plugin\Event\Variables(['order_id' => 1]));
+ $order_loader->load(new Plugin\Event\Variables(['order_id' => '1']));
+
+ Assert::equal([
+ 'order_id' => 1,
+ 'customer_firstname' => 'firstname_shipping',
+ 'customer_lastname' => 'lastname_shipping',
+ 'customer_company' => 'company_shipping',
+ 'customer_address' => 'address_1_shipping',
+ 'customer_city' => 'city_shipping',
+ 'customer_state' => 'state_shipping',
+ 'customer_postcode' => 'postcode_shipping',
+ 'customer_country' => 'country_shipping',
+ 'customer_country_id' => 'iso_code_shipping',
+ 'customer_invoice_firstname' => 'firstname_payment',
+ 'customer_invoice_lastname' => 'lastname_payment',
+ 'customer_invoice_company' => 'company_payment',
+ 'customer_invoice_address' => 'address_1_payment',
+ 'customer_invoice_city' => 'city_payment',
+ 'customer_invoice_state' => 'state_payment',
+ 'customer_invoice_postcode' => 'postcode_payment',
+ 'customer_invoice_country' => 'country_payment',
+ 'customer_invoice_country_id' => 'iso_code_payment',
+ 'customer_mobile' => 'telephone',
+ 'customer_email' => 'email',
+ 'shop_id' => 1,
+ 'lang_id' => 1,
+ 'customer_id' => 1,
+ 'id_address_delivery' => 1,
+ 'id_address_invoice' => 1,
+ 'order_status_id' => 1,
+ 'order_currency' => 'USD',
+ 'long_order_id' => '000001',
+ 'order_total_locale' => 'price_locale',
+ 'order_total_paid' => 100,
+ 'order_payment' => 'Payment',
+ 'order_tracking' => 'tracking',
+ 'order_message' => 'comment',
+ 'order_date' => 'date_locale',
+ 'order_date1' => '10.03.2025',
+ 'order_date2' => '10/03/2025',
+ 'order_date3' => '10-03-2025',
+ 'order_date4' => '2025-03-10',
+ 'order_date5' => '03.10.2025',
+ 'order_date6' => '03/10/2025',
+ 'order_date7' => '03-10-2025',
+ 'order_datetime' => 'datetime_locale',
+ 'order_time' => 'time_locale',
+ 'order_time1' => '13:00:00',
+ 'order_carrier_name' => 'Shipping',
+ 'order_carrier_price' => 5,
+ 'order_carrier_price_locale' => 'carrier_price_locale',
+ 'order_carrier_code' => 'shipping_code',
+ 'order_products1' => '2x name model product_price_locale',
+ 'order_products2' => '2x name product_price_locale',
+ 'order_products3' => '2x (product_id) name model product_price_locale',
+ 'order_products4' => '2x model product_price_locale',
+ 'order_products5' => '2x name model product_price_locale',
+ 'order_products6' => '2x name product_price_locale',
+ 'order_products7' => '2x (product_id) name model product_price_locale',
+ 'order_products8' => '2x model product_price_locale',
+ 'order_smsprinter1' => '2,name,44.8',
+ 'order_smsprinter2' => '2;name;44.8',
+ ], $variables->toArray());
+ }
+
+ public function testOverwrite()
+ {
+ $order_model = Mockery::mock(\Opencart\Catalog\Model\Checkout\Order::class);
+ $order_model->shouldReceive('getOrder')->with(Mockery::on(fn ($arg) => $arg === 1))->andReturn([
+ 'store_id' => 1,
+ 'language_id' => 1,
+ 'customer_id' => 1,
+ 'shipping_address_id' => 1,
+ 'payment_address_id' => 1,
+ 'order_status_id' => 1,
+ 'shipping_firstname' => 'firstname_shipping',
+ 'shipping_lastname' => 'lastname_shipping',
+ 'shipping_company' => 'company_shipping',
+ 'shipping_address_1' => 'address_1_shipping',
+ 'shipping_city' => 'city_shipping',
+ 'shipping_zone' => 'state_shipping',
+ 'shipping_postcode' => 'postcode_shipping',
+ 'shipping_country' => 'country_shipping',
+ 'shipping_iso_code_2' => 'iso_code_shipping',
+
+ 'payment_firstname' => '',
+ 'payment_lastname' => '',
+ 'payment_company' => '',
+ 'payment_address_1' => '',
+ 'payment_city' => '',
+ 'payment_zone' => '',
+ 'payment_postcode' => '',
+ 'payment_country' => '',
+ 'payment_iso_code_2' => '',
+
+ 'telephone' => 'telephone',
+ 'email' => 'email',
+ 'currency_code' => 'USD',
+ 'total' => 100,
+ 'payment_method' => ['name' => 'Payment'],
+ 'shipping_method' => ['name' => 'Shipping', 'cost' => 5, 'code' => 'shipping_code'],
+ 'products' => [
+ ['order_product_id' => 'product_id', 'name' => 'name', 'model' => 'model', 'quantity' => 2, 'total' => 40, 'tax' => 4.8],
+ ],
+ 'tracking' => 'tracking',
+ 'comment' => 'comment',
+ 'date_added' => '2025-03-10 13:00:00'
+ ]);
+ $formatter = Mockery::mock(\BulkGate\Plugin\Localization\Formatter::class);
+ $formatter->shouldReceive('format')->with('price', 100.0, 'USD')->andReturn('price_locale');
+ $formatter->shouldReceive('format')->with('date', '2025-03-10 13:00:00')->andReturn('date_locale');
+ $formatter->shouldReceive('format')->with('datetime', '2025-03-10 13:00:00')->andReturn('datetime_locale');
+ $formatter->shouldReceive('format')->with('time', '2025-03-10 13:00:00')->andReturn('time_locale');
+ $formatter->shouldReceive('format')->with('price', 5.0, 'USD')->andReturn('carrier_price_locale');
+ $formatter->shouldReceive('format')->with('price', 44.8, 'USD')->andReturn('product_price_locale');
+
+ $order_loader = new \BulkGate\CartSms\Event\Loader\Order($order_model, $formatter);
+ $order_loader->load($variables = new Plugin\Event\Variables([
+ 'order_id' => 1,
+ 'shop_id' => 2,
+ 'lang_id' => 3,
+ 'customer_id' => 4,
+ 'id_address_delivery' => 5,
+ 'id_address_invoice' => 6,
+ 'order_status_id' => 7
+ ]));
+
+ Assert::same(1, $variables['order_id']);
+ Assert::same(2, $variables['shop_id']);
+ Assert::same(3, $variables['lang_id']);
+ Assert::same(4, $variables['customer_id']);
+ Assert::same(5, $variables['id_address_delivery']);
+ Assert::same(6, $variables['id_address_invoice']);
+ Assert::same(7, $variables['order_status_id']);
+
+ Assert::same('firstname_shipping', $variables['customer_invoice_firstname']);
+ Assert::same('lastname_shipping', $variables['customer_invoice_lastname']);
+ Assert::same('company_shipping', $variables['customer_invoice_company']);
+ Assert::same('address_1_shipping', $variables['customer_invoice_address']);
+ Assert::same('city_shipping', $variables['customer_invoice_city']);
+ Assert::same('state_shipping', $variables['customer_invoice_state']);
+ Assert::same('postcode_shipping', $variables['customer_invoice_postcode']);
+ Assert::same('country_shipping', $variables['customer_invoice_country']);
+ Assert::same('iso_code_shipping', $variables['customer_invoice_country_id']);
+ }
+
+
+ public function testNoLoad(): void
+ {
+ $order_model = Mockery::mock(\Opencart\Catalog\Model\Checkout\Order::class);
+ $order_model->shouldNotReceive('getOrder');
+
+ $formatter = Mockery::mock(\BulkGate\Plugin\Localization\Formatter::class);
+
+ $order_loader = new \BulkGate\CartSms\Event\Loader\Order($order_model, $formatter);
+ $order_loader->load($variables = new Plugin\Event\Variables(['shop_id' => 2, 'lang_id' => 3]));
+
+ Assert::same(['shop_id' => 2, 'lang_id' => 3], $variables->toArray());
+ }
+
+ public function tearDown(): void
+ {
+ Mockery::close();
+ }
+}
+
+(new OrderTest())->run();
\ No newline at end of file
diff --git a/tests/Event/Loader/OrderReturn.phpt b/tests/Event/Loader/OrderReturn.phpt
new file mode 100644
index 0000000..85f6aea
--- /dev/null
+++ b/tests/Event/Loader/OrderReturn.phpt
@@ -0,0 +1,122 @@
+shouldReceive('getReturn')->with(Mockery::on(fn ($arg) => $arg === 1))->andReturn([
+ 'store_id' => 1,
+ 'language_id' => 1,
+ 'order_id' => 1,
+ 'customer_id' => 1,
+ 'return_status_id' => 1,
+ 'quantity' => 1,
+ 'product' => 'Product',
+ 'product_id' => 1,
+ 'return_status' => 'status',
+ 'comment' => 'test',
+ 'date_added' => '2025-03-10 11:00:00',
+ 'action' => 'action',
+ 'return_action_id' => 1,
+ 'return_reason_id' => 1,
+ 'reason' => 'reason'
+ ]);
+
+ $formatter = Mockery::mock(\BulkGate\Plugin\Localization\Formatter::class);
+ $formatter->shouldReceive('format')->with('date', '2025-03-10 11:00:00')->andReturn('date');
+
+ $order_return_loader = new \BulkGate\CartSms\Event\Loader\OrderReturn($return_model, $formatter);
+ $order_return_loader->load($variables = new Plugin\Event\Variables(['return_id' => 1]));
+ $order_return_loader->load(new Plugin\Event\Variables(['return_id' => "1"]));
+
+ Assert::same([
+ 'return_id' => 1,
+ 'lang_id' => 1,
+ 'order_id' => 1,
+ 'customer_id' => 1,
+ 'return_status_id' => 1,
+ 'return_customer_message' => 'test',
+ 'return_date' => 'date',
+ 'return_action' => 'action',
+ 'return_action_id' => 1,
+ 'return_reason_id' => 1,
+ 'return_reason' => 'reason',
+ 'return_status' => 'status',
+ 'return_products1' => '1x Product 1',
+ 'return_products2' => '1x Product',
+ 'return_products3' => '1x (1) Product',
+ 'return_products4' => '1x 1'
+ ], $variables->toArray());
+ }
+
+ public function testOverwrite()
+ {
+ $return_model = Mockery::mock(\Opencart\Catalog\Model\Account\Returns::class);
+ $return_model->shouldReceive('getReturn')->with(Mockery::on(fn ($arg) => $arg === 1))->andReturn([
+ 'store_id' => 1,
+ 'language_id' => 1,
+ 'order_id' => 1,
+ 'customer_id' => 1,
+ 'return_status_id' => 1,
+ 'quantity' => 1,
+ 'product' => 'Product',
+ 'product_id' => 1,
+ 'return_status' => 'status',
+ 'comment' => 'test',
+ 'date_added' => '2025-03-10 11:00:00',
+ 'action' => 'action',
+ 'return_action_id' => 1,
+ 'return_reason_id' => 1,
+ 'reason' => 'reason'
+ ]);
+
+ $formatter = Mockery::mock(\BulkGate\Plugin\Localization\Formatter::class);
+ $formatter->shouldReceive('format')->with('date', '2025-03-10 11:00:00')->andReturn('date');
+
+ $order_return_loader = new \BulkGate\CartSms\Event\Loader\OrderReturn($return_model, $formatter);
+ $order_return_loader->load($variables = new Plugin\Event\Variables([
+ 'return_id' => 1,
+ 'lang_id' => 2,
+ 'order_id' => 3,
+ 'customer_id' => 4,
+ 'return_status_id' => 5
+ ]));
+
+ Assert::same(1, $variables['return_id']);
+ Assert::same(2, $variables['lang_id']);
+ Assert::same(3, $variables['order_id']);
+ Assert::same(4, $variables['customer_id']);
+ Assert::same(5, $variables['return_status_id']);
+ }
+
+ public function testNoLoad(): void
+ {
+ $return_model = Mockery::mock(\Opencart\Catalog\Model\Account\Returns::class);
+ $return_model->shouldNotReceive('getReturn');
+
+ $formatter = Mockery::mock(\BulkGate\Plugin\Localization\Formatter::class);
+
+ $order_return_loader = new \BulkGate\CartSms\Event\Loader\OrderReturn($return_model, $formatter);
+ $order_return_loader->load($variables = new Plugin\Event\Variables(['shop_id' => 2, 'lang_id' => 3]));
+
+ Assert::same(['shop_id' => 2, 'lang_id' => 3], $variables->toArray());
+ }
+
+ public function tearDown(): void
+ {
+ Mockery::close();
+ }
+}
+
+(new OrderReturnTest())->run();
\ No newline at end of file
diff --git a/tests/Event/Loader/OrderReturnStatus.phpt b/tests/Event/Loader/OrderReturnStatus.phpt
new file mode 100644
index 0000000..09b304c
--- /dev/null
+++ b/tests/Event/Loader/OrderReturnStatus.phpt
@@ -0,0 +1,48 @@
+shouldReceive('getReturnStatus')->with(Mockery::on(fn ($arg) => $arg === 1))->andReturn(['name' => 'Pending']);
+
+ $return_status_loader = new \BulkGate\CartSms\Event\Loader\OrderReturnStatus($order_return_status_model);
+ $return_status_loader->load($variables = new Plugin\Event\Variables(['return_status_id' => 1]));
+ $return_status_loader->load(new Plugin\Event\Variables(['return_status_id' => "1"]));
+
+ Assert::same([
+ 'return_status_id' => 1,
+ 'return_status' => 'Pending'
+ ], $variables->toArray());
+ }
+
+ public function testNoLoad(): void
+ {
+ $order_return_status_model = Mockery::mock(\Opencart\Admin\Model\Localisation\ReturnStatus::class);
+ $order_return_status_model->shouldNotReceive('getReturnStatus');
+
+ $return_status_loader = new \BulkGate\CartSms\Event\Loader\OrderReturnStatus($order_return_status_model);
+ $return_status_loader->load($variables = new Plugin\Event\Variables(['shop_id' => 2, 'lang_id' => 3]));
+ $return_status_loader->load(new Plugin\Event\Variables(['return_status_id' => 0]));
+
+ Assert::same(['shop_id' => 2, 'lang_id' => 3], $variables->toArray());
+ }
+
+ public function tearDown(): void
+ {
+ Mockery::close();
+ }
+}
+
+(new OrderReturnStatusTest())->run();
\ No newline at end of file
diff --git a/tests/Event/Loader/OrderStatus.phpt b/tests/Event/Loader/OrderStatus.phpt
new file mode 100644
index 0000000..c154c26
--- /dev/null
+++ b/tests/Event/Loader/OrderStatus.phpt
@@ -0,0 +1,48 @@
+shouldReceive('getOrderStatus')->with(Mockery::on(fn($arg) => $arg === 1))->andReturn(['name' => 'Pending']);
+
+ $order_status_loader = new \BulkGate\CartSms\Event\Loader\OrderStatus($order_status_model);
+ $order_status_loader->load($variables = new Plugin\Event\Variables(['order_status_id' => 1]));
+ $order_status_loader->load(new Plugin\Event\Variables(['order_status_id' => "1"]));
+
+ Assert::same([
+ 'order_status_id' => 1,
+ 'order_status' => 'Pending'
+ ], $variables->toArray());
+ }
+
+ public function testNoLoad(): void
+ {
+ $order_status_model = Mockery::mock(\Opencart\Catalog\Model\Localisation\OrderStatus::class);
+ $order_status_model->shouldNotReceive('getOrderStatus');
+
+ $order_status_loader = new \BulkGate\CartSms\Event\Loader\OrderStatus($order_status_model);
+ $order_status_loader->load($variables = new Plugin\Event\Variables(['shop_id' => 2, 'lang_id' => 3]));
+ $order_status_loader->load(new Plugin\Event\Variables(['order_status_id' => 0]));
+
+ Assert::same(['shop_id' => 2, 'lang_id' => 3], $variables->toArray());
+ }
+
+ public function tearDown(): void
+ {
+ Mockery::close();
+ }
+}
+
+(new OrderStatusTest())->run();
\ No newline at end of file
diff --git a/tests/Event/Loader/Product.phpt b/tests/Event/Loader/Product.phpt
new file mode 100644
index 0000000..989ae88
--- /dev/null
+++ b/tests/Event/Loader/Product.phpt
@@ -0,0 +1,123 @@
+shouldReceive('getProduct')->with(Mockery::on(fn ($arg) => $arg === 1))->andReturn([
+ 'store_id' => 1,
+ 'language_id' => 1,
+ 'manufacturer_id' => 1,
+ 'quantity' => 'quantity',
+ 'minimum' => 'min_quantity',
+ 'name' => 'Product',
+ 'model' => 'Model',
+ 'description' => 'description',
+ 'price' => 20,
+ 'ean' => 'ean',
+ 'upc' => 'upc',
+ 'isbn' => 'isbn',
+ 'jan' => 'jan',
+ 'mpn' => 'mpn',
+ 'sku' => 'sku'
+ ]);
+
+ $manufacturer_model = Mockery::mock(\Opencart\Catalog\Model\Catalog\Manufacturer::class);
+ $manufacturer_model->shouldReceive('getManufacturer')->with(Mockery::on(fn ($arg) => $arg === 1))->andReturn(['name' => 'Manufacturer']);
+
+ $formatter = Mockery::mock(\BulkGate\Plugin\Localization\Formatter::class);
+ $formatter->shouldReceive('format')->with('price', 20.0, 'USD')->andReturn('price_locale');
+
+ $product_loader = new \BulkGate\CartSms\Event\Loader\Product($product_model, $manufacturer_model, $formatter);
+ $product_loader->load($variables = new Plugin\Event\Variables(['product_id' => 1, 'shop_currency' => 'USD']));
+ $product_loader->load(new Plugin\Event\Variables(['product_id' => '1', 'shop_currency' => 'USD']));
+
+ Assert::same([
+ 'product_id' => 1,
+ 'shop_currency' => 'USD',
+ 'shop_id' => 1,
+ 'lang_id' => 1,
+ 'product_quantity' => 'quantity',
+ 'product_minimal_quantity' => 'min_quantity',
+ 'product_name' => 'Product',
+ 'product_model' => 'Model',
+ 'product_description' => 'description',
+ 'product_manufacturer' => 'Manufacturer',
+ 'product_price' => 20,
+ 'product_price_locale' => 'price_locale',
+ 'product_ean' => 'ean',
+ 'product_upc' => 'upc',
+ 'product_isbn' => 'isbn',
+ 'product_jan' => 'jan',
+ 'product_mpn' => 'mpn',
+ 'product_sku' => 'sku',
+ ], $variables->toArray());
+ }
+
+ public function testOverwrite()
+ {
+ $product_model = Mockery::mock(\Opencart\Catalog\Model\Catalog\Product::class);
+ $product_model->shouldReceive('getProduct')->with(Mockery::on(fn ($arg) => $arg === 1))->andReturn([
+ 'store_id' => 1,
+ 'language_id' => 1,
+ 'manufacturer_id' => 1,
+ 'quantity' => 'quantity',
+ 'minimum' => 'min_quantity',
+ 'name' => 'Product',
+ 'model' => 'Model',
+ 'description' => 'description',
+ 'price' => 20,
+ 'ean' => 'ean',
+ 'upc' => 'upc',
+ 'isbn' => 'isbn',
+ 'jan' => 'jan',
+ 'mpn' => 'mpn',
+ 'sku' => 'sku'
+ ]);
+
+ $manufacturer_model = Mockery::mock(\Opencart\Catalog\Model\Catalog\Manufacturer::class);
+ $manufacturer_model->shouldReceive('getManufacturer')->with(Mockery::on(fn ($arg) => $arg === 1))->andReturn(['name' => 'Manufacturer']);
+
+ $formatter = Mockery::mock(\BulkGate\Plugin\Localization\Formatter::class);
+ $formatter->shouldReceive('format')->with('price', 20.0, 'USD')->andReturn('price_locale');
+
+ $product_loader = new \BulkGate\CartSms\Event\Loader\Product($product_model, $manufacturer_model, $formatter);
+ $product_loader->load($variables = new Plugin\Event\Variables(['product_id' => 1, 'shop_id' => 2, 'lang_id' => 3, 'shop_currency' => 'USD']));
+
+ Assert::same(1, $variables['product_id']);
+ Assert::same(2, $variables['shop_id']);
+ Assert::same(3, $variables['lang_id']);
+ }
+
+ public function testNoLoad(): void
+ {
+ $product_model = Mockery::mock(\Opencart\Catalog\Model\Catalog\Product::class);
+ $product_model->shouldNotReceive('getProduct');
+
+ $manufacturer_model = Mockery::mock(\Opencart\Catalog\Model\Catalog\Manufacturer::class);
+ $formatter = Mockery::mock(\BulkGate\Plugin\Localization\Formatter::class);
+
+ $product_loader = new \BulkGate\CartSms\Event\Loader\Product($product_model, $manufacturer_model, $formatter);
+ $product_loader->load($variables = new Plugin\Event\Variables(['shop_id' => 2, 'lang_id' => 3]));
+
+ Assert::same(['shop_id' => 2, 'lang_id' => 3], $variables->toArray());
+ }
+
+ public function tearDown(): void
+ {
+ Mockery::close();
+ }
+}
+
+(new ProductTest())->run();
\ No newline at end of file
diff --git a/tests/Event/Loader/Shop.phpt b/tests/Event/Loader/Shop.phpt
new file mode 100644
index 0000000..2c9e29e
--- /dev/null
+++ b/tests/Event/Loader/Shop.phpt
@@ -0,0 +1,135 @@
+shouldReceive('getSetting')->with('config', Mockery::on(fn($arg) => $arg === 1))->andReturn([
+ 'config_name' => 'Shop',
+ 'config_url' => 'http://www.example.com',
+ 'config_currency' => 'USD',
+ 'config_language_catalog' => 'en-gb',
+ 'config_telephone' => '777888999',
+ 'config_email' => 'store@example.com'
+ ]);
+
+ $language_model = Mockery::mock(\Opencart\Catalog\Model\Localisation\Language::class);
+ $language_model->shouldReceive('getLanguageByCode')->with('en-gb')->times(2)->andReturn(['language_id' => 1, 'code' => 'en-gb']);
+ $language_model->shouldReceive('getLanguage')->with(Mockery::on(fn($arg) => $arg === 1))->times(2)->andReturn(['language_id' => 1, 'code' => 'en-gb']);
+
+ $request = Mockery::mock(\Opencart\System\Library\Request::class);
+
+ $shop_loader = new \BulkGate\CartSms\Event\Loader\Shop($store_model, $language_model, $request);
+ $shop_loader->load($variables = new Plugin\Event\Variables(['shop_id' => 1]));
+ $shop_loader->load(new Plugin\Event\Variables(['shop_id' => 1]));
+
+ Assert::same([
+ 'shop_id' => 1,
+ 'shop_email' => 'store@example.com',
+ 'shop_name' => 'Shop',
+ 'shop_domain' => 'http://www.example.com',
+ 'shop_currency' => 'USD',
+ 'shop_phone' => '777888999',
+ 'lang_id' => 1,
+ 'lang_iso' => 'en-gb'
+ ], $variables->toArray());
+ }
+
+ public function testOverwrite()
+ {
+ $store_model = Mockery::mock(\Opencart\Catalog\Model\Setting\Setting::class);
+ $store_model->shouldReceive('getSetting')->with('config', Mockery::on(fn($arg) => $arg === 1))->once()->ordered()->andReturn([
+ 'config_name' => 'Shop',
+ 'config_url' => 'http://www.example.com',
+ 'config_currency' => 'USD',
+ 'config_language_catalog' => 'en-gb',
+ 'config_telephone' => '777888999',
+ 'config_email' => 'store@example.com'
+ ]);
+
+ $language_model = Mockery::mock(\Opencart\Catalog\Model\Localisation\Language::class);
+ $language_model->shouldReceive('getLanguage')->with(2)->once()->andReturn(['language_id' => 2, 'code' => 'cs-cz']);
+
+ $request = Mockery::mock(\Opencart\System\Library\Request::class);
+
+ $shop_loader = new \BulkGate\CartSms\Event\Loader\Shop($store_model, $language_model, $request);
+ $shop_loader->load($variables = new Plugin\Event\Variables(['shop_id' => 1, 'lang_id' => 2, 'shop_domain' => 'https://www.alza.cz']));
+
+ Assert::same(1, $variables['shop_id']);
+ Assert::same(2, $variables['lang_id']);
+ Assert::same('https://www.alza.cz', $variables['shop_domain']);
+ }
+
+ public function testLanguagePriority(): void
+ {
+ $store_model = Mockery::mock(\Opencart\Catalog\Model\Setting\Setting::class);
+ $store_model->shouldReceive('getSetting')->with('config', Mockery::on(fn($arg) => $arg === 1))->times(2)->ordered()->andReturn([
+ 'config_name' => 'Shop',
+ 'config_url' => 'http://www.example.com',
+ 'config_currency' => 'USD',
+ 'config_language_catalog' => 'en-gb',
+ 'config_telephone' => '777888999',
+ 'config_email' => 'store@example.com'
+ ]);
+
+ $language_model = Mockery::mock(\Opencart\Catalog\Model\Localisation\Language::class);
+ $language_model->shouldReceive('getLanguageByCode')->with('cs-cz')->once()->andReturn(['language_id' => 2, 'code' => 'cs-cz']);
+ $language_model->shouldReceive('getLanguageByCode')->with('fr-fr')->once()->andReturn(['language_id' => 3, 'code' => 'fr-fr']);
+ $language_model->shouldReceive('getLanguage')->with(Mockery::on(fn($arg) => $arg === 2))->once()->andReturn(['language_id' => 2, 'code' => 'cs-cz']);
+ $language_model->shouldReceive('getLanguage')->with(Mockery::on(fn($arg) => $arg === 3))->once()->andReturn(['language_id' => 3, 'code' => 'fr-fr']);
+
+ $request = Mockery::mock(\Opencart\System\Library\Request::class);
+ $request->get = ['language' => 'fr-fr'];
+ $request->cookie = ['language' => 'cs-cz'];
+
+ //from GET eg: ?route=product/product&language=fr-fr
+ $shop_loader = new \BulkGate\CartSms\Event\Loader\Shop($store_model, $language_model, $request);
+ $shop_loader->load($variables = new Plugin\Event\Variables(['shop_id' => 1]));
+
+ Assert::same(3, $variables['lang_id']);
+ Assert::same('fr-fr', $variables['lang_iso']);
+
+ //from Cookie eg: ?route=product/product
+ $request->get = [];
+
+ $shop_loader = new \BulkGate\CartSms\Event\Loader\Shop($store_model, $language_model, $request);
+ $shop_loader->load($variables = new Plugin\Event\Variables(['shop_id' => 1]));
+
+ Assert::same(2, $variables['lang_id']);
+ Assert::same('cs-cz', $variables['lang_iso']);
+ }
+
+ public function testNoLoad(): void
+ {
+ $store_model = Mockery::mock(\Opencart\Catalog\Model\Setting\Setting::class);
+ $store_model->shouldNotReceive('getSetting');
+
+ $language_model = Mockery::mock(\Opencart\Catalog\Model\Localisation\Language::class);
+ $request = Mockery::mock(\Opencart\System\Library\Request::class);
+
+ $shop_loader = new \BulkGate\CartSms\Event\Loader\Shop($store_model, $language_model, $request);
+ $shop_loader->load($variables = new Plugin\Event\Variables(['customer_id' => 2, 'lang_id' => 3]));
+
+ Assert::same(['customer_id' => 2, 'lang_id' => 3], $variables->toArray());
+ }
+
+ public function tearDown(): void
+ {
+ Mockery::close();
+ }
+}
+
+(new ShopTest())->run();
\ No newline at end of file
diff --git a/tests/Event/State.phpt b/tests/Event/State.phpt
new file mode 100644
index 0000000..e14d6c6
--- /dev/null
+++ b/tests/Event/State.phpt
@@ -0,0 +1,44 @@
+getInitial());
+ Assert::equal(null, $state->getActual());
+ Assert::equal(null, $state->getExpected());
+ Assert::false($state->isChanged());
+ Assert::false($state->shouldRunHook());
+ Assert::true($state->isExpected());
+
+ $state
+ ->captureInitial()
+ ->captureActual()
+ ->setExpected(2);
+
+ Assert::equal(1, $state->getInitial());
+ Assert::equal(2, $state->getActual());
+ Assert::equal(2, $state->getExpected());
+ Assert::true($state->isChanged());
+ Assert::true($state->shouldRunHook());
+ Assert::true($state->isExpected());
+
+ }
+}
+
+(new StateTest())->run();
\ No newline at end of file
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
new file mode 100644
index 0000000..d3d1b95
--- /dev/null
+++ b/tests/bootstrap.php
@@ -0,0 +1,16 @@
+register('Opencart\\' . APPLICATION, DIR_APPLICATION);
+$autoloader->register('Opencart\Extension', DIR_EXTENSION);
+$autoloader->register('Opencart\System', DIR_SYSTEM);
+
+require_once DIR_SYSTEM . 'vendor.php';
+
+// Registry
+$registry = new \Opencart\System\Engine\Registry();
+$registry->set('autoloader', $autoloader);
+
+// Config
+$config = new \Opencart\System\Engine\Config();
+$config->addPath(DIR_CONFIG);
+// Load the default config
+$config->load('default');
+$config->load(strtolower(APPLICATION));
+$registry->set('config', $config);
+
+// Set the default application
+$config->set('application', APPLICATION);
+
+// Factory
+$registry->set('factory', new \Opencart\System\Engine\Factory($registry));
+
+// Loader
+$loader = new \Opencart\System\Engine\Loader($registry);
+$registry->set('load', $loader);
+
+// Event
+$event = new \Opencart\System\Engine\Event($registry);
+$registry->set('event', $event);
+
+// Cache
+//$registry->set('cache', new \Opencart\System\Library\Cache($config->get('cache_engine'), $config->get('cache_expire')));
+
+//$db = new \Opencart\System\Library\DB($config->get('db_engine'), $config->get('db_hostname'), $config->get('db_username'), $config->get('db_password'), $config->get('db_database'), $config->get('db_port'), $config->get('db_ssl_key'), $config->get('db_ssl_cert'), $config->get('db_ssl_ca'));
+//$registry->set('db', $db);
+
+return $registry;
diff --git a/upload/admin/controller/cartsms/black_list.php b/upload/admin/controller/cartsms/black_list.php
deleted file mode 100644
index e0fec38..0000000
--- a/upload/admin/controller/cartsms/black_list.php
+++ /dev/null
@@ -1,20 +0,0 @@
-view('Black list', 'BlackList', 'default', true);
- }
-
- public function actionImport()
- {
- $this->view('Black list', 'BlackList', 'import', true);
- }
-}
\ No newline at end of file
diff --git a/upload/admin/controller/cartsms/dashboard.php b/upload/admin/controller/cartsms/dashboard.php
deleted file mode 100644
index cb42288..0000000
--- a/upload/admin/controller/cartsms/dashboard.php
+++ /dev/null
@@ -1,15 +0,0 @@
-view('Dashboard', 'Dashboard', 'default', false);
- }
-}
\ No newline at end of file
diff --git a/upload/admin/controller/cartsms/events.php b/upload/admin/controller/cartsms/events.php
deleted file mode 100644
index afb6022..0000000
--- a/upload/admin/controller/cartsms/events.php
+++ /dev/null
@@ -1,75 +0,0 @@
-runHook('return_status_change_'.$return_status_id, new Extensions\Hook\Variables([
- 'return_id' => (int) $return_id,
- 'return_status_id' => (int) $return_status_id,
- 'return_customer_message' => $comment
- ]));
- }
- }
-
-
- /**
- * admin/model/sale/return/addReturn/after
- * @param string $hook
- * @param array $input
- * @param int $return_id
- */
- public function returnGoods($hook, $input, $return_id)
- {
- $this->runHook('product_return', new Extensions\Hook\Variables(array(
- 'return_id' => (int) $return_id,
- )));
- }
-
-
- /**
- * admin/model/catalog/product/deleteProduct/before
- * @param string $hook
- * @param array $input
- */
- public function productDeleteHook($hook, $input)
- {
- list($product_id) = array_pad($input, 1, null);
-
- if($product_id)
- {
- $this->runHook('product_delete', new Extensions\Hook\Variables([
- 'product_id' => (int) $product_id
- ]));
- }
- }
-
- /**
- * admin/model/customer/customer/addCustomer/after
- * @param string $hook
- * @param array $input
- * @param int $customer_id
- */
- public function customerAddHook($hook, $input, $customer_id)
- {
- $this->runHook('customer_account_new', new Extensions\Hook\Variables(array(
- 'customer_id' => (int) $customer_id,
- )));
- }
-}
diff --git a/upload/admin/controller/cartsms/history.php b/upload/admin/controller/cartsms/history.php
deleted file mode 100644
index 7f27383..0000000
--- a/upload/admin/controller/cartsms/history.php
+++ /dev/null
@@ -1,15 +0,0 @@
-view('History', 'History', 'list', true);
- }
-}
\ No newline at end of file
diff --git a/upload/admin/controller/cartsms/inbox.php b/upload/admin/controller/cartsms/inbox.php
deleted file mode 100644
index de3eef1..0000000
--- a/upload/admin/controller/cartsms/inbox.php
+++ /dev/null
@@ -1,15 +0,0 @@
-view('Inbox', 'Inbox', 'list', true);
- }
-}
\ No newline at end of file
diff --git a/upload/admin/controller/cartsms/module_about.php b/upload/admin/controller/cartsms/module_about.php
deleted file mode 100644
index 4d9f03f..0000000
--- a/upload/admin/controller/cartsms/module_about.php
+++ /dev/null
@@ -1,17 +0,0 @@
-view('About module', 'ModuleAbout', 'default', false);
- }
-
-
-}
\ No newline at end of file
diff --git a/upload/admin/controller/cartsms/module_notifications.php b/upload/admin/controller/cartsms/module_notifications.php
deleted file mode 100644
index 5a51b11..0000000
--- a/upload/admin/controller/cartsms/module_notifications.php
+++ /dev/null
@@ -1,44 +0,0 @@
-oc_proxy->add('save', $this->link('cartsms/module_notifications/ajaxSaveAdmin'));
- $this->view('Admin SMS', 'ModuleNotifications', 'admin', true);
- }
-
- public function ajaxSaveAdmin()
- {
- $this->runAjax(function (CartSms\Controller $controller, array $post)
- {
- $post['template'] = htmlspecialchars_decode($post['template']);
- Extensions\JsonResponse::send(
- $controller->oc_di->getProxy()->saveAdminNotifications($post)
- );
- });
- }
-
- public function actionCustomer()
- {
- $this->oc_proxy->add('save', $this->link('cartsms/module_notifications/ajaxSaveCustomer'));
- $this->view('Customer SMS', 'ModuleNotifications', 'customer', true);
- }
-
- public function ajaxSaveCustomer()
- {
- $this->runAjax(function (CartSms\Controller $controller, array $post)
- {
- $post['template'] = htmlspecialchars_decode($post['template']);
- Extensions\JsonResponse::send(
- $controller->oc_di->getProxy()->saveCustomerNotifications($post)
- );
- });
- }
-}
\ No newline at end of file
diff --git a/upload/admin/controller/cartsms/module_settings.php b/upload/admin/controller/cartsms/module_settings.php
deleted file mode 100644
index f7ef17b..0000000
--- a/upload/admin/controller/cartsms/module_settings.php
+++ /dev/null
@@ -1,39 +0,0 @@
-response->redirect($this->link('cartsms/dashboard/actionDefault'));
- }
- $this->oc_proxy->add('save', $this->link('cartsms/module_settings/ajaxSave'));
- $this->oc_proxy->add('logout', $this->link('cartsms/module_settings/ajaxLogout'));
-
- $this->view('About module', 'ModuleSettings', 'default', false);
- }
-
- public function ajaxSave()
- {
- $this->runAjax(function (CartSms\Controller $controller, array $post)
- {
- $controller->oc_di->getProxy()->saveSettings($post);
- Extensions\JsonResponse::send(array('redirect' => $controller->link('cartsms/module_settings/actionDefault')));
-
- }, 'cartsms/module_settings/actionDefault');
- }
-
-
- public function ajaxLogout()
- {
- $this->oc_di->getProxy()->logout();
- Extensions\JsonResponse::send(array('token' => 'guest', 'redirect' => $this->link('cartsms/sign/actionIn')));
- }
-}
\ No newline at end of file
diff --git a/upload/admin/controller/cartsms/payment.php b/upload/admin/controller/cartsms/payment.php
deleted file mode 100644
index bff1e01..0000000
--- a/upload/admin/controller/cartsms/payment.php
+++ /dev/null
@@ -1,20 +0,0 @@
-view('Payment Data', 'Payment', 'data', true);
- }
-
- public function actionList()
- {
- $this->view('Payment list', 'Payment', 'list', true);
- }
-}
\ No newline at end of file
diff --git a/upload/admin/controller/cartsms/sign.php b/upload/admin/controller/cartsms/sign.php
deleted file mode 100644
index bf3d0c4..0000000
--- a/upload/admin/controller/cartsms/sign.php
+++ /dev/null
@@ -1,50 +0,0 @@
-oc_proxy->add('login', $this->link('cartsms/sign/ajaxIn'));
- $this->view('Sign in', 'ModuleSign', 'in', false);
- }
-
- public function ajaxIn()
- {
- $this->runAjax(function (CartSms\Controller $controller, array $post)
- {
- $response = $controller->oc_di->getProxy()->login(array_merge(array('name' => $controller->config->get('config_meta_title')), $controller->request->post['__bulkgate']));
-
- if($response instanceof Extensions\IO\Response)
- {
- Extensions\JsonResponse::send($response);
- }
- Extensions\JsonResponse::send(array(
- 'token' => $response,
- 'redirect' => $controller->link('cartsms/dashboard/actionDefault')
- ));
- });
- }
-
- public function actionUp()
- {
- $this->view('Sign up', 'Sign', 'up', false);
- }
-
- public function authenticate()
- {
- try
- {
- Extensions\JsonResponse::send($this->oc_di->getProxy()->authenticate());
- }
- catch (Extensions\IO\AuthenticateException $e)
- {
- Extensions\JsonResponse::send(array('redirect' => $this->link('cartsms/sign/actionIn')));
- }
- }
-}
\ No newline at end of file
diff --git a/upload/admin/controller/cartsms/sms_campaign.php b/upload/admin/controller/cartsms/sms_campaign.php
deleted file mode 100644
index 4b53ace..0000000
--- a/upload/admin/controller/cartsms/sms_campaign.php
+++ /dev/null
@@ -1,82 +0,0 @@
-view('Campaigns', 'SmsCampaign', 'default', true);
- }
-
- public function actionNew()
- {
- $this->view('Create new Campaign', 'SmsCampaign', 'new', true);
- }
-
- public function actionCampaign()
- {
- $this->oc_proxy->add('loadModuleData', $this->link('cartsms/sms_campaign/ajaxLoadModuleData'), 'campaign');
- $this->oc_proxy->add('saveModuleCustomers', $this->link('cartsms/sms_campaign/ajaxSaveModuleCustomers'), 'campaign');
- $this->oc_proxy->add('addModuleFilter', $this->link('cartsms/sms_campaign/ajaxAddModuleFilter'), 'campaign');
- $this->oc_proxy->add('removeModuleFilter', $this->link('cartsms/sms_campaign/ajaxRemoveModuleFilter'), 'campaign');
- $this->view('Campaign', 'SmsCampaign', 'campaign', true);
- }
-
- public function ajaxLoadModuleData()
- {
- $this->runAjax(function (CartSms\Controller $controller, array $post)
- {
- Extensions\JsonResponse::send($controller->oc_di->getProxy()->loadCustomersCount(
- isset($post['application_id']) ? $post['application_id'] : null,
- isset($post['campaign_id']) ? $post['campaign_id'] : null
- ));
- });
- }
-
- public function ajaxAddModuleFilter()
- {
- $this->runAjax(function (CartSms\Controller $controller, array $post)
- {
- Extensions\JsonResponse::send($controller->oc_di->getProxy()->loadCustomersCount(
- isset($post['application_id']) ? $post['application_id'] : null,
- isset($post['campaign_id']) ? $post['campaign_id'] : null,
- 'addFilter',
- $post
- ));
- });
- }
-
- public function ajaxRemoveModuleFilter()
- {
- $this->runAjax(function (CartSms\Controller $controller, array $post)
- {
- Extensions\JsonResponse::send($controller->oc_di->getProxy()->loadCustomersCount(
- isset($post['application_id']) ? $post['application_id'] : null,
- isset($post['campaign_id']) ? $post['campaign_id'] : null,
- 'removeFilter',
- $post
- ));
- });
- }
-
- public function ajaxSaveModuleCustomers()
- {
- $this->runAjax(function (CartSms\Controller $controller, array $post)
- {
- Extensions\JsonResponse::send($controller->oc_di->getProxy()->saveModuleCustomers(
- isset($post['application_id']) ? $post['application_id'] : null,
- isset($post['campaign_id']) ? $post['campaign_id'] : null
- ));
- });
- }
-
- public function actionActive()
- {
- $this->view('Campaign', 'SmsCampaign', 'active', false);
- }
-}
\ No newline at end of file
diff --git a/upload/admin/controller/cartsms/sms_price.php b/upload/admin/controller/cartsms/sms_price.php
deleted file mode 100644
index daa833e..0000000
--- a/upload/admin/controller/cartsms/sms_price.php
+++ /dev/null
@@ -1,15 +0,0 @@
-view('Price list', 'SmsPrice', 'list', false);
- }
-}
\ No newline at end of file
diff --git a/upload/admin/controller/cartsms/sms_settings.php b/upload/admin/controller/cartsms/sms_settings.php
deleted file mode 100644
index 36464ae..0000000
--- a/upload/admin/controller/cartsms/sms_settings.php
+++ /dev/null
@@ -1,15 +0,0 @@
-view('Sender ID Settings', 'SmsSettings', 'default', true);
- }
-}
\ No newline at end of file
diff --git a/upload/admin/controller/cartsms/statistics.php b/upload/admin/controller/cartsms/statistics.php
deleted file mode 100644
index 558cf4e..0000000
--- a/upload/admin/controller/cartsms/statistics.php
+++ /dev/null
@@ -1,15 +0,0 @@
-view('Statistics', 'Statistics', 'default', true);
- }
-}
\ No newline at end of file
diff --git a/upload/admin/controller/cartsms/top.php b/upload/admin/controller/cartsms/top.php
deleted file mode 100644
index 90981f3..0000000
--- a/upload/admin/controller/cartsms/top.php
+++ /dev/null
@@ -1,15 +0,0 @@
-view('Campaign', 'Top', 'up', true);
- }
-}
\ No newline at end of file
diff --git a/upload/admin/controller/cartsms/user.php b/upload/admin/controller/cartsms/user.php
deleted file mode 100644
index 7952ef0..0000000
--- a/upload/admin/controller/cartsms/user.php
+++ /dev/null
@@ -1,15 +0,0 @@
-view('User profile', 'User', 'profile', false);
- }
-}
\ No newline at end of file
diff --git a/upload/admin/controller/cartsms/wallet.php b/upload/admin/controller/cartsms/wallet.php
deleted file mode 100644
index da58eb9..0000000
--- a/upload/admin/controller/cartsms/wallet.php
+++ /dev/null
@@ -1,15 +0,0 @@
-view('Payment Data', 'Wallet', 'detail', true);
- }
-}
\ No newline at end of file
diff --git a/upload/admin/controller/extension/module/cartsms.php b/upload/admin/controller/extension/module/cartsms.php
deleted file mode 100644
index ab192f8..0000000
--- a/upload/admin/controller/extension/module/cartsms.php
+++ /dev/null
@@ -1,117 +0,0 @@
-response->redirect($this->url->link('cartsms/module_settings/actionDefault', 'user_token=' . $this->session->data['user_token'], true));
- }
-
- public function install()
- {
- $this->load->model('setting/event');
- $this->load->model('user/user_group');
-
- $this->model_setting_event->deleteEvent('cartsms');
-
- $this->oc_settings->install();
-
- $this->model_user_user_group->addPermission($this->user->getGroupId(), 'access', 'cartsms/black_list');
- $this->model_user_user_group->addPermission($this->user->getGroupId(), 'access', 'cartsms/dashboard');
- $this->model_user_user_group->addPermission($this->user->getGroupId(), 'access', 'cartsms/history');
- $this->model_user_user_group->addPermission($this->user->getGroupId(), 'access', 'cartsms/inbox');
- $this->model_user_user_group->addPermission($this->user->getGroupId(), 'access', 'cartsms/module_about');
- $this->model_user_user_group->addPermission($this->user->getGroupId(), 'access', 'cartsms/module_notifications');
- $this->model_user_user_group->addPermission($this->user->getGroupId(), 'access', 'cartsms/module_settings');
- $this->model_user_user_group->addPermission($this->user->getGroupId(), 'access', 'cartsms/payment');
- $this->model_user_user_group->addPermission($this->user->getGroupId(), 'access', 'cartsms/sign');
- $this->model_user_user_group->addPermission($this->user->getGroupId(), 'access', 'cartsms/sms_campaign');
- $this->model_user_user_group->addPermission($this->user->getGroupId(), 'access', 'cartsms/sms_price');
- $this->model_user_user_group->addPermission($this->user->getGroupId(), 'access', 'cartsms/sms_settings');
- $this->model_user_user_group->addPermission($this->user->getGroupId(), 'access', 'cartsms/statistics');
- $this->model_user_user_group->addPermission($this->user->getGroupId(), 'access', 'cartsms/top');
- $this->model_user_user_group->addPermission($this->user->getGroupId(), 'access', 'cartsms/user');
- $this->model_user_user_group->addPermission($this->user->getGroupId(), 'access', 'cartsms/wallet');
- $this->model_user_user_group->addPermission($this->user->getGroupId(), 'access', 'extension/module/cartsms');
-
- $this->model_setting_event->addEvent('cartsms', 'admin/model/sale/return/addReturnHistory/after', 'cartsms/events/returnGoodsStatus');
- $this->model_setting_event->addEvent('cartsms', 'admin/model/customer/customer/addCustomer/after', 'cartsms/events/customerAddHook');
- $this->model_setting_event->addEvent('cartsms', 'admin/model/catalog/product/deleteProduct/before', 'cartsms/events/productDeleteHook');
- $this->model_setting_event->addEvent('cartsms', 'admin/model/sale/return/addReturn/after', 'cartsms/events/returnGoods');
- $this->model_setting_event->addEvent('cartsms', 'catalog/model/checkout/order/addOrderHistory/after', 'cartsms/events/changeOrderStatusHook');
- $this->model_setting_event->addEvent('cartsms', 'catalog/model/account/customer/addCustomer/after', 'cartsms/events/customerAddHook');
- $this->model_setting_event->addEvent('cartsms', 'catalog/model/account/return/addReturn/after', 'cartsms/events/returnGoods');
- $this->model_setting_event->addEvent('cartsms', 'catalog/bulkgate/cartsms/new/order/hook', 'cartsms/events/orderAddHook');
- $this->model_setting_event->addEvent('cartsms', 'catalog/bulkgate/cartsms/contact/form/hook', 'cartsms/events/contactFormHook');
-
- $this->installOcMod();
- }
-
- public function uninstall()
- {
- $this->load->model('setting/event');
- $this->load->model('user/user_group');
-
- $this->oc_settings->uninstall();
-
- $this->model_user_user_group->removePermission($this->user->getGroupId(), 'access', 'cartsms/black_list');
- $this->model_user_user_group->removePermission($this->user->getGroupId(), 'access', 'cartsms/dashboard');
- $this->model_user_user_group->removePermission($this->user->getGroupId(), 'access', 'cartsms/history');
- $this->model_user_user_group->removePermission($this->user->getGroupId(), 'access', 'cartsms/inbox');
- $this->model_user_user_group->removePermission($this->user->getGroupId(), 'access', 'cartsms/module_about');
- $this->model_user_user_group->removePermission($this->user->getGroupId(), 'access', 'cartsms/module_notifications');
- $this->model_user_user_group->removePermission($this->user->getGroupId(), 'access', 'cartsms/module_settings');
- $this->model_user_user_group->removePermission($this->user->getGroupId(), 'access', 'cartsms/payment');
- $this->model_user_user_group->removePermission($this->user->getGroupId(), 'access', 'cartsms/sign');
- $this->model_user_user_group->removePermission($this->user->getGroupId(), 'access', 'cartsms/sms_campaign');
- $this->model_user_user_group->removePermission($this->user->getGroupId(), 'access', 'cartsms/sms_price');
- $this->model_user_user_group->removePermission($this->user->getGroupId(), 'access', 'cartsms/sms_settings');
- $this->model_user_user_group->removePermission($this->user->getGroupId(), 'access', 'cartsms/statistics');
- $this->model_user_user_group->removePermission($this->user->getGroupId(), 'access', 'cartsms/top');
- $this->model_user_user_group->removePermission($this->user->getGroupId(), 'access', 'cartsms/user');
- $this->model_user_user_group->removePermission($this->user->getGroupId(), 'access', 'cartsms/wallet');
- $this->model_user_user_group->removePermission($this->user->getGroupId(), 'access', 'extension/module/cartsms');
-
- $this->model_setting_event->deleteEventByCode('cartsms');
-
- $this->uninstallOcMod();
- }
-
- private function installOcMod()
- {
- $this->uninstallOcMod();
-
- $db = $this->oc_di->getDatabase();
-
- $db->execute($db->prepare("
- INSERT INTO `{$db->table('modification')}` (`name`, `author`, `version`, `link`, `xml`, `status`, `date_added`, `code`)
- VALUES (%s, %s, %s, %s, \"".$db->escape(file_get_contents(_BG_CARTSMS_DIR_ . DIRECTORY_SEPARATOR . CartSms\Init::MODULE_CODE . '.ocmod.xml'))."\", 1, NOW(), %s)
- ", array(
- CartSms\Init::NAME,
- CartSms\Init::AUTHOR,
- CartSms\Init::VERSION,
- CartSms\Init::URL,
- CartSms\Init::MODULE_CODE
- )));
-
- $refresh = new ControllerMarketplaceModification($this->registry);
- $refresh->refresh();
- }
-
- private function uninstallOcMod()
- {
- $db = $this->oc_di->getDatabase();
- $db->execute($db->prepare("DELETE FROM `{$db->table('modification')}` WHERE `code` = %s", array(CartSms\Init::MODULE_CODE)));
- }
-}
-
diff --git a/upload/admin/language/en-gb/extension/module/cartsms.php b/upload/admin/language/en-gb/extension/module/cartsms.php
deleted file mode 100644
index 34269ac..0000000
--- a/upload/admin/language/en-gb/extension/module/cartsms.php
+++ /dev/null
@@ -1,3 +0,0 @@
-CartSMS - SMS module for OpenCart 🌍';
diff --git a/upload/admin/view/template/cartsms/base.twig b/upload/admin/view/template/cartsms/base.twig
deleted file mode 100644
index bf3cecb..0000000
--- a/upload/admin/view/template/cartsms/base.twig
+++ /dev/null
@@ -1,72 +0,0 @@
-{{ header }}{{ column_left }}
-
-
-
-
-
-
-
-
-
-
{{ title|escape('html') }}
-
-
-
-
-
-
-
-
-{{ footer }}
diff --git a/upload/catalog/controller/cartsms/events.php b/upload/catalog/controller/cartsms/events.php
deleted file mode 100644
index fe9a891..0000000
--- a/upload/catalog/controller/cartsms/events.php
+++ /dev/null
@@ -1,103 +0,0 @@
-runHook('order_status_change_'.$order_status_id, new Extensions\Hook\Variables(array(
- 'order_status_id' => (int) $order_status_id,
- 'order_id' => (int) $order_id,
- 'order_status_message' => $comment
- )));
- }
- }
-
- /**
- * catalog/model/account/customer/addCustomer/after
- * @param string $hook
- * @param array $input
- * @param int $customer_id
- */
- public function customerAddHook($hook, $input, $customer_id)
- {
- $this->runHook('customer_account_new', new Extensions\Hook\Variables(array(
- 'customer_id' => (int) $customer_id,
- )));
- }
-
- /**
- * bulkgate/cartsms/new/order/hook
- * @param string $hook
- * @param array $input
- */
- public function orderAddHook($hook, $input)
- {
- list($order_id) = array_pad($input, 1, null);
-
- $this->runHook('order_new', new Extensions\Hook\Variables([
- 'order_id' => (int) $order_id
- ]));
-
- foreach(Helpers::productsOutOfStock($this->oc_di->getDatabase()) as $product_id)
- {
- if(Extensions\Helpers::outOfStockCheck($this->oc_settings, $product_id))
- {
- $this->runHook('product_out_of_stock', new Extensions\Hook\Variables([
- 'product_id' => (int) $product_id
- ]));
- }
- }
- }
-
- /**
- * bulkgate/cartsms/contact/form/hook
- * @param $hook
- * @param $input
- */
- public function contactFormHook($hook, $input)
- {
- list($email, $name, $text) = array_pad($input, 3, null);
-
- if($text !== null)
- {
- $this->runHook('contact_form', new Extensions\Hook\Variables(array(
- 'customer_email' => $email,
- 'customer_name' => $name,
- 'customer_message' => $text,
- 'customer_message_short_50' => Helpers::subStr($text, 0, 50),
- 'customer_message_short_80' => Helpers::subStr($text, 0, 80),
- 'customer_message_short_100' => Helpers::subStr($text, 0, 100),
- 'customer_message_short_120' => Helpers::subStr($text, 0, 120),
- )));
- }
- }
-
- /**
- * catalog/model/account/return/addReturn/after
- * @param string $hook
- * @param array $input
- * @param int $return_id
- */
- public function returnGoods($hook, $input, $return_id)
- {
- $this->runHook('product_return', new Extensions\Hook\Variables(array(
- 'return_id' => (int) $return_id,
- )));
- }
-}
diff --git a/upload/system/library/cartsms/Controller.php b/upload/system/library/cartsms/Controller.php
deleted file mode 100644
index c7f0a9a..0000000
--- a/upload/system/library/cartsms/Controller.php
+++ /dev/null
@@ -1,147 +0,0 @@
-registry);
- $this->oc_di = $init->di();
- $this->oc_module = $this->oc_di->getModule();
- $this->oc_settings = $this->oc_di->getSettings();
- $this->oc_proxy = new BulkGate\CartSms\ProxyGenerator();
-
- $this->load->model('setting/event');
- }
-
- protected function view($title, $presenter, $action, $box = false)
- {
- $this->synchronize();
- $this->document->addStyle($this->oc_module->getUrl('/dist/css/devices.min.css'));
- $this->document->addStyle($this->oc_module->getUrl('/'.(defined('BULKGATE_DEV_MODE') ? 'dev' : 'dist').'/css/bulkgate-cartsms.css'));
- $this->document->addStyle('https://fonts.googleapis.com/icon?family=Material+Icons|Open+Sans:300,300i,400,400i,600,600i,700,700i,800,800i');
-
- $this->response->setOutput($this->load->view('cartsms/base', array(
- 'application_id' => $this->oc_settings->load('static:application_id', ''),
- 'language' => $this->oc_settings->load('main:language', 'en'),
- 'presenter' => $presenter,
- 'action' => $action,
- 'title' => $title,
- 'mode' => defined('BULKGATE_DEV_MODE') ? 'dev' : 'dist',
- 'box' => $box,
- 'widget_api_url' => $this->oc_module->getUrl('/'.(defined('BULKGATE_DEV_MODE') ? 'dev' : 'dist').'/widget-api/widget-api.js'),
- 'logo' => $this->oc_module->getUrl('/images/products/oc.svg'),
- 'proxy' => $this->oc_proxy->get(),
- 'authenticate' => $this->link('cartsms/sign/authenticate'),
- 'homepage' => $this->link('cartsms/dashboard'),
- 'info' => $this->oc_module->info(),
- 'header' => $this->load->controller('common/header'),
- 'column_left' => $this->load->controller('common/column_left'),
- 'footer' => $this->load->controller('common/footer'),
- 'loading' => $this->oc_di->getTranslator()->translate('loading_content', 'Loading Content')
- )));
- }
-
- protected function link($route, array $params = array())
- {
- return BulkGate\CartSms\Helpers::fixUrl(
- $this->url->link($route, array_merge(array('user_token' => $this->session->data['user_token']), $params), true)
- );
- }
-
- protected function runAjax($callback, $fail_redirect = 'common/dashboard')
- {
- if(isset($this->request->post['__bulkgate']))
- {
- $post = $this->request->post['__bulkgate'];
-
- if(is_array($post))
- {
- call_user_func_array($callback, array($this, $post));
- }
- else
- {
- $this->response->redirect($this->url->link($fail_redirect, 'user_token=' . $this->session->data['user_token'], true));
- }
- }
- else
- {
- $this->response->redirect($this->url->link($fail_redirect, 'user_token=' . $this->session->data['user_token'], true));
- }
- }
-
- protected function synchronize($now = false)
- {
- if($this->oc_settings->load('static:application_token'))
- {
- $status = $this->oc_module->statusLoad(); $language = $this->oc_module->languageLoad(); $store = $this->oc_module->storeLoad(); $return = $this->oc_module->returnStatusLoad();
-
- $now = $now || $status || $language || $store || $return;
- try
- {
- $this->oc_di->getSynchronize()->run($this->oc_module->getUrl('/module/settings/synchronize'), $now);
- return true;
- }
- catch (Extensions\IO\InvalidResultException $e)
- {
- }
- }
- return false;
- }
-
- protected function runHook($name, Extensions\Hook\Variables $variables)
- {
- if(!$variables->get('language_id'))
- {
- $language_iso = isset($this->session->data['language']) ? $this->session->data['language'] : null;
- $variables->set('language_id', (int) BulkGate\CartSms\Helpers::getLanguageId($language_iso, $this->oc_di->getDatabase()));
- }
-
- $hook = new Extensions\Hook\Hook(
- $this->oc_di->getModule()->getUrl('/module/hook'),
- $variables->get('language_id', 0),
- $variables->get('store_id', (int) ($this->config->get('config_store_id') ?: 0)),
- $this->oc_di->getConnection(),
- $this->oc_settings,
- new BulkGate\CartSms\HookLoad($this->oc_di->getDatabase())
- );
-
- try
- {
- $hook->run((string) $name, $variables);
- return true;
- }
- catch (Extensions\IO\InvalidResultException $e)
- {
- return false;
- }
- }
-
-}
diff --git a/upload/system/library/cartsms/cartsms.ocmod.xml b/upload/system/library/cartsms/cartsms.ocmod.xml
deleted file mode 100644
index 3f4d24a..0000000
--- a/upload/system/library/cartsms/cartsms.ocmod.xml
+++ /dev/null
@@ -1,179 +0,0 @@
-
-
- CartSMS - SMS module for OpenCart
- 6.00
- BulkGate
- http://www.cartsms.com/
-
-
-
-
- user->hasPermission('access', 'cartsms/sign'))
- {
- // CartSMS
- $cartsms = new CartSms\Init($this->registry);
- $data['menus'][] = $cartsms->menu();
- }
- ]]>
-
-
-
-
-
- response->setOutput($this->load->view('sale/order_info', $data));
- ]]>
-
- registry);
-
- if($init->di()->getSettings()->load("static:application_token", false))
- {
- $data['cartsms_css'] = $init->di()->getModule()->getUrl('/'.(defined('BULKGATE_DEV_MODE') ? 'dev' : 'dist').'/css/bulkgate-cartsms.css');
- $data['cartsms_application_id'] = $init->di()->getSettings()->load('static:application_id', '');
- $data['cartsms_language'] = $init->di()->getSettings()->load('main:language', 'en');
- $data['cartsms_widget_api_url'] = $init->di()->getModule()->getUrl('/'.(defined('BULKGATE_DEV_MODE') ? 'dev' : 'dist').'/widget-api/widget-api.js');
- $data['cartsms_authenticate'] = \BulkGate\CartSms\Helpers::fixUrl($this->url->link('cartsms/sign/authenticate', 'user_token=' . $this->session->data['user_token'], true));
- $data['cartsms_customer_iso'] = \BulkGate\CartSms\Helpers::getCountryCode($init->di()->getDatabase(), isset($order_info['payment_country_id']) ? $order_info['payment_country_id'] : -1);
- }
- }
-
- ]]>
-
-
-
-
-
-