FAQ: Как отдавать обновления модулей через Центр обновлений¶
Это статья для разработчиков, которые предлагают модули для CS-Cart и Multi-Vendor и хотят упростить процесс обновления для своих пользователей. Вместо того, чтобы разрабатывать собственный механизм или просить клиентов вручную менять файлы, вы можете просто предложить им перейти в раздел Администрирование → Центр обновлений и получить обновления для модулей там.
Когда выпускать обновления модулей?¶
Есть две основные причины для выпуска обновления:
Вы разработали новую функциональность и готовы отдать её клиентам.
В ядре CS-Cart или Multi-Vendor произошли изменения:
удалены константы, классы, функции или хуки, которые используются вашим модулем;
поменялись аргументы у функций или хуков, которые используются вашим модулем;
константы, классы, функции или хуки, которые используются вашим модулем, помечены как устаревшие (deprecated). Мы не удаляем их сразу, но лучше переключиться на новую функциональность до того, как мы всё-таки удалим старую;
в базе данных изменилась структура таблиц, используемых модулем.
Это изменение не обязательно приведёт к поломке модуля — мы сохраняем обратную совместимость между релизами CS-Cart. Но если модуль использует прямые запросы к таблицам вместо функций ядра, то модуль может перестать работать.
Примечание
Примерно в то же время, когда мы выпускаем новую версию CS-Cart/Multi-Vendor, мы также анонсируем изменения ядра в специальном разделе документации для разработчиков.
Куда загружать пакеты обновлений?¶
Если хотите, чтобы покупатели могли обновить модуль через Центр обновлений, то есть два способа:
- CS-Cart Marketplace. Там есть все необходимые инструменты для сборки и распространения обновлений. Пока эти инструменты в закрытом бета-тестировании; чтобы получить доступ, напишите нам на marketplace@cs-cart.com.
 - Собственный сервер обновлений. Он нужен, если вы используете собственный механизм лицензирования и раздачи пакетов обновлений. Такой подход потребует от вас специальным образом подготовить модуль и самостоятельно собирать пакеты обновлений. Инструкции есть дальше в статье.
 
Как сделать модуль совместимым только с определёнными версиями CS-Cart?¶
В файле addon.xml можно задать все требования модуля: поддерживаемые версии CS-Cart и/или Multi-Vendor, PHP и его расширения, необходимые и конфликтующие модули.
Важно
Требования в файле addon.xml проверяются только в процессе установки модуля. Они НЕ проверяются при обновлении.
Если после обновления требования модуля поменялись, то недостаточно поменять addon.xml. Чтобы клиенты не установили обновление, которое не будет работать с их версией, у вас есть два варианта:
- передавайте версию CS-Cart/Multi-Vendor на ваш сервер обновлений, и пусть сервер решает, предлагать ли обновление;
 - добавьте в пакет обновления валидатор, и пусть он не даёт установить обновление, если не выполнены определённые требования.
 
Как подготовить модуль к работе с Центром обновлений?¶
CS-Cart и Multi-Vendor проверяют наличие обновлений в 2 случаях:
- Когда кто-то заходит в панель администратора (но только если в Настройки → Общие включена настройка Проверять наличие обновлений автоматически).
 - Когда кто-то заходит в Администрирование → Центр обновлений.
 
Вот несколько вещей, которые нужно учесть при адаптации модуля для работы с Центром обновлений:
- Чтобы проверять наличие обновлений и скачивать их, в модуле должен быть коннектор Центра обновлений. Этот коннектор — класс, который должен быть размещен в app/addons/[sample_addon]/Tygh/UpgradeCenter/Connectors/[SampleAddon]/Connector.php, где
[sample_addon]— идентификатор модуля;[SampleAddon]— идентификатор модуля, записанный в CamelCase.
 - Коннектор должен расширять класс 
\Tygh\UpgradeCenter\Connectors\BaseAddonConnectorи реализовывать интерфейс\Tygh\UpgradeCenter\Connectors\IConnector. - Адрес сервера обновлений указывается в поле 
urlрезультата вызова методаConnector::getConnectionData. - Данные, передаваемые на сервер обновлений для проверки наличия обновлений, задаются в поле 
dataрезультата вызова методаConnector::getConnectionData. Полеdataпредставляет из себя массив видаparameter_name => parameter_value. - Непосредственное скачивание пакета обновлений реализуется в методе 
Connector::downloadPackage. 
Вот пример коннектора с комментариями:
<?php
namespace Tygh\UpgradeCenter\Connectors\SampleAddon;
use Tygh\Addons\SchemesManager;
use Tygh\Http;
use Tygh\Registry;
use Tygh\Settings;
use Tygh\Tools\Url;
use Tygh\UpgradeCenter\Connectors\BaseAddonConnector;
use Tygh\UpgradeCenter\Connectors\IConnector;
/**
 * Класс Connector реализует коннектор Центра обновлений для модуля Sample Add-on.
 *
 * @package Tygh\UpgradeCenter\Connectors\SampleAddon
 */
class Connector extends BaseAddonConnector implements IConnector
{
    /**
     * @var string Имя параметра HTTP-запроса, который определяет, какое действие требуется от сервера обновлений
     */
    const ACTION_PARAM = 'dispatch';
    /**
     * @var string Значение параметра ACTION_PARAM, при котором сервер обновлений поймёт, что CS-Cart запрашивает проверку наличия обновлений
     */
    const ACTION_CHECK_UPDATES = 'updates.check';
    /**
     * @var string Значение параметра ACTION_PARAM, при котором сервер обновлений поймёт, что CS-Cart запрашивает скачивание пакета обнолвений
     */
    const ACTION_DOWNLOAD_PACKAGE = 'updates.download_package';
    /**
     * @var string Уникальный идентификатор модуля
     */
    protected $addon_id = 'sample_addon';
    /**
     * @var string Текущая версия модуля
     */
    protected $addon_version;
    /**
     * @var string URL магазина
     */
    protected $product_url;
    public function __construct()
    {
        parent::__construct();
        // адрес сервера обновлений нужно указывать в конструкторе Connector
        $this->updates_server = 'https://updates.example.com';
        // данные модуля
        $addon = SchemesManager::getScheme($this->addon_id);
        $this->addon_version = $addon->getVersion() ? $addon->getVersion() : '1.0';
        // значение настройки 'license_number' модуля
        $this->license_number = (string) Settings::instance()->getValue('license_number', $this->addon_id);
        // данные о магазине
        $this->product_name = PRODUCT_NAME;
        $this->product_version = PRODUCT_VERSION;
        $this->product_build = PRODUCT_BUILD;
        $this->product_edition = PRODUCT_EDITION;
        $this->product_url = Registry::get('config.current_location');
    }
    /**
     * Пердоставляет данные для подключения к серверу обновлений модуля.
     * Вызывается при проверке на наличие доступных обновлений.
     *
     * @return array
     */
    public function getConnectionData()
    {
        // номер лицензии магазина можно получить через $this->uc_settings['license_number']
        $data = [
            self::ACTION_PARAM => self::ACTION_CHECK_UPDATES,
            'addon_id'         => $this->addon_id,
            'addon_version'    => $this->addon_version,
            'license_number'   => $this->license_number,
            'product_name'     => $this->product_name,
            'product_version'  => $this->product_version,
            'product_build'    => $this->product_build,
            'product_edition'  => $this->product_edition,
            'product_url'      => $this->product_url,
        ];
        $headers = [];
        return [
            'method'  => 'get',
            'url'     => $this->updates_server,
            'data'    => $data,
            'headers' => $headers,
        ];
    }
    /**
     * Скачивает пакет обновлений модуля.
     *
     * @param array  $schema           Схема пакета обновлений
     * @param string $package_path     Абсолютный путь на сервере, куда нужно поместить пакет обновлений
     *
     * @return array
     */
    public function downloadPackage($schema, $package_path)
    {
        $download_url = new Url($this->updates_server);
        $download_url->setQueryParams(array_merge($download_url->getQueryParams(), [
            self::ACTION_PARAM => self::ACTION_DOWNLOAD_PACKAGE,
            'package_id'       => $schema['package_id'],
            'addon_id'         => $this->addon_id,
            'license_number'   => $this->license_number,
        ]));
        $download_url = $download_url->build();
        $request_result = Http::get($download_url, [], [
            'write_to_file' => $package_path,
        ]);
        if (!$request_result || strlen($error = Http::getError())) {
            $download_result = [false, __('text_uc_cant_download_package')];
            fn_rm($package_path);
        } else {
            $download_result = [true, ''];
        }
        return $download_result;
    }
}
Как подготовить пакет обновлений?¶
Основная информация¶
Для сборки пакета обновлений нужны два архива: со старой и новой версиями модуля. Чтобы собрать пакет обновлений, используйте следующие команды из нашего SDK:
cscart-sdk addon:export   #экспортировать архив с текущей версией модуля
cscart-sdk addon:build_upgrade   #собрать пакет обновлений
Важно
Чтобы собирать обновления c помощью SDK, придерживайтесь следующей структуры модуля при разработке:
├── app
│   └── addons
│       └── [sample_addon]
│           ├── addon.xml
│           ├── config.php
│           ├── func.php
│           ├── Tygh
│           │   └── UpgradeCenter
│           │       └── Connectors
│           │           └── [SampleAddon]
│           │               └── Connector.php
│           └── upgrades
│               ├── [version1]
│               │   ├── migrations
│               │   │   ├── 467676233_migration1.php
│               │   │   └── 467676233_migration2.php
│               │   │
│               │   ├── validators
│               │   │   ├── validator1.php
│               │   │   └── validator2.php
│               │   │
│               │   ├── scripts
│               │   │   ├── pre_script.php
│               │   │   └── post_script.php
│               │   │
│               │   ├── extra_files
│               │   │   ├── extra_file1.php
│               │   │   └── extra_file2.php
│               │   │
│               │   └── extra
│               │       └── extra.php
│               │
│               ├── [version2]
│                   │   ├── migrations
│                   │   │   ├── 467676233_migration1.php
│                   │   │   └── 467676233_migration2.php
│                   │   │
│                   │   ├── validators
│                   │   │   ├── validator1.php
│                   │   │   └── validator2.php
│                   │   │
│                   │   ├── scripts
│                   │   │   ├── pre_script.php
│                   │   │   └── post_script.php
│                   │   │
│                   │   ├── extra_files
│                   │   │   ├── extra_file1.php
│                   │   │   └── extra_file2.php
│                   │   │
│                   │   ├── extra
│                   │   │   └── extra.php
...
[sample_addon]— идентификатор модуля;[SampleAddon]— идентификатор модуля в CamelCase;[version1]— номер версии, например 1.1.0.[version2]— номер версии, например 1.1.1.app/addons/[sample_addon]/upgrades/[version]/migrations— папка с миграциями, которые надо применить при обновлении до [version].app/addons/[sample_addon]/upgrades/[version]/validators— папка с валидаторами, которые должны выполнить свои проверки перед обновлением до [version].app/addons/[sample_addon]/upgrades/[version]/scripts— папка с pre/post-скриптами, которые нужно выполнить перед или после обновления до [version].app/addons/[sample_addon]/upgrades/[version]/extra_files— папка с дополнительными файлами, которые используются только в процессе обновления и не добавляются в установку CS-Cart/Multi-Vendor.app/addons/[sample_addon]/upgrades/[version]/extra/extra.php— файл для расширения package.json пакета обновлений.Примечание
Файлы и папки в app/addons/[sample_addon]/upgrades/[version] не обязательны. Например, если у новой версии нет изменений в базе данных, то нет необходимости создавать папку с миграциями.
Миграции¶
Миграции применяются во время обновления и меняют структуру таблиц в базе данных и сами данные в них.
Для написания миграций используйте Phinx. Обратите внимание, что в CS-Cart старая версия Phinx (0.4.3), поэтому не все инструкции из современной документации могут подойти. Вот старая документация Phinx 0.4.3 о:
Класс миграции должен содержать метод up, который будет выполняться в процессе обновления.
Например:
use Phinx\Migration\AbstractMigration;
class AddonsSampleAddonUpdateVersion extends AbstractMigration
{
    public function up()
    {
        $options = $this->adapter->getOptions();
        $pr = $options['prefix'];
        $this->execute("UPDATE {$pr}addons SET version = '1.1' WHERE addon = 'sample_addon'");
    }
}
Разделяйте изменения по отдельным миграциям; каждая миграция должна содержать одно логически завершённое действие.
Не переименовывайте сгенерированные в формате YYYYMMDDHHMMSS_my_new_migration.php миграции.  В случае, если вы пишете миграции вручную, присваивайте им имена согласно правилам Phinx.
Не используйте чистый SQL в миграциях для изменения структуры таблицы; используйте только методы Phinx.
Не используйте функции ядра CS-Cart в миграциях: нет гарантии, что они будут доступны во время обновления модуля. В результате процесс обновления может прерваться, а магазин — сломаться.
Валидаторы¶
Валидаторы проверяют перед установкой обновления, соответствует ли магазин определённым требованиям. Каждый валидатор — отдельный класс в пространстве имён Tygh\UpgradeCenter\Validators.
Валидатор должен реализовывать интерфейс IValidator и содержать 2 обязательных метода:
getName()должен возвращать строку с отображаемым именем валидатора;check($schema, $request)должен возвращать массив с двумя значениями:- флаг (boolean), который означает, что проверка успешно пройдена;
 - строка (string) с сообщением, которое отобразится, если результат проверки будет неудачным.
 
Например:
<?php
namespace Tygh\UpgradeCenter\Validators;
/**
 * Проверяет минимальную версию PHP.
 */
class PhpVersionValidator implements IValidator
{
    protected $minimal_php_version = '5.6.0';
    /** @inheritdoc */
    public function check($schema, $request)
    {
        if (version_compare(PHP_VERSION, $this->minimal_php_version) == -1) {
            return [
                false,
                __('checking_php_version_is_not_suitable', [
                    '[version]' => PHP_VERSION,
                    '[min]'     => $this->minimal_php_version,
                    '[max]'     => '7.x',
                ]),
            ];
        }
        return [true, []];
    }
    /** @inheritdoc */
    public function getName()
    {
        return 'PHP Version';
    }
}
Скрипты¶
Скрипты расширяют или меняют то, как Центр обновлений работает с вашим пакетом обновлений при обновлении. Есть 2 типа скриптов:
перед обновлением: скрипт pre_script.php выполняется после того, как прошли все проверки валидаторов;
после обновления: скрипт post_script.php выполняется после того, как установлен пакет обновлений. Пост-скрипт в основном используется для того, чтобы показывать какие-то уведомления после обновления. Чтобы сделать такое уведомление, добавьте новый элемент в массив
$upgrade_notesв скрипте:<?php $upgrade_notes[] = [ 'title' => 'Sample Add-on v1.1 Changes', 'message' => 'Sample Add-on v1.1 Changes Description', ];
Эти скрипты включены в контекст класса \Tygh\UpgradeCenter\App и могут использовать все свойства и методы этого класса. Также вы можете в них использовать все функции и классы CS-Cart.
Папка Extra Files¶
В папку extra_files вы можете положить файлы, которые используются только при обновлении и не добавляются в установку CS-Cart/Multi-Vendor.
Расширение схемы пакета обновлений¶
Чтобы расширить схему, напишите свой скрипт в файле extra.php. Скрипт должен возвращать массив. Этот массив сольётся с package.json пакета обновлений.
Так вы можете добавлять любые дополнительные данные в пакет обновлений. Эти данные можно использовать в процессе обновления в пре- и пост-скриптах.
Например, вот как можно предложить клиентам пропустить встроенное в CS-Cart резервное копирование при установке обновления:
<?php
return [
    'backup' => [
        'is_skippable'    => true,
        'skip_by_default' => true,
    ],
];
Как настроить собственный сервер обновлений?¶
Примечание
Свой сервер обновлений настраивать необязательно — есть и другой путь. Но если ваши модули используют свой механизм лицензирования, то стоит завести свой сервер обновлений.
Когда CS-Cart отправляет запрос о наличии обновлений, сервер обновлений должен вернуть XML следующего вида:
<?xml version="1.0" encoding="utf-8" ?>
<update>
    <packages>
        <item id="unique_update_package_id">
            <file>update_package_name.zip</file>
            <name>Update package name</name>
            <description><![CDATA[Update package description.]]></description>
            <from_version>1.0</from_version>
            <to_version>1.1</to_version>
            <timestamp>1547199854</timestamp>
            <size>2048</size>
        </item>
    </packages>
</update>
update/packages/itemсодержит в себе информацию о доступном обновлении;update/packages/item@id— уникальный идентификатор пакета обновлений. Когда коннектор передаёт этот идентификатор серверу обновлений, то сервер должен предоставить архив с пакетом обновлений;update/packages/item/file— название архива с пакетом обновлений;update/packages/item/name— заголовок пакета обновлений (отобразится в Центре обновлений);update/packages/item/description— описание пакета обновлений. Может содержать HTML-разметку;update/packages/item/from_version— текущая версия модуля;update/packages/item/to_version— версия, до которой будет обновлен модуль;update/packages/item/timestamp— дата создания пакета обновлений в формате UNIX timestamp;update/packages/item/size— размер пакета обновлений в байтах.Примечание
Если доступных обновлений нет, сервер должен вернуть пустой ответ.
Вот как данный пакет обновлений отобразится в Центре обновлений: