К основному контенту

Собственный сервис обновлений для WordPress

Уверен, что каждый из разработчиков плагинов и тем для WordPress хоть раз, но задумывался над вопросом монетизации своих проектов. Единственный нюанс, который стоял на пути создания платных версий - это система обновления. У WordPress есть свой сервис - один для всех. Это очень не удобно, если вы пишите платную версию своего продукта, поскольку разделить код не получится со стандартным хранилищем обновлений. Да и выкладывать открытый код с каким-либо флажком вида платная или бесплатная версия - это точно не выход. В итоге все приходят либо к использованию уже готовых но платных сервисов, либо к созданию своей собственной системы обновления. К великому счастью, некоторые умные люди (да прибудет с ними сила) уже создали подобное детище. Я собираюсь поделиться опытом на эту тему.
Итак, у меня есть плагин, который я бы хотел обновлять через свой сервис. Допустим, у нас есть доменное имя вида http://update.mysite.com/api, к которому мы можем обратиться и спросить версию плагина и другие его подробности.

Создание сервиса обновлений

Для того, что бы сервис работал нужно совсем немного и, как я уже говорил, есть умные люди, которые уже сделали подобную систему.
Исходный PHP код API функций можно скачать здесь. Скачиваем, распаковываем на сервер. В папке API есть три файла:

  • download.php
  • index.php
  • packages.php
Нас будет интересовать файл packages.php. В нем хранится описание объектов, которые могут быть обновлены.

// Plugin with update info
$packages['ls_spam'] = array( //Replace plugin with the plugin slug that updates will be checking for
    'versions' => array(
        '0.0.6' => array( //Array name should be set to current version of update
            'version' => '0.0.6', //Current version available
            'date' => '2014-10-11', //Date version was released
            'author' => '<a href="htp://mysite.ru">Aleksey Komov</a>', //Author name - can be linked using html - <a href="http://link-to-site.com">Author Name</a>
            'requires' => '2.8', // WP version required for plugin
            'tested' => '4.0.0', // WP version tested with
            'homepage' => 'http://your_plugin_website', // Site devoted to your plugin if available
            'downloaded' => '1000', // Number of times downloaded
            'external' => '', // Unused
            //plugin.zip is the same as file_name
            'package' => 'http://updates.mysite.com/api/download.php?key=' . md5('ls_spam.zip' . mktime(0,0,0,date("m"),date("d"),date("Y"))),
//            'package' => 'http://updates.mysite.com/api/update/ls_spam.zip',
            //file_name is the name of the file in the update folder.
            'file_name' => 'ls_spam.zip',
            'sections' => array(
                /* Plugin Info sections tabs.  Each key will be used as the title of the tab, value is the contents of tab.
                  Must be lowercase to function properly
                  HTML can be used in all sections below for formating.  Must be properly escaped ie a single quote would have to be \'
                  Screenshot section must use exteranl links for img tags.
                 */
                'description' => 'E-Mail service.', //Description Tab
                'installation' => 'Install Info', //Installaion Tab
                'screenshots' => 'Screen Shots', //Screen Shots
                'changelog' => ''
                . 'Version 0.0.6<br />'
                . '* Price order correction'
                . '* CSS correction'
                . 'Version 0.0.5<br />'
                . '* Added autoupdate check<br />'
                . '', //Change Log Tab
                'faq' => 'FAQ', //FAQ Tab
                'other notes' => 'Other Notes'    //Other Notes Tab
            )
        )
    ),
    'info' => array(
        'url' => 'http://your_plugin_webiste'  // Site devoted to your plugin if available
    )
);

Для сервиса обновления все готово. Главное, не забудьте положить архив с новой версией плагина по указанному пути.

Автопроверка обновлений

Осталась самая малость - заставить WordPress проверять обновления не только на своем сервисе, но и на нашем. Конечно, лезть в движок и править код совсем не нужно. Можно пожертвовать глобализацией и сделать это через хуки, их всего 2:

  • pre_set_site_transient_update_plugins - для проверки обновлений
  • plugins_api - для процесса обновления
Один нюанс - эти фильтры нужно использовать только для администрирования. В моем случае это отдельный класс, так что в конструктор добавляем следующее:

/* Автообновления */
add_filter('pre_set_site_transient_update_plugins', array($this, 'CheckForUpdate'));
add_filter('plugins_api', array($this, 'CallPluginApi'), 10, 3);

Теперь осталась реализация.

// Push in plugin version information to get the update notification
public function CheckForUpdate($transient)
{
 if(empty($transient->checked))
 {
  return $transient;
 }
 
 $args = array(
  'slug' => $this->pluginSlug,
  'version' => $transient->checked[$this->pluginSlug . '/index.php'],
 );
 $request_string = array(
  'body' => array(
   'action' => 'basic_check',
   'request' => serialize($args),
   'api-key' => md5(get_bloginfo('url'))
  ),
  'user-agent' => 'WordPress/' . $this->wpVersion . '; ' . get_bloginfo('url')
 );
 
 // Start checking for an update
 $raw_response = wp_remote_post($this->apiUrl, $request_string);
 
 if(!is_wp_error($raw_response) && ($raw_response['response']['code'] == 200))
 {
  $response = unserialize($raw_response['body']);
 }
 
 if(is_object($response) && !empty($response))
 {
  // Feed the update data into WP updater
  $transient->response[$this->pluginSlug . '/index.php'] = $response;
 }
 
 return $transient;
}
 
// Push in plugin version information to display in the details lightbox
public function CallPluginApi($def, $action, $args)
{
 if(!isset($args->slug) || ($args->slug != $this->pluginSlug))
  return false;
 
 // Get the current version
 $plugin_info = get_site_transient('update_plugins');
 $current_version = $plugin_info->checked[$this->pluginSlug . '/index.php'];
 $args->version = $current_version;
 
 $request_string = array(
  'body' => array(
   'action' => $action,
   'request' => serialize($args),
   'api-key' => md5(get_bloginfo('url'))
  ),
  'blocking' => true,
  'user-agent' => 'WordPress/' . $this->wpVersion . '; ' . get_bloginfo('url')
 );
 
 $request = wp_remote_post($this->apiUrl, $request_string);
 
 if(is_wp_error($request))
 {
  $res = new WP_Error('plugins_api_failed', __('An Unexpected HTTP Error occurred during the API request.</p> <p><a href="?" onclick="document.location.reload(); return false;">Try again</a>', 'lswp'), $request->get_error_message());
 }
 else
 {
  $res = unserialize($request['body']);
 
  if($res === false)
   $res = new WP_Error('plugins_api_failed', __('An unknown error occurred', 'lswp'), $request['body']);
 }
 
 return $res;
}

Со стороны программирования - все. Проверял на реальном проекте - все работает.

Нюансы при обновлении

Главный совет - перед первым авто обновлением сделайте копию плагина на всякий случай. Собственно, что за случай может произойти? Я столкнулся с таким случаем еще на этапе разработки. WordPress скачивал и распаковывал архив с обновлением, потом затирал текущую версию и выдавал ошибку типа "Не удалось удалить предыдущую версию плагина". Парадокс был в том, что он удалял все кроме самой папки. Причина крылась в правах доступа к папке - у WordPress должен быть полный доступ к этой папке. Когда все перенес на внешний сервер такой проблемы не возникало.

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

Комментарии

Популярные сообщения из этого блога

Прямые ссылки на файлы Google диска

В предыдущей статье я рассказал, как подключить свой JavaScript файл к блогу BLOGSPOT . Но для того, что бы их подключить нужны прямые ссылки на файл, а Google диск при предоставлении общего доступа к файлу выдает ссылку на предварительный просмотр, которая никак напрямую не ссылается на файл. Для Google диска прямая ссылка на файл - это ссылка на скачивание. Ниже описаны два способа создания ссылки на скачивание на примере файла prism.js.

События для ведения таблиц

Как и всегда, в пылу проекта внезапно родилась Z табличка. Главный нюанс был в том, что она должна была хранить пароли для авторизации на стороннем сервере. Естественно, никто не хотел хранить пароли в открытом виде, а двустороннее шифрование SAP не умеет без сторонних пакетов и надстроек. Далее, все как обычно - придумали алгоритм, сделали табличку. Дело осталось за малым - нужно шифровать пароли, которые вводит пользователь. Делать отдельную программу нет смысла, поскольку ее функционал мало чем будет отличаться от сгенерированного. Вот здесь на помощь приходят события! С их помощью можно, наверное, все. По крайней мере, я не нашел чего-либо, что нельзя сделать с данными через события.

OOP ALV GRID с HTML шапкой

В этой статье хочу постараться подробно описать и привести пример, как можно создать ALV отчет с таблицей на весь экран и с HTML шапкой вверху. Я не буду описывать начальный этап, где пишется селекционный экран или делается выборка данных. Будем считать, что основа у нас есть и нам нужно просто вывести данные. Главной изюминкой является то, что нужно вывести ALV GRID на экран без использования каких-либо дополнительных элементов на экране. Step-By-Step Шаг 1. Создание окна Создаем самое простое окно с номером 100. На него не нужно кидать никаких контейнеров. Оно нам нужно только для модулей PAI и PBO и вывода на него ALV GRID.