Статьи

Руководство по API WordPress HTTP: автоматические обновления плагинов

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


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

Чтобы установить новую версию, просто нажмите кнопку «Обновить автоматически». WordPress автоматически загрузит новый пакет, распакует его и заменит старые файлы. Нет FTP, удаление старых файлов и загрузка не требуется.

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

Автообновления как волшебство; Вы на расстоянии одного клика от получения последней версии. Хорошей новостью является то, что сам процесс не сложен или, по крайней мере, не понимает его и не настраивает его для собственного использования. Может быть много причин, почему вы хотите запустить свой собственный сервер для распространения обновлений для ваших плагинов; Единственный, о котором я думаю, это премиум плагины. Если ваш плагин бесплатный и открыт для публики, вы должны опубликовать его в репозитории плагинов WordPress.

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

1
2
3
4
5
add_filter (‘pre_set_site_transient_update_plugins’, ‘display_transient_update_plugins’);
function display_transient_update_plugins ($transient)
{
    var_dump($transient);
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
object(stdClass)[18]
  public ‘last_checked’ => int 1333808132
  public ‘checked’ =>
    array
      ‘access/access.php’ => string ‘1.0’ (length=3)
      ‘adpress/wp-adpress.php’ => string ‘3.1’ (length=3)
          …
      ‘wp-paypal/plug.php’ => string ‘1.0’ (length=3)
  public ‘response’ =>
    array
      ‘akismet/akismet.php’ =>
        object(stdClass)[13]
          public ‘id’ => string ’15’ (length=2)
          public ‘slug’ => string ‘akismet’ (length=7)
          public ‘new_version’ => string ‘2.5.5’ (length=5)
          public ‘url’ => string ‘http://wordpress.org/extend/plugins/akismet/’ (length=44)
          public ‘package’ => string ‘http://downloads.wordpress.org/plugin/akismet.2.5.5.zip’ (length=55)
      ‘update.tmp/plugin.php’ =>
        object(stdClass)[12]
          public ‘slug’ => string ‘plugin’ (length=6)
          public ‘new_version’ => string ‘1.1’ (length=3)
          public ‘url’ => string ‘http://localhost/update.php’ (length=27)
          public ‘package’ => string ‘http://localhost/update.php’ (length=27)

Вывод, конечно, будет отличаться от одного блога к другому, но содержащаяся информация по сути одинакова:

  • last_checked (int) — последний раз, когда запускалась проверка автообновлений (в секундах)
  • checked (массив) — список проверенных плагинов и их версия
  • response (массив) — возвращенный ответ от сервера API

Если вы похожи на меня, вам, вероятно, не понравится 12 часов ждать, пока WordPress проверит наличие новых обновлений, когда вы будете заниматься соответствующей отладкой и разработкой. Для этого вы можете просто нажать кнопку «Проверить снова» на странице обновлений. 12-часовой период ожидания определяется в ядре WordPress в строке 147 файла wp-includes / update.php , это переменная $timeout .

Поскольку ваш плагин не размещен в репозитории плагинов WordPress, от традиционного API не будет ответа. Решением является создание собственного API, проверка обновлений; и когда доступно новое обновление, добавьте ответ к переходному update_plugins « update_plugins ». WordPress загрузит новую версию из « download_url », которое вы указали в ответе.

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

Чтобы лучше понять, как работает фильтр, выберите любой плагин из «проверенного» списка, который отображается в вашем блоге. Например, плагин access / access.php и поместите следующий код в новый или уже работающий плагин WordPress.

01
02
03
04
05
06
07
08
09
10
11
add_filter (‘pre_set_site_transient_update_plugins’, ‘display_transient_update_plugins’);
function display_transient_update_plugins ($transient)
{
    $obj = new stdClass();
    $obj->slug = ‘access.php’;
    $obj->new_version = ‘2.0’;
    $obj->url = ‘http://anyurl.com’;
    $obj->package = ‘http://anyurl.com’;
    $transient[plugin_directory/plugin_file.php] => $obj;
    return $transient;
}

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

Таким образом, наше решение должно сделать следующее:

  1. Проверьте наличие новых обновлений на вашем сервере.
  2. Когда новое обновление существует, добавьте его к свойству ответа переходного процесса « update_plugins ».
  3. plugins_api « plugins_api » аналогичным образом, чтобы предоставить информацию о новой версии.
  4. Сервер API, который предоставляет версию, подробности и пакет последней версии.

Чтобы стандартизировать и оптимизировать вещи, я создал класс PHP, который может работать с любым плагином. Он довольно прост в использовании, вам просто нужно указать 3 параметра: текущую версию плагина, путь обновления и слаг плагина. Позже я подробно объясню, что каждый из них делает.

Наш класс сможет выполнить 3 HTTP-запроса, 2 обязательных и третий необязательный:

  1. Первый запрос проверит наличие последней версии на удаленном сервере. Для упрощения и ускорения сервер API будет возвращать только последнюю версию в виде строки (например, «1.0» или «1.1»).
  2. Второй запрос вернет информацию о последней версии в виде сериализованного объекта PHP.

В классе есть 3 открытых функции, которые вы можете использовать в своем плагине:

  • getRemote_version — возвращает последнюю удаленную версию в виде строки.
  • getRemote_description — возвращает информацию о последней удаленной версии в виде объекта PHP.
  • getRemote_license — возвращает статус лицензии плагина. Это необязательно, и в этом руководстве нет реализации лицензирования.

Класс может быть инициирован в действии » init «. Имеет 3 параметра:

  1. Текущая версия — строка с текущей установленной версией плагина.
  2. Путь обновления — путь к серверу обновлений. В нашем случае это файл PHP в моей корневой директории localhost.
  3. Плагин плагин — это необходимо, чтобы получить плагин плагин и имя. Для этого класс должен быть инициирован в том же файле, который имеет заголовок плагина WordPress.
1
2
3
4
5
6
7
8
9
add_action(‘init’, ‘wptuts_activate_au’);
function wptuts_activate_au()
{
    require_once (‘wp_autoupdate.php’);
    $wptuts_plugin_current_version = ‘1.0’;
    $wptuts_plugin_remote_path = ‘http://localhost/update.php’;
    $wptuts_plugin_slug = plugin_basename(__FILE__);
    new wp_auto_update ($wptuts_plugin_current_version, $wptuts_plugin_remote_path, $wptuts_plugin_slug);
}

Это все! Теперь ваш плагин готов к получению обновлений по указанному удаленному пути.


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

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
class wp_auto_update
{
    /**
     * The plugin current version
     * @var string
     */
    public $current_version;
 
    /**
     * The plugin remote update path
     * @var string
     */
    public $update_path;
 
    /**
     * Plugin Slug (plugin_directory/plugin_file.php)
     * @var string
     */
    public $plugin_slug;
 
    /**
     * Plugin name (plugin_file)
     * @var string
     */
    public $slug;
 
    /**
     * Initialize a new instance of the WordPress Auto-Update class
     * @param string $current_version
     * @param string $update_path
     * @param string $plugin_slug
     */
    function __construct($current_version, $update_path, $plugin_slug)
    {
    }
 
    /**
     * Add our self-hosted autoupdate plugin to the filter transient
     *
     * @param $transient
     * @return object $ transient
     */
    public function check_update($transient)
    {
    }
 
    /**
     * Add our self-hosted description to the filter
     *
     * @param boolean $false
     * @param array $action
     * @param object $arg
     * @return bool|object
     */
    public function check_info($false, $action, $arg)
    {
    }
 
    /**
     * Return the remote version
     * @return string $remote_version
     */
    public function getRemote_version()
    {
    }
 
    /**
     * Get information about the remote version
     * @return bool|object
     */
    public function getRemote_information()
    {
    }
 
    /**
     * Return the status of the plugin licensing
     * @return boolean $remote_license
     */
    public function getRemote_license()
    {
    }
}

Конструктор инициирует новый экземпляр нашего класса. Он принимает три параметра, которые мы уже определили в предыдущем разделе. Конструктор устанавливает открытые свойства « current_version », « update_path », « plugin_slug » и « slug » (которые он извлекает из « plugin_slug »). Он также plugins_api фильтрам » pre_set_site_transient_update_plugins » и » plugins_api «.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
/**
 * Initialize a new instance of the WordPress Auto-Update class
 * @param string $current_version
 * @param string $update_path
 * @param string $plugin_slug
 */
function __construct($current_version, $update_path, $plugin_slug)
{
    // Set the class public variables
    $this->current_version = $current_version;
    $this->update_path = $update_path;
    $this->plugin_slug = $plugin_slug;
    list ($t1, $t2) = explode(‘/’, $plugin_slug);
    $this->slug = str_replace(‘.php’, », $t2);
 
    // define the alternative API for updating checking
    add_filter(‘pre_set_site_transient_update_plugins’, array(&$this, ‘check_update’));
 
    // Define the alternative response for information checking
    add_filter(‘plugins_api’, array(&$this, ‘check_info’), 10, 3);
}

Есть 3 функции, которые делают удаленные запросы в наш собственный репозиторий. Это открытые функции, которые можно использовать независимо, и каждая из них либо возвращает значение ответа, либо false.

getRemote_version — возвращает последнюю версию плагина из нашего собственного репозитория в виде строки.

01
02
03
04
05
06
07
08
09
10
11
12
/**
 * Return the remote version
 * @return bool|string $remote_version
 */
public function getRemote_version()
{
    $request = wp_remote_post($this->update_path, array(‘body’ => array(‘action’ => ‘version’)));
    if (!is_wp_error($request) || wp_remote_retrieve_response_code($request) === 200) {
        return $request[‘body’];
    }
    return false;
}

getRemote_information — возвращает информацию о последней версии плагина из нашего собственного репозитория в виде объекта PHP.

01
02
03
04
05
06
07
08
09
10
11
12
/**
 * Get information about the remote version
 * @return bool|object
 */
public function getRemote_information()
{
    $request = wp_remote_post($this->update_path, array(‘body’ => array(‘action’ => ‘info’)));
    if (!is_wp_error($request) || wp_remote_retrieve_response_code($request) === 200) {
        return unserialize($request[‘body’]);
    }
    return false;
}

getRemote_license — возвращает статус лицензирования. Эта функция является необязательной, и в этом руководстве нет реализации лицензирования.

01
02
03
04
05
06
07
08
09
10
11
12
/**
 * Return the status of the plugin licensing
 * @return bool|string $remote_license
 */
public function getRemote_license()
{
    $request = wp_remote_post($this->update_path, array(‘body’ => array(‘action’ => ‘license’)));
    if (!is_wp_error($request) || wp_remote_retrieve_response_code($request) === 200) {
        return $request[‘body’];
    }
    return false;
}

Класс plugins_api фильтрам « pre_set_site_transient_update_plugins » и « plugins_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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
 * Add our self-hosted autoupdate plugin to the filter transient
 *
 * @param $transient
 * @return object $ transient
 */
public function check_update($transient)
{
    if (empty($transient->checked)) {
        return $transient;
    }
 
    // Get the remote version
    $remote_version = $this->getRemote_version();
 
    // If a newer version is available, add the update
    if (version_compare($this->current_version, $remote_version, ‘<‘)) {
        $obj = new stdClass();
        $obj->slug = $this->slug;
        $obj->new_version = $remote_version;
        $obj->url = $this->update_path;
        $obj->package = $this->update_path;
        $transient->response[$this->plugin_slug] = $obj;
    }
    return $transient;
}
 
/**
 * Add our self-hosted description to the filter
 *
 * @param boolean $false
 * @param array $action
 * @param object $arg
 * @return bool|object
 */
public function check_info($false, $action, $arg)
{
    if ($arg->slug === $this->slug) {
        $information = $this->getRemote_information();
        return $information;
    }
    return false;
}
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
class wp_auto_update
{
    /**
     * The plugin current version
     * @var string
     */
    public $current_version;
 
    /**
     * The plugin remote update path
     * @var string
     */
    public $update_path;
 
    /**
     * Plugin Slug (plugin_directory/plugin_file.php)
     * @var string
     */
    public $plugin_slug;
 
    /**
     * Plugin name (plugin_file)
     * @var string
     */
    public $slug;
 
    /**
     * Initialize a new instance of the WordPress Auto-Update class
     * @param string $current_version
     * @param string $update_path
     * @param string $plugin_slug
     */
    function __construct($current_version, $update_path, $plugin_slug)
    {
        // Set the class public variables
        $this->current_version = $current_version;
        $this->update_path = $update_path;
        $this->plugin_slug = $plugin_slug;
        list ($t1, $t2) = explode(‘/’, $plugin_slug);
        $this->slug = str_replace(‘.php’, », $t2);
 
        // define the alternative API for updating checking
        add_filter(‘pre_set_site_transient_update_plugins’, array(&$this, ‘check_update’));
 
        // Define the alternative response for information checking
        add_filter(‘plugins_api’, array(&$this, ‘check_info’), 10, 3);
    }
 
    /**
     * Add our self-hosted autoupdate plugin to the filter transient
     *
     * @param $transient
     * @return object $ transient
     */
    public function check_update($transient)
    {
        if (empty($transient->checked)) {
            return $transient;
        }
 
        // Get the remote version
        $remote_version = $this->getRemote_version();
 
        // If a newer version is available, add the update
        if (version_compare($this->current_version, $remote_version, ‘<‘)) {
            $obj = new stdClass();
            $obj->slug = $this->slug;
            $obj->new_version = $remote_version;
            $obj->url = $this->update_path;
            $obj->package = $this->update_path;
            $transient->response[$this->plugin_slug] = $obj;
        }
        var_dump($transient);
        return $transient;
    }
 
    /**
     * Add our self-hosted description to the filter
     *
     * @param boolean $false
     * @param array $action
     * @param object $arg
     * @return bool|object
     */
    public function check_info($false, $action, $arg)
    {
        if ($arg->slug === $this->slug) {
            $information = $this->getRemote_information();
            return $information;
        }
        return false;
    }
 
    /**
     * Return the remote version
     * @return string $remote_version
     */
    public function getRemote_version()
    {
        $request = wp_remote_post($this->update_path, array(‘body’ => array(‘action’ => ‘version’)));
        if (!is_wp_error($request) || wp_remote_retrieve_response_code($request) === 200) {
            return $request[‘body’];
        }
        return false;
    }
 
    /**
     * Get information about the remote version
     * @return bool|object
     */
    public function getRemote_information()
    {
        $request = wp_remote_post($this->update_path, array(‘body’ => array(‘action’ => ‘info’)));
        if (!is_wp_error($request) || wp_remote_retrieve_response_code($request) === 200) {
            return unserialize($request[‘body’]);
        }
        return false;
    }
 
    /**
     * Return the status of the plugin licensing
     * @return boolean $remote_license
     */
    public function getRemote_license()
    {
        $request = wp_remote_post($this->update_path, array(‘body’ => array(‘action’ => ‘license’)));
        if (!is_wp_error($request) || wp_remote_retrieve_response_code($request) === 200) {
            return $request[‘body’];
        }
        return false;
    }
}

Теперь, когда мы подготовили наш плагин для самостоятельного автоматического обновления, пришло время настроить сервер, который будет обслуживать уведомление, описание и пакет обновлений. В нашем случае мы используем PHP с единственным файлом с именем « update.php » в корневом каталоге моего локального сервера. Обновление плагина » update.zip » в том же каталоге.

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

  1. Версия плагина
    • Тип запроса : POST
    • Параметр / значение : «действие» / «версия»
    • Возвращаемое значение : String («1.0», «1.5», «2.0» …)
  2. Информация о плагине
    • Тип запроса : POST
    • Параметр / значение : «действие» / «информация»
    • Возвращаемое значение : строка. Сериализованный PHP возражал
  3. Лицензия на плагин
    • Тип запроса : POST
    • Параметр / значение : «действие» / «лицензия»
    • Возвращаемое значение : не стесняйтесь реализовать его 🙂
  4. Пакет плагинов
    • Тип запроса : GET
    • Параметр / значение : нет
    • Возвращаемое значение : Zip Package
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
if (isset($_POST[‘action’])) {
  switch ($_POST[‘action’]) {
    case ‘version’:
      echo ‘1.1’;
      break;
    case ‘info’:
      $obj = new stdClass();
      $obj->slug = ‘plugin.php’;
      $obj->plugin_name = ‘plugin.php’;
      $obj->new_version = ‘1.1’;
      $obj->requires = ‘3.0’;
      $obj->tested = ‘3.3.1’;
      $obj->downloaded = 12540;
      $obj->last_updated = ‘2012-01-12’;
      $obj->sections = array(
        ‘description’ => ‘The new version of the Auto-Update plugin’,
        ‘another_section’ => ‘This is another section’,
        ‘changelog’ => ‘Some new features’
      );
      $obj->download_link = ‘http://localhost/update.php’;
      echo serialize($obj);
    case ‘license’:
      echo ‘false’;
      break;
  }
} else {
    header(‘Cache-Control: public’);
    header(‘Content-Description: File Transfer’);
    header(‘Content-Type: application/zip’);
    readfile(‘update.zip’);
}

Можно настроить окно информации о плагине. Коробка имеет:

  1. Информация о плагине (описание, последнее обновление, количество скачиваний)
  2. Разделы, которые будут добавлять вкладки в информационном окне

Разделы могут быть добавлены в свойство массива разделов. Вкладка описания сама является разделом.

1
2
3
4
5
$obj->sections = array(
  ‘description’ => ‘The new version of the Auto-Update plugin’,
  ‘another_section’ => ‘This is another section’,
  ‘changelog’ => ‘Some new features’
);

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