Развертывание WordPress-плагинов через Github с помощью Transients

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

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

План

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

  • Сначала мы получим некоторую информацию о transients, а также о том, как они работают в WordPress
  • Затем мы создадим PHP класс, который будет содержать весь наш код
  • Далее мы свяжем наш плагин с Github
  • Наконец, мы создадим элементы интерфейса, которые позволят пользователям взаимодействовать с нашим плагином

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

Transients в WordPress

Для начала давайте посмотрим, что такое transients в WordPress. Transients в WordPress – это объемы данных, которые хранятся определенное время (обычно недолгое), после чего автоматически удаляются. Их можно представить себе в виде серверных cookies. WordPress использует transients для хранения информации обо всех плагинах, которые установлены в данный момент, включая их номер версии. Время от времени WordPress обновляет данные, хранящиеся в transient. Именно это событие используется в WordPress для проверки репозитория Subversion на наличие обновленных плагинов. Это событие мы будем использовать для того, чтобы проверить обновления нашего собственного плагина на GitHub.

Первые шаги

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

class Smashing_Updater {
  protected $file;
  
  public function __construct( $file ) {
    $this->file = $file;
    return $this;
  }
}

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

Вы могли бы задаться вопросом, зачем нам передавать путь плагина в наш класс. В WordPress информация о плагине хранится в виде пути к главному файлу плагина – этот путь является уникальным идентификатором (т.е. базовым именем). Допустим, ваш плагин находится в каталоге /wp-content/plugins/smashing-plugin/smashing-plugin.php. Базовое имя нашего плагина — smashing-plugin/smashing-plugin.php. Мы будем использовать это базовое имя также для проверки, активирован ли плагин, который мы обновляем.

Далее мы должны получить данные плагина и присвоить их к свойству в нашем классе:

class Smashing_Updater {
  protected $file;
  protected $plugin;
  protected $basename;
  protected $active;
  
  public function __construct( $file ) {
    $this->file = $file;
    add_action( 'admin_init', array( $this, 'set_plugin_properties' ) );
    return $this;
  }
  
  public function set_plugin_properties() {
    $this->plugin   = get_plugin_data( $this->file );
    $this->basename = plugin_basename( $this->file );
    $this->active   = is_plugin_active( $this->basename );
  }
}

Вы, возможно, заметили, что я использую действие admin_init для задания свойств плагина. Сделано это по той причине, что функция get_plugin_data()может быть не определена в том месте, где мы вызываем код. Подцепив ее к admin_init, мы гарантируем, что эта функция будет доступна для получения данных нашего плагина. Также мы проверяем, активирован ли плагин, и привязываем эти данные наряду с объектом плагина к свойствам в нашем классе.

Чтобы узнать больше о действиях и фильтрах WordPress, вы должны изучить Plugin API.

Теперь, когда мы задали наш класс и получили некоторые базовые данные плагина, пришла пора поговорить о GitHub.

Настраиваем GitHub

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

01-github_root

В моем репозитории имеется два файла. Главный файл плагина smashing-plugin.php и скрипт апдейтера updater.php. Ваш репозиторий может иметь несколько иной вид в зависимости от файлов плагина.

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

Перейдите к вкладке Releases и щелкните по «Create a new release»

02-releases-opt

Здесь вы найдете несколько полей, которые надо заполнить. Самое важное поле — Tag version. Здесь мы поместим текущую версию релиза нашего плагина. К настоящему времени вы, вероятно, уже знакомы с семантическим форматом управления версиями. Этот формат мы будем использовать для нашего поля. Мы должны использовать этот формат также для нашего главного файла плагина в PHP-комментариях. Продолжайте заполнять поля заголовка и описания релиза, пока ваш релиз не будет выглядеть следующим образом:

03-new_release

Щелкаем по кнопке Publish release, чтобы создать релиз. В вашем репозитории появится ZIP-файл, который вы можете использовать в своем скрипте апдейтера. Затем вы просто повторяете этот процесс всякий раз, когда вы хотите развернуть новый релиз плагина. Мы можем теперь использовать GitHub API для проверки номера версии, и у нас есть готовый ZIP-архив.

Но у меня приватный плагин!

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

04-settings

Затем перейдите к Applications в левом меню и выберите «Generate a new access token» в мета-поле Personal access tokens. Вы будете перенесены к следующей странице:

05-token

Вы можете дать токену любое описание, после чего выберите репозиторий repo и нажмите Generate token. После того как вы сгенерируете ваш токен, сохраните его где-либо – он нам еще понадобится.

Соединяемся с Github

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

private $username;
private $repository;
private $authorize_token;
private $github_response;

Нам нужно будет добавить несколько сеттеров для наших новых свойств:

public function set_username( $username ) {
  $this->username = $username;
}
public function set_repository( $repository ) {
  $this->repository = $repository;
}
public function authorize( $token ) {
  $this->authorize_token = $token;
}

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

// Include our updater file
include_once( plugin_dir_path( __FILE__ ) . 'update.php');

$updater = new Smashing_Updater( __FILE__ ); // instantiate our class
$updater->set_username( 'rayman813' ); // set username
$updater->set_repository( 'smashing-plugin' ); // set repo

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

private function get_repository_info() {
  if ( is_null( $this->github_response ) ) { // Do we have a response?
    $request_uri = sprintf( 'https://api.github.com/repos/%s/%s/releases', $this->username, $this->repository ); // Build URI
    if( $this->authorize_token ) { // Is there an access token?
        $request_uri = add_query_arg( 'access_token', $this->authorize_token, $request_uri ); // Append it
    }        
    $response = json_decode( wp_remote_retrieve_body( wp_remote_get( $request_uri ) ), true ); // Get JSON and parse it
    if( is_array( $response ) ) { // If it is an array
        $response = current( $response ); // Get the first item
    }
    if( $this->authorize_token ) { // Is there an access token?
        $response['zipball_url'] = add_query_arg( 'access_token', $this->authorize_token, $response['zipball_url'] ); // Update our zip url with token
    }
    $this->github_response = $response; // Set it to our property  
  }
}

В данном методе мы проверяем, получили ли мы уже ответ или нет; если нет, то в таком случае мы совершаем запрос к конечной точке GitHub API, используя юзернейм и репозиторий. Мы также добавили код для проверки наличия токена к приватным репозиториям. Если токен имеется, мы добавляем его к URL и обновляем zipball URL (файл, который мы будем загружать при обновлении). Затем мы получаем ответ JSON, анализируем его, после чего берем последний релиз. Как только у нас будет последний релиз, мы вносим его в свойство, созданное ранее.

Модификация WordPress

Отлично, до сих пор мы собирали информацию о нашем плагине и нашем репозитории, после чего соединялись с репозиторием через API. Пришло время настроить WordPress, чтобы получить наши новые данные и поместить их в нужное место. Процесс будет состоять из трех шагов:

  • Изменения вывода Update transient в WordPress
  • Обновление интерфейса WordPress для корректного вывода информации о нашем плагине
  • Проверка того, правильно ли работает плагин после обновления.

Подцепляемся к update transient

Есть несколько способов сцепления с данным событием transient. Есть два фильтра и два действия, которые мы можем использовать для инъекции нашего кода. Вот они:

  • Фильтр: pre_set_transient_update_plugins
  • Фильтр: pre_set_site_transient_update_plugins
  • Действие: set_transient_update_plugins
  • Действие: set_site_transient_update_plugins

Вы можете видеть, что теги являются очень похожими. Разница в том, что добавляется слово «site». Данное различие важно, поскольку оно изменяет масштаб нашего кода. Теги без слова site будут работать только для отдельных сайтов в WordPress; теги со словом site будут работать как для отдельных сайтов, так и для мультисайтов. Вы можете выбирать те или другие варианты в зависимости от вашего плагина.

Я просто хочу изменить стандартный transient, поэтому я буду использовать один из фильтров. Я решил использовать pre_set_site_transient_update_plugins, чтобы наш код работал как для отдельных сайтов, так и для мультисайтов.

Примечание: если вы используете этот скрипт для обновления темы, вы можете также использовать фильтр pre_set_site_transient_update_themes.

Давайте взглянем на нашу функцию initialize:

public function initialize() {
  add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'modify_transient' ), 10, 1 );
  add_filter( 'plugins_api', array( $this, 'plugin_popup' ), 10, 3);
  add_filter( 'upgrader_post_install', array( $this, 'after_install' ), 10, 3 );
}

В этом примере я добавил три фильтра. Каждый соответствует шагам, о которых мы говорили ранее. Первый фильтр изменит transient; второй позволит гарантировать, что наши данные плагина переданы в интерфейс WordPress; третий позволит убедиться в том, что наш плагин активирован после обновления.

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

// Include our updater file
include_once( plugin_dir_path( __FILE__ ) . 'update.php');

$updater = new Smashing_Updater( __FILE__ ); // instantiate our class
$updater->set_username( 'rayman813' ); // set username
$updater->set_repository( 'smashing-plugin' ); // set repo
$updater->initialize(); // initialize the updater

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

Пишем код для наших фильтров

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

public function modify_transient( $transient ) {
  if( $checked = $transient->checked ) { // Did WordPress check for updates?
    $this->get_repository_info(); // Get the repo info
    $out_of_date = version_compare( $this->github_response['tag_name'], $checked[$this->basename] ); // Check if we're out of date
    if( $out_of_date ) {
      $new_files = $this->github_response['zipball_url']; // Get the ZIP
      $plugin = array( // setup our plugin info
        'url' => $this->plugin["PluginURI"],
        'slug' => $this->basename,
        'package' => $new_files,
        'new_version' => $this->github_response['tag_name']
      );
      $transient->response[ $this->basename ] = (object) $plugin; // Return it in response
    }
  }
  return $transient; // Return filtered transient
}

В данном фрагменте мы просто получаем номер версии из комментариев в нашем основном файле плагина и сравниваем его с названием тега, который мы дали нашему релизу на GitHub. Если номер версии на GitHub выше, наш код сообщает WordPress о том, что доступна новая версия плагина. Если вы смените номер версии в вашем основном файле плагина на версию, которая ниже, чем ваш тег GitHub, вы должны получить уведомление об обновлении в админке WordPress:

06-updates_available

Однако если вы перейдете по ссылке View version 1.0.0 details, вы получите ошибку в WordPress. Это вызвано тем, что WordPress пытается найти детали этого плагина в его репозиториях. Вместо них вы должны загрузить свои собственные данные о плагине. С этим справляется следующий метод, plugin_popup:

public function plugin_popup( $result, $action, $args ) {
  if( ! empty( $args->slug ) ) { // If there is a slug
    if( $args->slug == $this->basename ) { // And it's our slug
      $this->get_repository_info(); // Get our repo info
      // Set it to an array
      $plugin = array(
        'name'              => $this->plugin["Name"],
        'slug'              => $this->basename,
        'version'           => $this->github_response['tag_name'],
        'author'            => $this->plugin["AuthorName"],
        'author_profile'    => $this->plugin["AuthorURI"],
        'last_updated'      => $this->github_response['published_at'],
        'homepage'          => $this->plugin["PluginURI"],
        'short_description' => $this->plugin["Description"],
        'sections'          => array( 
            'Description'   => $this->plugin["Description"],
            'Updates'       => $this->github_response['body'],
        ),
        'download_link'     => $this->github_response['zipball_url']
      );
      return (object) $plugin; // Return the data
    }
  }   
  return $result; // Otherwise return default
}

Данный фрагмент достаточно простой. Мы проверяем, требуются ли WordPress данные о нашем плагине, и если да, то в таком случае возвращаем свой собственный массив данных. Мы получаем данные в виде комбинации ответа GitHub и наших комментариев в плагине. Вы также можете отметить для себя ключ sections в массиве. Каждый пункт в данном массиве выводится в виде вкладки в панели метаданных плагина. Вы можете легко поместить все что угодно в эти секции, включая HTML. В данный момент мы просто выводим описание плагина и информацию о версии.

07-update_popup

Давайте рассмотрим наш заключительный метод, after_install:

public function after_install( $response, $hook_extra, $result ) {
  global $wp_filesystem; // Get global FS object

  $install_directory = plugin_dir_path( $this->file ); // Our plugin directory 
  $wp_filesystem->move( $result['destination'], $install_directory ); // Move files to the plugin dir
  $result['destination'] = $install_directory; // Set the destination for the rest of the stack
   
  if ( $this->active ) { // If it was active
    activate_plugin( $this->basename ); // Reactivate
  }
  return $result;
}

Фрагмент делает две вещи. Во-первых, в нем мы устанавливаем аргумент под названием destination, который является директорией, куда WordPress будет устанавливать наш код. Мы передаем в данный аргумент директорию с основным файлом плагина, поскольку в этом аргументе всегда должна быть корневая папка плагина. Во-вторых, мы проверяем, был ли плагин активирован, используя свойство, которое мы установили ранее. Если плагин был активен перед обновлением, наш метод повторно активирует его.

Заключение

Поздравляем вас! Теперь вы можете обновить свой плагин, просто щелкнув по кнопке «Update now» на странице плагинов. Вы должны помнить о том, что версия вашего плагина в основном файле должна соответствовать текущему релизу вашего плагина – они должны быть одинаковыми. Если вы не обновите версию в комментариях, вы можете столкнуться с бесконечным циклом «Имеются доступные обновления».

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

Источник: smashingmagazine.com

Блог про WordPress
Комментарии: 10
  1. board

    Очень полезно, обязательно применю в использование

  2. Андрей

    Без указания логина на GIT никак? Может его как-то где-то спрятать, зашифровать?..

    1. Дмитрий (автор)

      Этого подсказать не могу, к сожалению.

  3. Андрей

    Подскажите, а какие есть особенности, при интеграции этого кода не в плагин, а в тему?

    1. Дмитрий (автор)

      В тему не рекомендуется включать подобный функционал. Это уже «территория» плагинов.

  4. Андрей

    Всё-таки попробовал реализовать обновление темы с реп гитхаба, вп видит обновление, но сама тема не обновляется, висит бесконечно. Можно как-то проконсультироваться с Вами по коду? Кроме Вашей статьи инфы больше не нашел, а реализовать это очень важно.

    1. Дмитрий (автор)

      Посмотрите еще вот такой плагин: https://github.com/UCF/Theme-Updater

      Возможно, как-то поможет вам. Но он уже больше не поддерживается создателями.

      Мы не разработчики, потому детальных консультаций не предоставляем.

  5. Андрей

    Большое спасибо! Изучил код, смог починить свой скрипт. Осталась последняя проблема и последний вопрос. После обновления, название папки с темой меняется (точнее создается новая папка с другим названием, она же становится активной, а из старой всё удаляется). Не подскажете куда смотреть, какой параметр отвечает за имя папки в процессе обновления?

    1. Дмитрий (автор)

      Возможно, что хранится в массиве $plugin.

  6. Alexandr Nikulin

    на основе данной статьи реализовал код и для темы если кому интересно https://github.com/SashokNekulin/WpAutoUpdate или тут https://packagist.org/packages/sashoknekulin/wpautoupdate

Добавить комментарий для Андрей Отменить ответ

Получать новые комментарии по электронной почте.