FAQ: Как отдавать обновления модулей через Центр обновлений

Это статья для разработчиков, которые предлагают модули для CS-Cart и Multi-Vendor и хотят упростить процесс обновления для своих пользователей. Вместо того, чтобы разрабатывать собственный механизм или просить клиентов вручную менять файлы, вы можете просто предложить им перейти в раздел Администрирование → Центр обновлений и получить обновления для модулей там.

Когда выпускать обновления модулей?

Есть две основные причины для выпуска обновления:

  1. Вы разработали новую функциональность и готовы отдать её клиентам.

  2. В ядре 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 случаях:

  1. Когда кто-то заходит в панель администратора (но только если в Настройки → Общие включена настройка Проверять наличие обновлений автоматически).
  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.

Вот пример коннектора с комментариями:

Скачать пример Connector.php

<?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'");
    }
}

Разделяйте изменения по отдельным миграциям; каждая миграция должна содержать одно логически завершённое действие.

Не используйте чистый 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 — размер пакета обновлений в байтах.

    Примечание

    Если доступных обновлений нет, сервер должен вернуть пустой ответ.

Вот как данный пакет обновлений отобразится в Центре обновлений: