Цей огляд заснований на diff найпростіших вбудованих модулів. Але все перевірено на простому власному модулі, в якому немає ані подій, ані vQmod.
Основні зміни у коді стандартного модуля
* Примітка
Слово Opencart, виділене жовтим кольором — це код модуля (!). Але, мабуть, ви й самі здогадалися
У контролері
OpenCart 3
OpenCart 4
class ControllerExtensionModuleAccount extends Controller {
namespace Opencart\Admin\Controller\Extension\Opencart\Module;
class Account extends \Opencart\System\Engine\Controller {
protected function validate() {
public function save(): void {
// ...
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode($json));
}
private $error = array();
Більше не потрібно. Всі помилки обробляються відразу в методі save()
if (isset($this->error['warning'])) {
$data['error_warning'] = $this->error['warning'];
} else {
$data['error_warning'] = '';
}
Не потрібно. Обробка помилок відбувається у методі save(). також Обробка помилок
if (($this->request->server['REQUEST_METHOD'] == 'POST') && $this->validate()) {
Більше не використовується. Збереження форми відбувається шляхом надсилання AJAX-запиту до методу save()
$data['action'] = $this->url->link('extension/module/account', 'user_token=' . $this->session->data['user_token'], true);
// 4.0.0.0
$data['save'] = $this->url->link('extension/opencart/module/account|save', 'user_token=' . $this->session->data['user_token']);
// 4.0.2.0
$data['save'] = $this->url->link('extension/opencart/module/account.save', 'user_token=' . $this->session->data['user_token']);
*Зміни у побудові шляхів впливають на будь-які посилання всередині модуля. Ну, це і так зрозуміло.
$data['cancel'] = $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=module', true);
$data['back'] = $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=module');
$this->load->model('extension/dashboard/map');
$this->load->model('extension/opencart/dashboard/map');
$results = $this->model_extension_dashboard_map->getTotalOrdersByCountry();
$results = $this->model_extension_opencart_dashboard_map->getTotalOrdersByCountry();
if (isset($this->request->post['module_account_status'])) {
$data['module_account_status'] = $this->request->post['module_account_status'];
} else {
$data['module_account_status'] = $this->config->get('module_account_status');
}
$data['module_account_status'] = $this->config->get('module_account_status');
У моделі
OpenCart 3
OpenCart 4
class ModelExtensionDashboardMap extends Model {
namespace Opencart\Admin\Model\Extension\Opencart\Dashboard;
class Map extends \Opencart\System\Engine\Model {
У в'юшці
В OpenCart 4.0.0.0 був FontAwesome 5.15.4, а в OpenCart 4.0.2.0 FontAwesome 6.1.1. Це впливає на класи іконок.
OpenCart 3
OpenCart 4
pull-right
float-end
data-toggle="tooltip"
data-bs-toggle="tooltip"
<i class="fa fa-save"></i>
<!-- 4.0.0.0 -->
<i class="fas fa-save"></i>
<!-- 4.0.2.0 -->
<i class="fa-solid fa-save"></i>
{{ cancel }}
{{ button_cancel }}
{{ back }}
{{ button_back }}
<ul class="breadcrumb">
<ol class="breadcrumb">
<li><a href="{{ breadcrumb.href }}">{{ breadcrumb.text }}</a></li>
<li class="breadcrumb-item"><a href="{{ breadcrumb.href }}">{{ breadcrumb.text }}</a></li>
{% if error_warning %}
<div class="alert alert-danger alert-dismissible"><i class="fa fa-exclamation-circle"></i> {{ error_warning }}
<button type="button" class="close" data-dismiss="alert">×</button>
</div>
{% endif %}{% if error_warning %}
-- (AJAX)
<div class="panel panel-default">
<div class="card">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-pencil"></i> {{ text_edit }}</h3>
</div>
<!-- 4.0.0.0 -->
<div class="card-header"><i class="fas fa-pencil-alt"></i> {{ text_edit }}</div>
<!-- 4.0.2.0 -->
<div class="card-header"><i class="fa-solid fa-pencil"></i> {{ text_edit }}</div>
<div class="panel-body">
<div class="card-body">
<form action="{{ action }}" method="post" enctype="multipart/form-data" id="form-module" class="form-horizontal">
<form id="form-module" action="{{ save }}" method="post" data-oc-toggle="ajax">
* Якщо пропустити data-oc-toggle="ajax", то форма обробиться по-старому з повним завантаженням сторінки (принаймні у версії 4.0.0.0). Хоча сам .alert у bootstrap 5 трохи змінився.
<div class="form-group">
<div class="row mb-3">
control-label
col-form-label
<select name="module_account_status" ...
<!-- 4.0.2.0 -->
<input type="checkbox" name="module_account_status" ...
Шляхи в AJAX-запитах
url: 'index.php?route=extension/module/imagescanner/getNotUsedImagesList
<!-- 4.0.0.0 -->
url: 'index.php?route=extension/imagescanner/module/imagescanner|getNotUsedImagesList
<!-- 4.0.2.0 -->
url: 'index.php?route=extension/imagescanner/module/imagescanner.getNotUsedImagesList
Шляхи до зображень
var img_loader = new Image().src='view/image/imagescanner-loader.gif';
var img_loader = new Image().src='extension/imagescanner/admin/view/image/imagescanner-loader.gif';
А воно запитує: http://opencart-4000.loc/admin/extension/imagescanner/admin/view/image/imagescanner-loader.gif
Значить треба вказати повний шлях до файлу
var img_loader = new Image().src='{{ constant('HTTP_CATALOG') }}extension/imagescanner/admin/view/image/imagescanner-loader.gif';
Обробка помилок
Щоб поля з помилками підсвічувалися і до них були пояснювальні підписи, першою дією у в'юшці необхідно вписати порожні контейнери для текстів помилок.
<div id="error-field" class="invalid-feedback"></div>
Далі, при обробці форми по AJAX можна отримати наступний формат відповіді:
{
"error": {
"name_1": "Product Name must be greater than 1 and less than 255 characters!",
"keyword_0_1": "SEO URL keyword required!",
"warning": "Warning: Please check the form carefully for errors!"
}
}
Потім воно буде універсально оброблено в admin/view/javascript/common.js з урахуванням того, чи це рядок, чи це об'єкт, та чи є там редірект. Також воно автоматично переведе under_score індекси массиву з помилками в kebab-case html-ідентифікаторів, в яких потрібно показати відповідні тексти червоним кольором.
TypeError: Cannot access offset of type string on string in при обробці помилок
У js при обробці відповіді AJAX-запиту робиться розмежування між отриманим рядком та об'єктом. Все тому, що в найпростішому "модулі" (наприклад, account) в помилки потрапляє лише рядок з текстом без зайвих проблем. І ось я скопіпастив звідти, а потім побачив, що в товарах йде інакше, і скопіпастив шматок коду ще й звідти. У результаті напоровся на помилку: TypeError: Cannot access offset of type string on string in ...
$json['error'] = $this->language->get('error_text'); // В найпростішому модулі присвоюється рядок
...
if (isset($json['error']) && !isset($json['error']['warning'])) {
$json['error']['warning'] = $this->language->get('error_warning'); // Намагається рядку присвоїти індекс масиву, але це ж PHP 8...
}
Власні бібліотеки у складі модуля
Якщо ваш модуль використовує бібліотеку, яка зазвичай завантажувалася в system/library, то зараз при розпакуванні архіву вона потрапить до extnension/modulecode/system/library/ .
OpenCart автоматично створює простори імен, як для контролерів з моделями, так і для бібілотек:
// 4.0.0.0
[Opencart\Admin\Controller\Extension\Imagescanner] => Array
(
[directory] => .../opencart-4000.loc/extension/imagescanner/admin/controller/
[psr4] =>
)
[Opencart\Admin\Model\Extension\Imagescanner] => Array
(
[directory] => .../opencart-4000.loc/extension/imagescanner/admin/model/
[psr4] =>
)
[Opencart\System\Extension\Imagescanner] => Array
(
[directory] => .../opencart-4000.loc/extension/imagescanner/system/
[psr4] =>
)
// 4.0.2.0
[Opencart\System\Library\Extension\Imagescanner] => Array
(
[directory] => .../opencart-4021.loc/extension/imagescanner/system/library/
[psr4] =>
)
Зверніть увагу, що варіант іменування класу бібліотеки ImageScanner при підключенні перетвориться на image_scanner.php (system/engine/autoloader.php). Тоді як Imagescanner відповідатиме imagescanner.php. Це при тому, що в контролері назва класу в стилі CamelCase працює ок. А чому так — це вже окрема історія.
У файлі бібліотеки
Задамо простір імен:
// 4.0.0.0
namespace Opencart\System\Extension\Modulecode\Library;
// 4.0.2.0
namespace Opencart\System\Library\Extension\Imagescanner;
Називаємо клас:
class Yourclassname {
У файлі контролера
Створюємо екземпляр класу бібліотеки у нашому контролері:
// 4.0.0.0
$this->instance = new \Opencart\System\Extension\Modulecode\Library\Yourclassname();
// 4.0.2.0
$this->stdelog = new \Opencart\System\Library\Extension\Imagescanner\Stdelog('imagescanner');
З іншого боку чудово відпрацює і по-старому, і ще й ніяких приколів зі змінами в системі:
require_once DIR_EXTENSION . 'modulecode/system/library/modulecode.php';
$this->instance = new Yourclassname(); // и не паритися
Папка модуля
На прикладі присутньої в системі папки extension/opencart (де складені всі дефолтні модулі) здавалося, що OpenCart 4 з'явилося поняття "папка постачальника". Але потім з'ясувалося, що при спробі встановити в ту ж папку інший свій модуль, воно не працює
До речі, якщо в інсталяційному архіві будуть нестандартні шляхи до файлів (я, наприклад, пробував modulecode/library/file.php на 4.0.0.0), то при видаленні модуля з адмінки папка модуля не видаляється, хоча всі стандартні файли та папки звідти видалені. Тобто це може створити проблеми при оновленні модуля, адже в існуючу папку модуля установник не хоче записувати.
І ще з цією папкою є один приємний момент: щоб упакувати модуль, досить просто скопіювати папку та заархівувати. Більше не потрібно ритися в папках і копіювати кожен файл окремо.
P.S.
На преший погляд, адаптація модуль під OpenCart 4 має бути досить простою. Я цю статтю довше писав, ніж адаптував модуль. Але огляд поки що не повний. Дописуватиму згодом. Також буду додавати в текст статті зауваження з коментарів.
--