Статьи

Создание системы обновления с управлением лицензией: выполнение обновления

В первых двух руководствах из этой серии мы создали плагин WordPress для управления лицензиями на программное обеспечение. В первой части мы создали инструменты администрирования, необходимые для добавления продуктов и лицензий, а во второй части мы создали API-интерфейс, который плагины и темы могут использовать для доступа к информации о лицензии и продукте.

Теперь пришло время связать все это вместе и заставить тему WordPress и плагин WordPress использовать сервер лицензий для проверки обновлений. Для этого мы создадим класс, который можно будет перетащить в любой плагин WordPress или кодовую базу темы, чтобы тема или плагин использовали сайт WordPress, на котором работает наш плагин WP License Manager для проверки обновлений.

В этом уроке вы узнаете о следующих темах:

  • Как подключиться к системе обновления тем WordPress.
  • Как подключиться к системе обновления плагинов WordPress (да, оба они раздельные, но схожие, и их нужно решать по одному).
  • Как вызвать внешний API из WordPress.

Функциональность, которую мы создадим в этом уроке, требует от нас более глубокого понимания того, как WordPress работает под его капотом, что делает этот урок немного более продвинутым, чем первые два в серии. Тем не менее, мы тщательно проработаем каждый шаг, поэтому, если вы уже следовали этому уроку, не должно возникнуть никаких проблем с его завершением.

Так же, как и в предыдущих частях, вы можете следовать учебному руководству по написанию собственного кода по ходу работы или загрузить последний класс и использовать его в качестве справочного материала.

Давайте начнем.

Чтобы построить клиентский класс менеджера лицензий и протестировать его, вам нужна среда тестирования со следующими компонентами:

  • Сервер WordPress, на котором запущен плагин WP License Manager, созданный в частях 1 и 2 серии учебных пособий. Мы назовем это сервером лицензий .
  • Сервер WordPress для запуска плагина и темы для обновления. Мы назовем это тестовым сервером .
  • Тема для тестирования системы обновлений.
  • Плагин для тестирования системы обновления.

Вы можете использовать одну из существующих тем и плагинов для тестирования или создать заполнитель без реального содержимого или функций, кроме обновлений, следуя приведенным ниже инструкциям.

В wp-content/themes создайте новый каталог с именем hello-world-theme (например).

В этом каталоге добавьте файл style.css со стандартным заголовком темы. Это необходимо для WordPress, чтобы найти тему.

1
2
3
4
5
/*
Theme Name: Hello World Theme
Description: A test theme that says Hello.
Version: 0.1
*/

Добавьте файл index.php . Он может быть пустым, или вы можете использовать следующий заполнитель HTML:

1
2
3
4
5
6
7
8
<html>
    <head>
        <title>Hello, World</title>
    </head>
    <body>
        <h1>Hello, World!</h1>
    </body>
</html>

Для тестирования клиентского класса менеджера лицензий нам все еще нужен файл functions.php . Здесь вы инициализируете класс, который мы создадим в этом уроке — сейчас вы можете оставить файл пустым.

С этой минимальной настройкой WordPress примет тему без ошибок. Активируйте его сейчас.

После того, как вы определились с темой для использования, добавьте продукт, чтобы представить его на сервере лицензий. Назовите новый продукт «Hello World Theme» и установите для его номера версии значение, превышающее 0.1 мы определили в заголовке темы выше.

Отметьте часть «post slug» постоянной ссылки продукта ( hello-world-theme ), так как она будет использоваться в качестве идентификатора продукта при разговоре с API менеджера лицензий.

Создание пустого тестового плагина даже быстрее, чем создание тестовой темы. Все, что вам нужно, это файл PHP с пустым заголовком плагина.

В wp-content/plugins создайте каталог с именем hello-world-plugin и внутри него файл hello-world.php со следующим содержимым:

1
2
3
4
5
6
<?php
/*
Plugin Name: Hello World Plugin
Description: A test plugin that says Hello.
Version: 0.1
*/

Плагин готов, так что включите его.

Как и в случае с темой, создайте продукт для этого плагина на сервере диспетчера лицензий. Назовите продукт «Hello World Plugin » — это должно дать вам идентификатор (slug) hello-world-plugin — и установите для его номера версии значение, превышающее 0.1 определенное в заголовке плагина.

Теперь, когда вы настроили среду тестирования и у вас есть плагин и тема, готовая к тестированию, пришло время начать с самого класса клиента менеджера лицензий.

Давайте начнем создавать клиентский класс диспетчера лицензий, создав общую функциональность, которая используется совместно темами и плагинами. Поскольку нам нужно протестировать код в некотором контексте, мы начнем с темы и добавим специфические для плагина вещи, как только обновления темы будут работать.

В каталоге вашей темы ( hello-world-theme ) добавьте новый класс с именем Wp_License_Manager_Client (используя имя файла class-wp-license-manager-client.php ). Этот класс будет содержать весь код, связанный с проверкой лицензий и обработкой обновлений темы.

Поскольку этот файл класса предназначен для включения в любое количество тем и плагинов WordPress, окружите его class_exists . Таким образом, если есть много установленных плагинов и тем, которые используют этот класс, они не сломают систему, пытаясь создать класс много раз.

1
2
3
4
5
6
7
8
<?php
if ( ! class_exists( ‘Wp_License_Manager_Client’ ) ) {
 
    class Wp_License_Manager_Client {
     
    }
 
}

Давайте продолжим некоторую инициализацию, прежде чем перейти к реальной функциональности.

Сначала добавьте некоторые переменные, которые будут инициализированы в конструкторе класса. Комментарии к коду объясняют переменные, и я буду упоминать их снова, когда мы будем использовать их в коде.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
 * The API endpoint.
 *
 * @var String The API endpoint.
 */
private $api_endpoint;
 
/**
 * The product id (slug) used for this product on the License Manager site.
 * Configured through the class’s constructor.
 *
 * @var int The product id of the related product in the license manager.
 */
private $product_id;
 
/**
 * The name of the product using this class.
 *
 * @var int The name of the product (plugin / theme) using this class.
 */
private $product_name;
 
/**
 * The type of the installation in which this class is being used.
 *
 * @var string ‘theme’ or ‘plugin’.
 */
private $type;
 
/**
 * The text domain of the plugin or theme using this class.
 * Populated in the class’s constructor.
 *
 * @var String The text domain of the plugin / theme.
 */
private $text_domain;
 
/**
 * @var string The absolute path to the plugin’s main file.
 * class with a plugin.
 */
private $plugin_file;

Затем добавьте конструктор для установки этих переменных экземпляра:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
/**
 * Initializes the license manager client.
 *
 * @param $product_id string The text id (slug) of the product on the license manager site
 * @param $product_name string The name of the product, used for menus
 * @param $text_domain string Theme / plugin text domain, used for localizing the settings screens.
 * @param $api_url string The URL to the license manager API (your license server)
 * @param $type string The type of project this class is being used in (‘theme’ or ‘plugin’)
 * @param $plugin_file string The full path to the plugin’s main file (only for plugins)
 */
public function __construct( $product_id, $product_name, $text_domain, $api_url,
                             $type = ‘theme’, $plugin_file = » ) {
        // Store setup data
        $this->product_id = $product_id;
        $this->product_name = $product_name;
        $this->text_domain = $text_domain;
        $this->api_endpoint = $api_url;
        $this->type = $type;
        $this->plugin_file = $plugin_file;
    }
}

Чтобы лучше понять, для чего используются эти параметры, и чтобы связать класс с вашей темой, откройте файл functions.php вашей темы и создайте экземпляр класса Wp_License_Manager_Client :

01
02
03
04
05
06
07
08
09
10
11
<?php
require_once( ‘class-wp-license-manager-client.php’ );
 
if ( is_admin() ) {
    $license_manager = new Wp_License_Manager_Client(
        ‘hello-world-theme’,
        ‘Hello World Theme’,
        ‘hello-world-text’,
        ‘http://<URL_TO_LICENSE_SERVER>/api/license-manager/v1’
    );
}

Давайте рассмотрим код построчно:

Строка 2 : включите класс менеджера лицензий, который мы собираемся создать.

Строка 4 : Клиент менеджера лицензий необходим только на экранах администратора, поэтому, чтобы не создавать ненужных объектов, проверьте, просматривает ли пользователь один из экранов администрирования WordPress или нет.

Строки 5–9 : Создайте экземпляр Wp_License_Manager_Client используя следующие значения параметров:

  • hello-world-theme : слаг (id) продукта (темы) на сервере лицензий.
  • Hello World Theme : название темы. Он будет использоваться для экранов настроек и других ситуаций, когда нам нужно ссылаться на продукт по его названию.
  • hello-world-text : текстовый домен, используемый вашей темой для локализации. Использование одного и того же текстового домена для экранов управления лицензиями позволяет локализаторам переводить всю тему сразу.
  • http://<URL_TO_LICENSE_SERVER>/api/license-manager/v1 : это URL-адрес API, который мы создали в предыдущей части серии руководств. Замените URL_TO_LICENSE_SERVER на URL-адрес сайта WordPress, на котором установлен плагин WP License Manager.

Оставшиеся два параметра в конструкторе нужны только тогда, когда класс используется с плагином, поэтому мы пока оставим им значения по умолчанию и вернемся к ним позже в руководстве, когда мы перейдем к обновлениям плагина.

Прежде всего, пользователь должен иметь возможность использовать API менеджера лицензий — это действительный лицензионный ключ и способ ввести его в тему.

Для этого мы сейчас создадим новый экран настроек.

Поскольку мы уже создали ряд похожих экранов в частях 1 и 2 серии учебных пособий, я кратко рассмотрю код.

Обратите внимание, что каждый компонент функциональности, который входит в клиентский класс менеджера лицензий, должен быть активирован путем присоединения одной из функций класса к действию или фильтру WordPress. Таким образом, все остается чистым и простым в управлении. Чтобы максимально упростить разработку тем, которые будут использовать этот класс, я поместил всю инициализацию в конструктор класса.

Добавьте хуки, связанные с экраном настроек, в конструктор, прямо под кодом инициализации, который мы добавили на предыдущем шаге:

1
2
3
4
5
6
7
// Add actions required for the class’s functionality.
// NOTE: Everything should be done through actions and filters.
if ( is_admin() ) {
    // Add the menu screen for inserting license information
    add_action( ‘admin_menu’, array( $this, ‘add_license_settings_page’ ) );
    add_action( ‘admin_init’, array( $this, ‘add_license_settings_fields’ ) );
}

Затем создайте две упомянутые функции, add_license_settings_page и add_license_settings_fields . Обратите внимание, как мы используем переменные $product_name и $text_domain из конструктора при определении заголовка для страницы настроек.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
 * Creates the settings items for entering license information (email + license key).
 */
public function add_license_settings_page() {
    $title = sprintf( __( ‘%s License’, $this->text_domain ), $this->product_name );
 
    add_options_page(
        $title,
        $title,
        ‘read’,
        $this->get_settings_page_slug(),
        array( $this, ‘render_licenses_menu’ )
    );
}
 
/**
 * Creates the settings fields needed for the license settings menu.
 */
public function add_license_settings_fields() {
    $settings_group_id = $this->product_id .
    $settings_section_id = $this->product_id .
 
    register_setting( $settings_group_id, $this->get_settings_field_name() );
 
    add_settings_section(
        $settings_section_id,
        __( ‘License’, $this->text_domain ),
        array( $this, ‘render_settings_section’ ),
        $settings_group_id
    );
 
    add_settings_field(
        $this->product_id .
        __( ‘License e-mail address’, $this->text_domain ),
        array( $this, ‘render_email_settings_field’ ),
        $settings_group_id,
        $settings_section_id
    );
 
    add_settings_field(
        $this->product_id .
        __( ‘License key’, $this->text_domain ),
        array( $this, ‘render_license_key_settings_field’ ),
        $settings_group_id,
        $settings_section_id
    );
}

Обе функции, созданные выше, определяют количество обратных вызовов для рендеринга страницы настроек и полей настроек. Добавьте функции обратного вызова:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
 * Renders the description for the settings section.
 */
public function render_settings_section() {
    _e( ‘Insert your license information to enable updates.’, $this->text_domain);
}
 
/**
 * Renders the settings page for entering license information.
 */
public function render_licenses_menu() {
    $title = sprintf( __( ‘%s License’, $this->text_domain ), $this->product_name );
    $settings_group_id = $this->product_id .
 
    ?>
        <div class=»wrap»>
            <form action=’options.php’ method=’post’>
 
                <h2><?php echo $title;
 
                <?php
                    settings_fields( $settings_group_id );
                    do_settings_sections( $settings_group_id );
                    submit_button();
                ?>
 
            </form>
        </div>
    <?php
}
 
/**
 * Renders the email settings field on the license settings page.
 */
public function render_email_settings_field() {
    $settings_field_name = $this->get_settings_field_name();
    $options = get_option( $settings_field_name );
    ?>
        <input type=’text’ name='<?php echo $settings_field_name;
           value='<?php echo $options[’email’];
    <?php
}
 
/**
 * Renders the license key settings field on the license settings page.
 */
public function render_license_key_settings_field() {
    $settings_field_name = $this->get_settings_field_name();
    $options = get_option( $settings_field_name );
    ?>
        <input type=’text’ name='<?php echo $settings_field_name;
           value='<?php echo $options[‘license_key’];
    <?php
}

Чтобы минимизировать риск опечатки при вводе имени поля настроек, я создал вспомогательную функцию get_settings_field_name , которая используется во всех фрагментах кода выше. Другая вспомогательная функция, get_settings_page_slug , используется для определения пути к экрану настроек.

Добавьте эти две функции в конец класса, чтобы код работал без ошибок:

01
02
03
04
05
06
07
08
09
10
11
12
13
/**
 * @return string The name of the settings field storing all license manager settings.
 */
protected function get_settings_field_name() {
    return $this->product_id .
}
 
/**
 * @return string The slug id of the licenses settings page.
 */
protected function get_settings_page_slug() {
    return $this->product_id .
}

Функции используют $product_id переданный в конструкторе, чтобы создать поле настроек и фрагмент страницы настроек. Таким образом, каждая тема или плагин, использующий класс, получит свои собственные настройки и не будет никаких конфликтов.

Страница настроек теперь готова и выглядит следующим образом:

Экран настроек лицензии

Пользователь может легко пропустить такой параметр и в конечном итоге задаться вопросом, почему тема не получает обновлений. Поэтому рекомендуется создать напоминание, которое будет отображаться у администратора, пока пользователь не настроит тему и не введет лицензионный ключ.

Пришло время для другого действия WordPress. Действие admin_notices используется для всех уведомлений и ошибок WordPress, поэтому идеально подходит для наших целей.

В конструкторе, прямо под двумя действиями, которые мы добавили ранее, добавьте:

1
2
// Add a nag text for reminding the user to save the license information
add_action( ‘admin_notices’, array( $this, ‘show_admin_notices’ ) );

Затем добавьте функцию show_admin_notices :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
 * If the license has not been configured properly, display an admin notice.
 */
public function show_admin_notices() {
    $options = get_option( $this->get_settings_field_name() );
 
    if ( !$options || ! isset( $options[’email’] ) || ! isset( $options[‘license_key’] ) ||
        $options[’email’] == » ||
 
        $msg = __( ‘Please enter your email and license key to enable updates to %s.’, $this->text_domain );
        $msg = sprintf( $msg, $this->product_name );
        ?>
            <div class=»update-nag»>
                <p>
                    <?php echo $msg;
                </p>
 
                <p>
                    <a href=»<?php echo admin_url( ‘options-general.php?page=’ . $this->get_settings_page_slug() ); ?>»>
                        <?php _e( ‘Complete the setup now.’, $this->text_domain );
                    </a>
                </p>
            </div>
        <?php
    }
}

Функция сначала проверяет, были ли установлены опции. Если элемент параметров не существует или какое-либо из двух обязательных полей не заполнено, на нем отображаются уведомление и ссылка на страницу настроек лицензии темы, где пользователь может завершить настройку. Класс update-nag определяет внешний вид уведомления.

Вот как выглядит «ворчание» в верхней части панели администратора:

Напоминание об обновлении настроек лицензии

Теперь, чтобы избавиться от этого, давайте добавим информацию о лицензии.

Сначала зайдите в область администрирования WordPress сервера лицензий и создайте новую лицензию для тестовой темы ( Hello World Theme ). После того, как вы нажали « Добавить лицензию» , вы увидите список лицензий.

Скопируйте лицензионный ключ, сгенерированный плагином менеджера лицензий. Затем вернитесь на тестовый сайт и настройте параметры лицензии темы, введя свой адрес электронной почты и лицензионный ключ, скопированный с сервера менеджера лицензий.

Ваш надрыв исчез, и настройки вашей темы уже готовы для тестирования функциональности, которую мы создадим дальше.

Имея базовую структуру, давайте посмотрим на взаимодействие с нашим API менеджера лицензий.

Сначала добавьте функцию для вызова API диспетчера лицензий. Функция принимает два параметра: метод или действие API для вызова на сервере диспетчера лицензий и массив с параметрами для действия:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//
// API HELPER FUNCTIONS
//
 
/**
 * Makes a call to the WP License Manager API.
 *
 * @param $method String The API action to invoke on the license manager site
 * @param $params array The parameters for the API call
 * @return array The API response
 */
private function call_api( $action, $params ) {
    $url = $this->api_endpoint .
 
    // Append parameters for GET request
    $url .= ‘?’
 
    // Send the request
    $response = wp_remote_get( $url );
    if ( is_wp_error( $response ) ) {
        return false;
    }
         
    $response_body = wp_remote_retrieve_body( $response );
    $result = json_decode( $response_body );
     
    return $result;
}

Строка 13 : создайте URL-адрес для вызова, используя базовый URL-адрес, переданный в качестве параметра конструктора ( $api_endpoint ), и действие, переданное в качестве параметра ( $method ).

Строка 16 : используйте функцию PHP http_build_query для создания строки параметра в кодировке URL и добавления ее в запрос GET.

Строка 19 : используйте встроенную в WordPress функцию HTTP wp_remote_get для отправки запроса GET на URL-адрес, созданный в предыдущих строках.

Строки 20–22 : выполните базовую обработку ошибок, чтобы увидеть, произошла ли ошибка. Далее мы добавим функцию is_api_error .

Строки 24–25 : прочитайте ответ и проанализируйте его из строки в кодировке JSON в массив, содержащий данные, полученные с сервера лицензий.

Строка 27 : вернуть ответ.

Затем добавьте функцию проверки ошибок. Обработка очень грубая и не дает никаких подробностей о том, что пошло не так — достаточно просто знать, была ли ошибка или нет:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
/**
 * Checks the API response to see if there was an error.
 *
 * @param $response mixed|object The API response to verify
 * @return bool True if there was an error.
 */
private function is_api_error( $response ) {
    if ( $response === false ) {
        return true;
    }
 
    if ( ! is_object( $response ) ) {
        return true;
    }
 
    if ( isset( $response->error ) ) {
        return true;
    }
 
    return false;
}

Теперь, когда мы создали функцию для общения с API, мы можем использовать ее для вызова сервера лицензий, чтобы получить некоторую информацию о текущей теме или плагине.

Добавьте следующую функцию для вызова info действия:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
 * Calls the License Manager API to get the license information for the
 * current product.
 *
 * @return object|bool The product data, or false if API call fails.
 */
public function get_license_info() {
    $options = get_option( $this->get_settings_field_name() );
    if ( ! isset( $options[’email’] ) || ! isset( $options[‘license_key’] ) ) {
        // User hasn’t saved the license to settings yet.
        return false;
    }
 
    $info = $this->call_api(
        ‘info’,
        array(
            ‘p’ => $this->product_id,
            ‘e’ => $options[’email’],
            ‘l’ => $options[‘license_key’]
        )
    );
 
    return $info;
}

Строка 8: получить параметры клиента менеджера лицензий.

Строки 9–12 : убедитесь, что лицензионный ключ и параметры электронной почты установлены. Если у нас их нет, вызов API будет пустой тратой времени — мы уже знаем, что запрос не удастся.

Строки 14–21 : вызвать info действие API менеджера лицензий со следующими параметрами:

  • p : идентификатор продукта, определенный в конструкторе Wp_License_Manager_Client .
  • e : адрес электронной почты владельца лицензии, прочитайте в настройках.
  • l : лицензионный ключ, читайте из настроек.

Строка 23 : вернуть данные лицензии. Данные будут включать следующие поля:

  • name : название продукта (тема или плагин).
  • description : описание продукта.
  • version : текущая версия доступна на сервере.
  • package_url : URL-адрес для загрузки продукта. Это URL-адрес действия API get на сервере диспетчера лицензий.
  • last_updated : когда продукт был обновлен.
  • description_url : URL-адрес страницы, которая может использоваться для отображения дополнительной информации о продукте.
  • tested : самая высокая версия WordPress, на которой продукт был протестирован (необходим только для плагинов).
  • banner_low : Низкая (обычная) версия изображения баннера продукта (требуется только для плагинов).
  • banner_high : версия изображения баннера продукта с высоким разрешением (сетчатка) (требуется только для плагинов).

Далее, давайте использовать функцию, чтобы проверить, есть ли обновление, ожидающее загрузки. Добавьте следующую функцию:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
/**
 * Checks the license manager to see if there is an update available for this theme.
 *
 * @return object|bool If there is an update, returns the license information.
 * Otherwise returns false.
 */
public function is_update_available() {
    $license_info = $this->get_license_info();
    if ( $this->is_api_error( $license_info ) ) {
        return false;
    }
 
    if ( version_compare( $license_info->version, $this->get_local_version(), ‘>’ ) ) {
        return $license_info;
    }
 
    return false;
}

Строка 8 : запрос информации о продукте с помощью только что get_license_info функции get_license_info .

Строки 9–11 : если во время вызова API произошла ошибка, верните false. Также было бы неплохо показать ошибку пользователю — для простоты я оставил эту функциональность пока.

Строка 13 : используйте PHP version_compare чтобы увидеть, является ли номер версии, полученный с сервера, больше, чем локальный номер версии.

Строка 14 : если доступно обновление, верните данные лицензии. Возвращая данные сразу же, мы избавляем себя от необходимости делать дополнительный вызов API, чтобы получить данные снова.

Чтобы завершить эту функцию, нам все еще нужно реализовать функцию для получения локального номера версии, используемую в строке 13. Доступ к данным плагина и темы немного по-разному, поэтому функция будет иметь отдельные реализации для обоих:

01
02
03
04
05
06
07
08
09
10
11
12
/**
 * @return string The theme / plugin version of the local installation.
 */
private function get_local_version() {
    if ( $this->is_theme() ) {
        $theme_data = wp_get_theme();
        return $theme_data->Version;
    } else {
        $plugin_data = get_plugin_data( $this->plugin_file, false );
        return $plugin_data[‘Version’];
    }
}

Строка 2 : получить данные текущей темы.

Строка 3 : прочитать текущую версию и вернуть ее.

Строка 5 : данные плагина считываются с использованием другой функции get_plugin_data , которая использует имя основного файла плагина в качестве идентификатора. Мы установим переменную $this->plugin_file позже при интеграции в наш тестовый плагин.

Строка 6 : вернуть номер версии плагина.

Добавьте функцию is_theme для проверки типа продукта, внутри которого мы работаем:

1
2
3
private function is_theme() {
    return $this->type == ‘theme’;
}

Теперь мы создали функции, необходимые для подключения к серверу лицензий и проверки, доступна ли более новая версия. Затем пришло время для действительно интересной части: связать это с функциональностью обновления WordPress.

Основа для построения нашей темы и система обновления плагинов на месте. Пришло время углубиться в функциональность обновления WordPress.

В действии admin_init (и ряде других, более конкретных, действий), если прошло достаточно времени с момента последней проверки обновлений, WordPress сравнивает свои плагины и темы с версиями, размещенными в каталогах плагинов и тем WordPress.org, чтобы выяснить, новые версии были выпущены.

После проверки WordPress сохраняет результаты на временном сайте: update_themes для тем и update_plugins для плагинов.

Затем, когда вы посещаете страницу «Обновления» (или страницы «Темы и плагины»), WordPress проверяет эти переходные процессы, чтобы увидеть и отметить темы и плагины, для которых доступны обновления.

Когда вы посмотрите на код для этой функции (который вы можете найти в wp-includes/update.php ), вы увидите, что он был разработан для работы с официальными каталогами и ничего больше: здесь нет ловушек для указания сервера для проверять или анализировать запросы до их отправки.

Но это не помешало разработчикам плагинов и тем использовать свои собственные серверы лицензий — и это не остановит нас.

Решение заключается в том, как сохраняются результаты проверки обновлений. Я упоминал выше, что результаты поиска обновлений хранятся в переходном процессе. И в самом начале функции WordPress set_site_transient мы находим:

01
02
03
04
05
06
07
08
09
10
/**
 * Filter the value of a specific site transient before it is set.
 *
 * The dynamic portion of the hook name, $transient, refers to the transient name.
 *
 * @since 3.0.0
 *
 * @param mixed $value Value of site transient.
 */
$value = apply_filters( ‘pre_set_site_transient_’ . $transient, $value );

Этот фильтр дает нам доступ к содержимому данных плагина и обновлений темы до того, как они будут сохранены, как раз вовремя, чтобы добавить в него свои собственные данные!

Давайте начнем реализовывать наш собственный код проверки обновлений, начиная с тем.

Как мы видели выше, когда WordPress завершил проверку обновлений тем из каталога тем WordPress.org, он хранит информацию о темах, нуждающихся в обновлении, в переходном процессе с именем update_themes .

Чтобы проверить наши собственные обновления и добавить данные в переходный процесс до его сохранения, мы подключим собственную функцию к фильтру pre_set_site_transient_update_themes . В этой функции мы будем вызывать сервер лицензий для проверки обновлений этой темы, а затем, если доступно обновление, добавить информацию в переходный процесс.

Сначала добавьте фильтр в конце конструктора Wp_License_Manager_Client :

1
2
3
4
if ( $type == ‘theme’ ) {
    // Check for updates (for themes)
    add_filter( ‘pre_set_site_transient_update_themes’, array( $this, ‘check_for_update’ ) );
}

Затем создайте функцию check_for_update :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
 * The filter that checks if there are updates to the theme or plugin
 * using the WP License Manager API.
 *
 * @param $transient mixed The transient used for WordPress
 * theme / plugin updates.
 *
 * @return mixed The transient with our (possible) additions.
 */
public function check_for_update( $transient ) {
    if ( empty( $transient->checked ) ) {
        return $transient;
    }
 
    $info = $this->is_update_available();
    if ( $info !== false ) {
 
        if ( $this->is_theme() ) {
            // Theme update
            $theme_data = wp_get_theme();
            $theme_slug = $theme_data->get_template();
 
            $transient->response[$theme_slug] = array(
                ‘new_version’ => $info->version,
                ‘package’ => $info->package_url,
                ‘url’ => $info->description_url
            );
        } else {
            // Plugin updates will be added here.
        }
    }
 
    return $transient;
}

Строки 10–13 : $transient->checked — это массив со всеми установленными в данный момент темами и номерами их версий (таблица стилей темы в качестве ключа и версия в качестве значения). Если бы вы проверяли обновления сразу для нескольких тем, вы могли бы использовать этот массив для сбора данных и отправки их на сервер лицензий. В этой простой версии, однако, мы просто проверяем, что массив не пустой, и идем дальше.

Строки 15–16 . Проверьте, доступно ли обновление для этого продукта (темы) на нашем сервере лицензий, используя функцию is_update_available мы создали ранее. Функция возвращает информацию о продукте, если версия на сервере лицензий выше установленной, и false, если нет доступных обновлений.

Строка 18 : проверьте, является ли это тема или плагин. На данный момент мы сконцентрируемся только на обновлениях темы, поэтому я оставил ветку else пустым.

Строки 20–21 : найдите «слаг» темы, чтобы использовать его в качестве ключа для обозначения текущей темы, нуждающейся в обновлении.

Строки 23–27 . Вставьте информацию, относящуюся к обновлению темы, в массив $response переходного процесса, используя слаг в качестве ключа.

  • new_version : версия темы на сервере.
  • package : URL-адрес для загрузки пакета темы. Поскольку WordPress должен иметь возможность загружать тему непосредственно с этого URL-адреса, мы создали менеджер лицензий, чтобы возвращать полностью сформированный URL-адрес в информации о лицензии, включая адрес электронной почты пользователя и лицензионный ключ.
  • url : URL-адрес страницы для отображения информации о теме. Этот параметр используется при нажатии для получения дополнительной информации на странице Темы .

Строка 33 : вернуть переходный процесс, чтобы WordPress мог его сохранить, с приложенной к нему информацией об обновлении темы.

Вот и все! Теперь вы создали полную систему обновлений для собственных тем WordPress. Давайте проверим функциональность, а затем перейдем к обновлению плагинов.

Прежде чем перейти к обновлению плагинов, давайте проверим функциональность, чтобы увидеть обновление в действии.

Обновление темы, которую вы используете для тестирования, перезапишет тему, поэтому сначала создайте новый zip-файл, используя его файлы. Затем загрузите zip-файл в Amazon S3, как мы это делали во второй части серии руководств.

Затем войдите на сервер лицензий и отредактируйте информацию о продукте Hello World Theme , убедившись, что он использует ZIP-файл, который вы только что загрузили в Amazon S3, и что номер версии продукта больше, чем у установленной локально темы.

Изменить информацию о применении

Сохраните изменения и вернитесь на тестовый сервер, чтобы протестировать обновление.

На тестовом сервере нажмите « Панель управления»> «Обновления» . WordPress теперь будет выполнять вызовы API, чтобы проверить, есть ли темы, требующие обновления. Если ничего не происходит, возможно, что предыдущая проверка только что прошла, а WordPress все еще ожидает ограничения по времени между проверками версий. В этом случае, подождите минуту и ​​нажмите « Проверить снова», чтобы выполнить проверку.

Теперь вы должны увидеть свою тему в списке тем, которые можно обновить.

Hello World Тема нуждается в обновлении

Далее попробуйте сделать обновление. Если все пойдет хорошо — как и должно быть — вы увидите следующий результат. По умолчанию детали не отображаются, если нет ошибки, поэтому вам нужно нажать « Показать подробности», чтобы увидеть, что произошло в обновлении.

Обратите внимание, что тема загружается не с WordPress.org, а с вашего собственного сервера менеджера лицензий.

Обновление темы

После того, как обновления темы сделаны и протестированы, мы почти закончили с учебником. Большая часть кода, которая понадобится для обновления плагинов, уже готова. Но есть некоторые различия и необходимые дополнения, поэтому давайте приступим к работе и добавим поддержку плагинов в наш клиентский класс менеджера лицензий.

Скопируйте файл class-wp-license-manager-client.php в исходную папку вашего тестового плагина. Затем, в подходящем месте в плагине, поместите следующий код инициализации.

Если вы используете пустой тестовый плагин, просто поместите его в основной файл плагина, прямо под заголовком плагина. Не забудьте заменить <URL_TO_LICENSE_SERVER> на URL своего собственного сервера.

01
02
03
04
05
06
07
08
09
10
11
12
require_once( ‘class-wp-license-manager-client.php’ );
 
if ( is_admin() ) {
    $license_manager = new Wp_License_Manager_Client(
        ‘hello-world-plugin’,
        ‘Hello World Plugin’,
        ‘hello-world-plugin-text’,
        ‘http://<URL_TO_LICENSE_SERVER>/api/license-manager/v1’,
        ‘plugin’,
        __FILE__
    );
}

Большинство параметров такие же, как и при инициализации класса для использования с темой — с другими, специфичными для продукта значениями (например, hello-world-plugin вместо hello-world-theme ).

Последние два параметра не использовались при создании темы, поэтому давайте посмотрим на них:

  • $type (со значением plugin ) указывает экземпляру Wp_License_Manager_Client использовать специальные функции плагина.
  • $plugin_file (последний параметр) используется для получения идентификатора плагина для запроса данных плагина, таких как его текущая версия. Когда эта инициализация будет выполнена в основном классе плагина, мы можем использовать __FILE__ .

Ранее в этом уроке мы подключили наш класс Wp_License_Manager_Client для поиска изменений в переходном процессе update_themes , поместив строку add_filter в конце конструктора. Обновления плагинов будут обрабатываться аналогично, только с использованием переходных update_plugins вместо update_themes .

Итак, сразу после существующего фильтра добавьте специальный код плагина (строки 1-3 уже присутствовали в конструкторе, поэтому я включаю их здесь, чтобы показать, куда должен идти новый код):

1
2
3
4
5
6
7
if ( $type == ‘theme’ ) {
    // Check for updates (for themes)
    add_filter( ‘pre_set_site_transient_update_themes’, array( $this, ‘check_for_update’ ) );
} elseif ( $type == ‘plugin’ ) {
    // Check for updates (for plugins)
    add_filter( ‘pre_set_site_transient_update_plugins’, array( $this, ‘check_for_update’ ) );
}

Как вы заметили из кода выше, новый фильтр использует ту же функцию, что мы добавили для тем, check_for_update .

Ранее, когда мы создавали функцию check_for_update , мы оставляли еще одну ветвь пустой, ожидая, когда ее заполнит код для обработки обновлений плагина. Теперь давайте добавим контент в этот раздел. Вот вся функция с добавленным кодом, связанным с плагином, начиная со строки 27:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
 * The filter that checks if there are updates to the theme or plugin
 * using the License Manager API.
 *
 * @param $transient mixed The transient used for WordPress theme updates.
 * @return mixed The transient with our (possible) additions.
 */
public function check_for_update( $transient ) {
    if ( empty( $transient->checked ) ) {
        return $transient;
    }
 
    if ( $this->is_update_available() ) {
        $info = $this->get_license_info();
 
        if ( $this->is_theme() ) {
            // Theme update
            $theme_data = wp_get_theme();
            $theme_slug = $theme_data->get_template();
 
            $transient->response[$theme_slug] = array(
                'new_version' => $info->version,
                'package' => $info->package_url,
                'url' => $info->description_url
            );
        } else {
            // Plugin update
            $plugin_slug = plugin_basename( $this->plugin_file );
 
            $transient->response[$plugin_slug] = (object) array(
                'new_version' => $info->version,
                'package' => $info->package_url,
                'slug' => $plugin_slug
            );
        }
    }
 
    return $transient;
}

Глядя на код, приятно заметить, что хотя функция та же самая, теперь мы обрабатываем другой переходный процесс, update_pluginsа не update_themes. Один из способов это показывает, что хотя данные в update_themesхранятся в виде массива, update_pluginsиспользуется объект ( строка 30 )!

Вызовы к нашему API диспетчера лицензий такие же, как и при использовании темы, но данные, хранящиеся в переходном процессе, немного отличаются — в зависимости от того, что WordPress ожидает найти в переходном процессе.

Имея этот код, вы уже можете тестировать обновления так же, как мы делали с темой (создайте zip-файл, загрузите его на S3, отредактируйте свойства продукта). Убедитесь, что номер версии плагина на сервере лицензий больше установленного, и перейдите на страницу обновлений тестового сервера . Обновление для Hello World Plugin должно появиться:

появляется обновление для Hello World Plugin

Когда вы обновите плагин, вы увидите, что он загружается с вашего собственного сервера, как и тема.

Но мы еще не закончили: если вы посмотрите на информацию о плагине на снимке экрана выше, вы заметите, что «Совместимость с WordPress 4.0.1» говорит «Неизвестно». Кроме того, если вы нажмете на Просмотр версии 0.2 , появится сообщение об ошибке.

Давайте это исправим.

Для сбора информации о подключаемых модулях WordPress использует функцию под названием plugins_api. По умолчанию функция вызывает API-интерфейс WordPress.org , как и проверки версий. Однако, в отличие от проверки версии, эта функция содержит три мощных фильтра: plugins_api_argsдля редактирования параметров, отправляемых в API, plugins_apiдля переопределения запросов API по умолчанию и plugins_api_resultдля редактирования результатов, полученных из API.

Мы будем использовать, plugins_apiпоскольку это дает нам максимальный уровень контроля над функциональностью: когда WordPress инициирует вызов API плагинов для получения информации о текущем плагине, наша функция подключается и обрабатывает запрос, используя сервер лицензий вместо WordPress.org. Остальные запросы будут оставлены для обработки в WordPress.

Сначала добавьте фильтр в Wp_License_Manager_Clientспецифическую ветку else плагина конструктора:

1
2
// Showing plugin information
add_filter( 'plugins_api', array( $this, 'plugins_api_handler' ), 10, 3 );

Затем добавьте функцию:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
 * A function for the WordPress "plugins_api" filter. Checks if
 * the user is requesting information about the current plugin and returns
 * its details if needed.
 *
 * This function is called before the Plugins API checks
 * for plugin information on WordPress.org.
 *
 * @param $res bool|object The result object, or false (= default value).
 * @param $action string The Plugins API action. We're interested in 'plugin_information'.
 * @param $args array The Plugins API parameters.
 *
 * @return object The API response.
 */
public function plugins_api_handler( $res, $action, $args ) {
    if ( $action == 'plugin_information' ) {
 
        // If the request is for this plugin, respond to it
        if ( isset( $args->slug ) && $args->slug == plugin_basename( $this->plugin_file ) ) {
            $info = $this->get_license_info();
 
            $res = (object) array(
                'name' => isset( $info->name ) ? $info->name : '',
                'version' => $info->version,
                'slug' => $args->slug,
                'download_link' => $info->package_url,
 
                'tested' => isset( $info->tested ) ? $info->tested : '',
                'requires' => isset( $info->requires ) ? $info->requires : '',
                'last_updated' => isset( $info->last_updated ) ? $info->last_updated : '',
                'homepage' => isset( $info->description_url ) ? $info->description_url : '',
 
                'sections' => array(
                    'description' => $info->description,
                ),
 
                'banners' => array(
                    'low' => isset( $info->banner_low ) ? $info->banner_low : '',
                    'high' => isset( $info->banner_high ) ? $info->banner_high : ''
                ),
 
                'external' => true
            );
 
            // Add change log tab if the server sent it
            if ( isset( $info->changelog ) ) {
                $res['sections']['changelog'] = $info->changelog;
            }
 
            return $res;
        }
    }
 
    // Not our request, let WordPress handle this.
    return false;
}

Строка 16 : проверьте запрашиваемое действие API плагина. В настоящее время мы заинтересованы только в этом plugin_information, поэтому если WordPress запрашивает что-то еще, мы просто возвращаемся falseи пропускаем запрос вплоть до WordPress.org.

Строка 19: проверьте, относится ли запрос к текущему плагину. Если да, мы возьмем запрос и обработаем его. Если нет, вернитесь, falseчтобы мы не нарушали запросы API плагинов для других плагинов.

Строка 20 : Позвоните на наш сервер лицензий, чтобы получить информацию о текущем продукте.

Строки 22–48 : собрать все данные плагина, которые мы получили из информационного запроса. Теперь мы наконец-то используем все поля, возвращаемые API.

Строка 50 : вернуть объект результата.

Чтобы проверить эту функцию, отредактируйте продукт Hello World Plugin на своем сервере лицензий и добавьте значения во все поля настроек продукта. В дополнение к настройкам, определенным ранее, введите следующее, установив значения на что угодно — мы просто тестируем …

  • Протестировано с версией WordPress : самая высокая версия WordPress, на которой вы тестировали плагин.
  • Требуется версия WordPress : минимальная версия WordPress, необходимая для запуска вашего плагина.
  • Последнее обновление : дата последнего обновления этого плагина в YYYY-MM-DDформате.
  • Низкий баннер и Высокий баннер : эти поля определяют обычную (низкую) и сетчатую (высокую) версии баннера, отображаемого в верхней части информационного экрана плагина. Вставьте URL-адреса к изображениям следующих размеров: 772×250 для низкого и 1544×500 для высокого.

Сохраните продукт и вернитесь на страницу обновлений тестового сайта . Теперь вы увидите, что поле совместимости с версией WordPress больше не является неизвестным, и когда вы нажмете на Просмотр сведений о версии 0.2, вы увидите это всплывающее окно:

Hello World Plugin всплывающее окно

Сейчас мы создали полнофункциональный, управляемый лицензией плагин WordPress и систему обновления тем. Это все еще довольно простой, но уже может использоваться для передачи обновлений внутренним пользователям вашей организации или вашим клиентам премиум-плагина и темы.

Надеюсь, вы также узнали что-то новое о работе с плагинами и темами WordPress и можете использовать полученные знания при создании собственных проектов.