Статьи

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

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

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

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

Работая над плагином, вы узнаете о следующих темах:

  • Создание API поверх плагина WordPress.
  • Создание страницы настроек.
  • Загрузка файлов в Amazon Simple Storage Service (S3)
  • Использование AWS SDK для создания подписанных URL-адресов, которые можно использовать для загрузки личных файлов с S3.

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

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

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

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

Наш API будет доступен через URL-адрес http://<yoursite>/api/license-manager/v1/ , где yoursite — это URL-адрес вашего сайта WordPress, на котором работает плагин WP License Manager. API будет иметь две функции:

  • info : возвращает информацию о запрашиваемом продукте, если данный лицензионный ключ действителен.
  • get : Возвращает загружаемый файл, если указанный лицензионный ключ действителен.

Давайте строить это!

До сих пор мы работали хорошо, просто используя существующие классы из шаблона WordPress Plugin Boilerplate, но теперь, чтобы сохранить чистоту, давайте добавим новый класс для хранения специфических функций API.

API является общедоступной частью плагина, поэтому новый класс должен перейти в public каталог в шаблоне плагина. Вызовите класс API Wp_License_Manager_API и поместите его в файл с именем class-wp-license-manager-api.php .

Сделайте пока пустой класс:

01
02
03
04
05
06
07
08
09
10
11
/**
 * The API handler for handling API requests from themes and plugins using
 * the license manager.
 *
 * @package Wp_License_Manager
 * @subpackage Wp_License_Manager/public
 * @author Jarkko Laine <[email protected]>
 */
class Wp_License_Manager_API {
 
}

Свяжите вновь созданный класс с вашим классом Wp_License_Manager_Public чтобы сделать его доступным при необходимости. Сначала добавьте поле для класса API:

1
2
3
4
/**
 * @var License_Manager_API The API handler
 */
private $api;

Затем инициализируйте его в конструкторе ( строка 11 новая, все остальное уже есть в первой части):

01
02
03
04
05
06
07
08
09
10
11
12
/**
 * Initialize the class and set its properties.
 *
 * @var string $plugin_name The name of the plugin.
 * @var string $version The version of this plugin.
 */
public function __construct( $plugin_name, $version ) {
    $this->plugin_name = $plugin_name;
    $this->version = $version;
 
    $this->api = new Wp_License_Manager_API();
}

Теперь все готово для создания функциональности API.

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

По умолчанию (и под капотом) URL-адреса WordPress представляют собой комбинацию index.php и набора параметров запроса, например: http://<yoursite>/?p=123 ( index.php в URL опущен, но вот куда идет запрос). Большинство сайтов используют более приятные настройки постоянных ссылок — и мы тоже это сделаем — чтобы сделать URL более читабельными, но это всего лишь слой украшения поверх этой основной функциональности.

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

В конце функции define_public_hooks в классе Wp_License_Manager добавьте следующие строки, чтобы связать фильтр с перехватчиком query_vars :

1
2
// The external API setup
$this->loader->add_filter( ‘query_vars’, $plugin_public, ‘add_api_query_vars’ );

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
/**
 * Defines the query variables used by the API.
 *
 * @param $vars array Existing query variables from WordPress.
 * @return array The $vars array appended with our new variables
 */
public function add_api_query_vars( $vars ) {
    // The parameter used for checking the action used
    $vars []= ‘__wp_license_api’;
 
    // Additional parameters defined by the API requests
    $api_vars = $this->api->get_api_vars();
 
    return array_merge( $vars, $api_vars );
}

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

Строка 9: добавить переменную для указания действия API (и признать, что запрос фактически предназначен для API), __wp_license_api .

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

Строка 14 : вернуть обновленный массив $vars .

Чтобы заставить код работать, нам все еще нужно создать функцию get_api_vars :

1
2
3
4
5
6
7
8
/**
 * Returns a list of variables used by the API
 *
 * @return array An array of query variable names.
 */
public function get_api_vars() {
    return array( ‘l’, ‘e’, ‘p’ );
}

На данный момент, поскольку API все еще очень прост, я решил упростить эту функцию и просто включить все параметры в один массив и вернуть их.

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

Это можно сделать с помощью действия WordPress parse_request .

В Wp_License_Manager define_public_hooks связать функцию с действием:

1
$this->loader->add_action( ‘parse_request’, $plugin_public, ‘sniff_api_requests’ );

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

Функция sniff_api_requests переходит в Wp_License_Manager_Public :

01
02
03
04
05
06
07
08
09
10
11
12
/**
 * A sniffer function that looks for API calls and passes them to our API handler.
 */
public function sniff_api_requests() {
    global $wp;
    if ( isset( $wp->query_vars[‘__wp_license_api’] ) ) {
        $action = $wp->query_vars[‘__wp_license_api’];
        $this->api->handle_request( $action, $wp->query_vars );
 
        exit;
    }
}

Строка 6: проверьте, присутствует ли переменная запроса действия API. Если да, это вызов для этого API для обработки.

Строки 7-8: handle_request запрос в функцию класса API handle_request для обработки. Мы создадим эту функцию дальше.

Строка 10 : остановите выполнение запроса WordPress, как только мы обработаем вызов API. Это важно, так как в противном случае выполнение продолжится до самого WordPress и отобразит страницу ошибки 404 после вывода API.

Далее давайте добавим функцию handle_request .

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
 * The handler function that receives the API calls and passes them on to the
 * proper handlers.
 *
 * @param $action string The name of the action
 * @param $params array Request parameters
 */
public function handle_request( $action, $params ) {
    switch ( $action ) {
        case ‘info’:
            break;
 
        case ‘get’:
            break;
 
        default:
            $response = $this->error_response( ‘No such API action’ );
            break;
    }
 
    $this->send_response( $response );
}

Строка 17 : $response — это массив с информацией, которую следует передать приложению, вызывающему API.

Строка 21 : распечатать ответ в виде строки в кодировке JSON.

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

Во-первых, send_response :

1
2
3
4
5
6
7
8
/**
 * Prints out the JSON response for an API call.
 *
 * @param $response array The response as associative array.
 */
private function send_response( $response ) {
    echo json_encode( $response );
}

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

Сейчас я решил использовать базовую версию, в которой нет ничего, кроме сообщения об ошибке внутри массива:

01
02
03
04
05
06
07
08
09
10
/**
 * Generates and returns a simple error response.
 * message uses same formatting.
 *
 * @param $msg string The message to be included in the error response.
 * @return array The error response as an array that can be passed to send_response.
 */
private function error_response( $msg ) {
    return array( ‘error’ => $msg );
}

Теперь вы создали структуру, внутри которой мы можем начать создавать действия API. Чтобы проверить это, попробуйте вызвать несуществующее действие API, например, http://<yoursite>/?__wp_license_api=dummy-action , в окне браузера.

Вы должны увидеть что-то вроде этого:

Даже если API все еще возвращает просто сообщение об ошибке, тест показывает, что запрос направляется на правильную функцию-обработчик и не смешивается с остальной частью потока WordPress. Как мы и хотели.

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

Хотя http://<yoursite>/?__wp_license_api=<action> работает, это не очень красиво, и, на мой взгляд, содержит слишком много внутренних деталей реализации API. Итак, давайте создадим более удобный формат URL для использования вместо него.

В качестве напоминания в начале этого раздела приведем URL-адрес, который мы будем использовать: http://<yoursite>/api/license-manager/v1/

Я добавил v1 к URL-адресу API только на тот случай, если мы когда-нибудь обнаружим, что поддерживаем несколько различных версий API. Наличие информации о версии с самого начала облегчает добавление новых версий позже.

Во-первых, чтобы заставить «красивые» URL-адреса работать подобным образом, установите для параметра « Постоянные ссылки» в настройках WordPress значение, отличное от значения по умолчанию (все остальные параметры, кроме «По умолчанию», подходят).

Затем давайте добавим фильтр для создания нового правила перезаписи. Добавьте регистрацию фильтра в Wp_License_Manager define_public_hooks :

1
$this->loader->add_action( ‘init’, $plugin_public, ‘add_api_endpoint_rules’ );

Затем создайте функцию (в Wp_License_Manager_Public ):

01
02
03
04
05
06
07
08
09
10
11
12
13
/**
 * The permalink structure definition for API calls.
 */
public function add_api_endpoint_rules() {
    add_rewrite_rule( ‘api/license-manager/v1/(info|get)/?’,
        ‘index.php?__wp_license_api=$matches[1]’, ‘top’ );
 
    // If this was the first time, flush rules
    if ( get_option( ‘wp-license-manager-rewrite-rules-version’ ) != ‘1.1’ ) {
        flush_rewrite_rules();
        update_option( ‘wp-license-manager-rewrite-rules-version’, ‘1.1’ );
    }
}

Строка 5. Используя регулярное выражение, определите новое правило перезаписи URL-адреса для отправки запросов, отформатированных в соответствии с тем, что мы имели в виду, на URL-адрес, который мы использовали на предыдущем шаге. (info|get) определяет методы API, которые должен принимать API.

Строки 9-12 : сохраните правила перезаписи, чтобы сделать их активными. flush_rewrite_rules — это вызов, потребляющий ресурсы, поэтому я добавил проверку вокруг него, чтобы убедиться, что сброс выполняется только после изменения правил.

Теперь, когда этот шаг завершен, любые запросы к http://<yoursite>/api/license-manager/v1/<action> преобразуются в запрос к http://<yoursite>/?__wp_license_api=<action> , Проверьте это с помощью того же фиктивного действия из предыдущего шага — теперь вы должны получить тот же результат, используя оба URL-адреса.

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

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

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

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

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

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
/**
 * Checks the parameters and verifies the license, then forwards the request to the
 * actual API request handlers.
 *
 * @param $action_function callable The function (or array with class and function) to call
 * @param $params array The WordPress request parameters.
 * @return array API response.
 */
private function verify_license_and_execute( $action_function, $params ) {
    if ( ! isset( $params[‘p’] ) || ! isset( $params[‘e’] ) || ! isset( $params[‘l’] ) ) {
        return $this->error_response( ‘Invalid request’ );
    }
 
    $product_id = $params[‘p’];
    $email = $params[‘e’];
    $license_key = $params[‘l’];
 
    // Find product
    $posts = get_posts(
        array (
            ‘name’ => $product_id,
            ‘post_type’ => ‘wplm_product’,
            ‘post_status’ => ‘publish’,
            ‘numberposts’ => 1
        )
    );
 
    if ( ! isset( $posts[0] ) ) {
        return $this->error_response( ‘Product not found.’ );
    }
 
    // Verify license
    if ( ! $this->verify_license( $posts[0]->ID, $email, $license_key ) ) {
        return $this->error_response( ‘Invalid license or license expired.’ );
    }
 
    // Call the handler function
    return call_user_func_array( $action_function, array( $posts[0], $product_id, $email, $license_key ) );
}

Строки 10-12 : убедитесь, что присутствуют все параметры, связанные с лицензией. p — это фрагмент продукта, e — адрес электронной почты пользователя, а l — лицензионный ключ.

Строки 14-16 : собрать и очистить параметры.

Строки 19-26 : найдите сообщение, соответствующее указанному идентификатору продукта. Как я уже упоминал выше, идентификатор продукта на самом деле является пост-слагом продукта, чтобы пользователю было проще узнать его правильное значение.

Строки 28-30 : если товар не найден, вернуть ошибку.

Строки 33-35 : Выполните фактическую проверку лицензионного ключа. Мы создадим эту функцию дальше.

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

Далее давайте добавим функцию для проверки лицензии:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
/**
 * Checks whether a license with the given parameters exists and is still valid.
 *
 * @param $product_id int The numeric ID of the product.
 * @param $email string The email address attached to the license.
 * @param $license_key string The license key.
 * @return bool true if license is valid.
 */
private function verify_license( $product_id, $email, $license_key ) {
    $license = $this->find_license( $product_id, $email, $license_key );
    if ( ! $license ) {
        return false;
    }
 
    $valid_until = strtotime( $license[‘valid_until’] );
    if ( $license[‘valid_until’] != ‘0000-00-00 00:00:00’ && time() > $valid_until ) {
        return false;
    }
 
    return true;
}

Строки 10-13 : найдите лицензию из базы данных (мы добавим эту функцию далее), используя отправленные параметры. Если лицензия не найдена, она не может быть действительной, поэтому мы можем вернуть false.

Строки 15-18 : если лицензия существует, проверьте, не истек ли срок ее действия. В первой части руководства мы определили, что 0000-00-00 00:00:00 используется для лицензии, срок действия которой никогда не истекает.

Строка 20 : все проверки пройдены, лицензия действительна.

Наконец, для завершения кода проверки лицензии нам нужна функция для получения лицензии из базы данных.

Добавьте функцию find_license :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
 * Looks up a license that matches the given parameters.
 *
 * @param $product_id int The numeric ID of the product.
 * @param $email string The email address attached to the license.
 * @param $license_key string The license key
 * @return mixed The license data if found.
 */
private function find_license( $product_id, $email, $license_key ) {
    global $wpdb;
    $table_name = $wpdb->prefix .
 
    $licenses = $wpdb->get_results(
        $wpdb->prepare( «SELECT * FROM $table_name WHERE product_id = %d AND email = ‘%s’ AND license_key = ‘%s'»,
            $product_id, $email, $license_key ), ARRAY_A );
 
    if ( count( $licenses ) > 0 ) {
        return $licenses[0];
    }
 
    return false;
}

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

Если лицензия найдена, функция возвращает ее. В противном случае возвращается false .

Далее, давайте добавим первое из двух действий, info . В функции handle_request которую мы создали недавно, заполните ветвь case для действия:

1
2
3
case ‘info’:
   $response = $this->verify_license_and_execute( array( $this, ‘product_info’ ), $params );
   break;

Строка, которую мы добавили, вызывает нашу функцию verify_license_and_execute с двумя параметрами:

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

Мы уже рассмотрели, что происходит внутри verify_license_and_execute , поэтому теперь давайте добавим функцию product_info . Эта функция будет собирать информацию о продукте и возвращать ее в виде массива.

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

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
/**
 * The handler for the «info» request.
 * returns information about the product (latest version, name, update url).
 *
 * @param $product WP_Post The product object
 * @param $product_id string The product id (slug)
 * @param $email string The email address associated with the license
 * @param $license_key string The license key associated with the license
 *
 * @return array The API response as an array.
 */
private function product_info( $product, $product_id, $email, $license_key ) {
    // Collect all the metadata we have and return it to the caller
    $meta = get_post_meta( $product->ID, ‘wp_license_manager_product_meta’, true );
 
    $version = isset( $meta[‘version’] ) ?
    $tested = isset( $meta[‘tested’] ) ?
    $last_updated = isset( $meta[‘updated’] ) ?
    $author = isset( $meta[‘author’] ) ?
    $banner_low = isset( $meta[‘banner_low’] ) ?
    $banner_high = isset( $meta[‘banner_high’] ) ?
 
    return array(
        ‘name’ => $product->post_title,
        ‘description’ => $product->post_content,
        ‘version’ => $version,
        ‘tested’ => $tested,
        ‘author’ => $author,
        ‘last_updated’ => $last_updated,
        ‘banner_low’ => $banner_low,
        ‘banner_high’ => $banner_high,
        «package_url» => home_url( ‘/api/license-manager/v1/get?p=’ . $product_id . ‘&e=’ . $email . ‘&l=’ . urlencode( $license_key ) ),
        «description_url» => get_permalink( $product->ID ) .
    );
}

Строка 14 : получение информации о продукте из метаданных публикации.

Строки 16-21 : сбор данных из полей метаданных.

Строки 23-34 : Построить ответ. В основном это просто размещение данных из строк 16-21 в массиве. Одна строка, строка 32 , заслуживает еще большего внимания.

Это для системы обновления WordPress, которую мы будем использовать в следующем уроке из этой серии. Чтобы упростить этот шаг, информационное действие возвращает URL-адрес, указывающий на другое действие API get , со всеми необходимыми параметрами.

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

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

Вывод будет выглядеть примерно так, в зависимости от того, что вы сохранили в качестве данных вашего продукта:

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

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

На следующих шагах мы сначала рассмотрим, как загрузить файлы в Amazon S3 с помощью онлайн-панели инструментов AWS (Amazon Web Services). Затем, после загрузки файла, мы добавим функцию загрузки в наш API.

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

Создание учетной записи AWS хорошо документировано, но в ней много шагов, поэтому давайте быстро пройдемся по ней.

Сначала зайдите на aws.amazon.com и нажмите «Зарегистрироваться» в правом верхнем углу.

На следующем экране у вас есть возможность создать новую учетную запись Amazon или использовать существующую, которую вы использовали для других сервисов Amazon (например, для самого интернет-магазина):

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

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

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

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

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

В администраторе AWS доступно множество функций, но остальные вы можете пока игнорировать (это продвинутые вещи, которые большинству разработчиков WordPress никогда не понадобятся — я сейчас использую только S3 и EC2).

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

Каждый объект, который вы храните в Amazon S3, находится в ведре. Вы можете использовать сегменты для группировки связанных объектов так же, как вы используете каталог для группировки файлов в файловой системе. У контейнеров есть свойства, такие как права доступа и статус управления версиями, и вы можете указать регион, в котором вы хотите их разместить.

Дайте вашему ведру имя, состоящее из строчных букв (az), цифр, тире и точек (для более подробных требований ознакомьтесь с документацией ) и нажмите «Создать».

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

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

На этом экране вы можете создать столько новых сегментов, сколько захотите. Пока достаточно одного. Нажмите на название корзины, чтобы добавить к ней файлы.

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

Нажмите кнопку « Загрузить» в левом верхнем углу экрана администрирования S3. Появится экран загрузки:

Выберите « Добавить файлы», чтобы выбрать файлы для загрузки. Затем нажмите Начать загрузку .

Мы могли бы использовать Set Details, чтобы установить права доступа к файлу, но поскольку мы не хотим делать загрузку общедоступной, параметры по умолчанию — это то, что нам нужно.

Как только файл будет загружен, вы увидите его в своем ведре:

Закройте окно передачи, нажав на крестик в правом верхнем углу. Затем выберите файл и выберите « Свойства» в правом верхнем меню:

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

Если все настроено правильно, вы не можете сделать это: даже если случайный посетитель знал точный URL-адрес вашего файла загрузки, он или она не сможет загрузить его, пропустив проверку лицензии в вызове API.

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

В качестве последнего шага перед дальнейшим программированием создайте продукт в WordPress с помощью нашего плагина менеджера лицензий (или обновите существующий) и добавьте информацию о загружаемом файле:

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

Для этого мы заполним заполнитель действия API, который мы добавили в нашу функцию обработки API handle_request ранее в этом руководстве. Сначала действие проверяет лицензию с помощью verify_license_and_execute а затем использует официальную библиотеку Amazon AWS для создания и возврата подписанной ссылки на скачивание в загружаемый файл продукта.

Для начала загрузите AWS SDK для PHP с Amazon.

Есть много способов включить SDK в ваш проект, но я предлагаю использовать zip-загрузку в этом проекте, даже если он уже немного старомоден: SDK содержит функциональные возможности для использования всех функций AWS — как вы видели ранее, их много — и мы используем только один, S3. Загрузка zip-релиза и включение его в ваш проект вручную дает вам возможность удалить все дополнительные файлы, которые занимают только место при загрузке плагина.

После загрузки AWS SDK создайте каталог с именем lib в каталоге wp-license-manager . Внутри него создайте каталог с именем aws содержащий содержимое zip-пакета SDK.

Теперь, если хотите, вы можете обрезать содержимое каталога, оставив только каталоги Common и S3 внутри Aws . Если размер плагина не является проблемой, вы можете просто пропустить этот шаг и оставить SDK без изменений.

Затем подключите AWS SDK к плагину WP License Manager на уровне кода.

В SDK используются пространства имен, функция PHP, которая была добавлена ​​только в PHP 5.3. Официальное требование PHP для WordPress — 5.2.6, поэтому использование SDK не будет работать для всех пользователей WordPress.

Чтобы обойти это ограничение, можно использовать стороннюю библиотеку S3 или написать ее самостоятельно. Однако, поскольку использование официального SDK является более рекомендуемым способом в общем, давайте на этот раз пойдем по этому пути. В версию плагина, опубликованную в репозитории плагинов WordPress, я включил как версию AWS SDK, так и резервную версию с использованием этой автономной библиотеки S3 . Плагин выбирает правильный для использования во время выполнения, основываясь на версии PHP системы, на которой работает плагин.

Однако в этом уроке мы сделаем все немного проще и будем иметь только одну версию AWS SDK.

Создайте новый класс Wp_License_Manager_S3 и поместите его в Wp_License_Manager_S3 модулей плагина:

01
02
03
04
05
06
07
08
09
10
11
<?php
/**
 * A wrapper for our Amazon S3 API actions.
 *
 * @package Wp_License_Manager
 * @subpackage Wp_License_Manager/includes
 * @author Jarkko Laine <[email protected]>
 */
class Wp_License_Manager_S3 {
 
}

Чтобы включить класс в проект, добавьте следующие строки в функцию load_dependencies класса load_dependencies :

1
2
3
4
5
/**
 * A wrapper class for our Amazon S3 connectivity.
 */
require_once plugin_dir_path( dirname( __FILE__ ) ) .
require_once plugin_dir_path( dirname( __FILE__ ) ) .

Первая строка require включает созданный выше класс-оболочку, а вторая — автозагрузчик AWS SDK, фрагмент кода, отвечающий за запрос исходных файлов SDK по мере необходимости.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
/**
 * Returns a signed Amazon S3 download URL.
 *
 * @param $bucket string Bucket name
 * @param $file_name string File name (URI)
 * @return string The signed download URL
 */
public static function get_s3_url( $bucket, $file_name ) {
    $options = get_option( ‘wp-license-manager-settings’ );
 
    $s3_client = Aws\S3\S3Client::factory(
        array(
            ‘key’ => $options[‘aws_key’],
            ‘secret’ => $options[‘aws_secret’]
        )
    );
 
    return $s3_client->getObjectUrl( $bucket, $file_name, ‘+10 minutes’ );
}

Давайте рассмотрим функцию, чтобы увидеть, что делает id:

Строки 11-16 : создание экземпляра класса клиента Amazon S3 для создания ссылки на скачивание. Для SDK требуются учетные данные безопасности AWS, которые мы будем хранить в настройках WordPress, используя экран настроек, который мы создадим на следующем шаге.

Строка 18 : используйте SDK для генерации ссылки на скачивание для данного сегмента и имени файла. С этого момента ссылка на скачивание будет действовать в течение 10 минут, так что у пользователя будет достаточно времени для начала загрузки.

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

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

Все начинается с добавления двух действий, одного в admin_init и одного в admin_menu :

Теперь, когда вы добавили действия, создайте функции для их обработки. Оба они должны быть добавлены в класс Wp_License_Manager_Admin .

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

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
/**
 * Creates the settings fields for the plugin options page.
 */
public function add_plugin_settings_fields() {
    $settings_group_id = ‘wp-license-manager-settings-group’;
    $aws_settings_section_id = ‘wp-license-manager-settings-section-aws’;
    $settings_field_id = ‘wp-license-manager-settings’;
 
    register_setting( $settings_group_id, $settings_field_id );
 
    add_settings_section(
        $aws_settings_section_id,
        __( ‘Amazon Web Services’, $this->plugin_name ),
        array( $this, ‘render_aws_settings_section’ ),
        $settings_group_id
    );
 
    add_settings_field(
        ‘aws-key’,
        __( ‘AWS public key’, $this->plugin_name ),
        array( $this, ‘render_aws_key_settings_field’ ),
        $settings_group_id,
        $aws_settings_section_id
    );
 
    add_settings_field(
        ‘aws-secret’,
        __( ‘AWS secret’, $this->plugin_name ),
        array( $this, ‘render_aws_secret_settings_field’ ),
        $settings_group_id,
        $aws_settings_section_id
    );
}

Строки 5-7 : Инициализируйте три переменные с именами полей настроек и имен разделов. Это помогает нам убедиться, что мы не вставляем опечатки …

Строка 9 : зарегистрируйте пункт настроек 'wp-license-manager-settings' . Это будет массив, в котором будут храниться две настройки: aws-key и aws-secret .

Строки 11-16 : добавьте раздел настроек, который будет содержать оба поля настроек.

Строки 18-32 : Добавьте два поля настроек ( aws-keyи aws-secret), поместив их в раздел настроек, созданный в строках 11-16.

Теперь добавьте вторую функцию add_plugin_settings_page. Эта функция создаст страницу настроек как дочернюю для главного меню настроек , используя эту функцию add_options_page.

01
02
03
04
05
06
07
08
09
10
11
12
/**
 * Adds an options page for plugin settings.
 */
public function add_plugin_settings_page() {
    add_options_page(
        __( 'License Manager', $this->plugin_name ),
        __( 'License Manager Settings', $this->plugin_name ),
        ‘manage_options’,
        'wp-license-settings',
        array( $this, 'render_settings_page' )
    );
}

Каждый из элементов функций 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
/**
 * Renders the plugin's options page.
 */
public function render_settings_page() {
    $settings_group_id = 'wp-license-manager-settings-group';
    require plugin_dir_path( dirname( __FILE__ ) ) . 'admin/partials/settings_page.php';
}
 
/**
 * Renders the description for the AWS settings section.
 */
public function render_aws_settings_section() {
    // We use a partial here to make it easier to add more complex instructions
    require plugin_dir_path( dirname( __FILE__ ) ) . 'admin/partials/aws_settings_group_instructions.php';
}
 
/**
 * Renders the settings field for the AWS key.
 */
public function render_aws_key_settings_field() {
    $settings_field_id = 'wp-license-manager-settings';
    $options = get_option( $settings_field_id );
    ?>
        <input type='text' name='<?php echo $settings_field_id; ?>[aws_key]' value='<?php echo $options['aws_key']; ?>' class='regular-text'>
    <?php
}
 
/**
 * Renders the settings field for the AWS secret.
 */
public function render_aws_secret_settings_field() {
    $settings_field_id = 'wp-license-manager-settings';
    $options = get_option( $settings_field_id );
    ?>
       <input type='text' name='<?php echo $settings_field_id; ?>[aws_secret]' value='<?php echo $options['aws_secret']; ?>' class='regular-text'>
    <?php
}

Поле рендеринга в настройках функционирует render_aws_key_settings_fieldи render_aws_secret_settings_fieldявляется в основном копиями друг друга: сначала они получают параметры плагина, а затем распечатывают текстовое поле с названием настройки и текущим значением.

Функции для рендеринга страницы настроек ( render_settings_page) и секции настроек ( render_aws_settings_section) похожи, но вместо того, чтобы печатать HTML прямо в функции, они используют отдельные шаблоны HTML. Это ни в коем случае не единственный правильный способ сделать это — я выбрал этот подход, потому что эти функции визуализируют немного больше HTML, и может потребоваться его расширение в более поздний момент времени.

Вот что входит в шаблоны. Во-первых, admin/partials/settings_page.phpчастичное для страницы настроек:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
/**
 * The view for the plugin's options page.
 *
 * @package Wp_License_Manager
 * @subpackage Wp_License_Manager/admin/partials
 */
?>
 
<div class=»wrap»>
    <div id="icon-edit" class="icon32 icon32-posts-post"></div>
 
    <h2>
        ?>
    </h2>
 
    <form action=’options.php’ method=’post’>
        <?php
        settings_fields( $settings_group_id );
        do_settings_sections( $settings_group_id );
        submit_button();
        ?>
    </form>
</div>

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

Партиал для раздела настроек ( admin/partials/aws_settings_group_instructions.php) в настоящее время почти пуст, выводит короткую строку инструкций:

01
02
03
04
05
06
07
08
09
10
<?php
/**
 * The view for the AWS settings section's description on the plugin's settings page.
 *
 * @package Wp_License_Manager
 * @subpackage Wp_License_Manager/admin/partials
 */
?>
 
?>

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

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

Для этого вернитесь в консоль администратора AWS и щелкните свое имя в правом верхнем меню. В раскрывающемся списке выберите Учетные данные безопасности .

Далее вам будет показано следующее всплывающее окно:

Выберите опцию « Начало работы с пользователями IAM» . Таким образом, вместо повсеместного использования ваших глобальных учетных данных безопасности, вы можете создавать отдельные имена пользователей (и учетные данные для доступа) для любого другого использования AWS, которое у вас может быть.

Затем создайте пользователя, который будет использоваться для установки WP License Manager.

Сначала нажмите « Создать пользователей» вверху страницы. Затем на следующей странице введите имя пользователя, которое имеет смысл для вас (я использовал просто test_user, но, возможно, лучшим вариантом будет менеджер лицензий или что-то, что скажет вам, для чего используется пользователь), и нажмите « Создать». ,

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

Теперь мы собрали все части, необходимые для getдействия API. Давайте сложим их вместе и создадим само действие.

Сначала заполните switch..caseблок, который мы оставили для getдействия handle_requestвнутри функции Wp_License_Manager_API:

1
2
3
case 'get':
   $response = $this->verify_license_and_execute( array( $this, 'get_product' ), $params );
   break;

Функция verify_license_and_executeуже выполнена, поэтому осталось добавить функцию-обработчик get_product.

Вот функция:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
 * The handler for the "get" request. Redirects to the file download.
 *
 * @param $product WP_Post The product object
 */
private function get_product( $product, $product_id, $email, $license_key ) {
    // Get the AWS data from post meta fields
    $meta = get_post_meta( $product->ID, 'wp_license_manager_product_meta', true );
    $bucket = isset ( $meta['file_bucket'] ) ? $meta['file_bucket'] : '';
    $file_name = isset ( $meta['file_name'] ) ? $meta['file_name'] : '';
 
    if ( $bucket == '' || $file_name == '' ) {
        // No file set, return error
        return $this->error_response( 'No download defined for product.' );
    }
 
    // Use the AWS API to set up the download
    // This API method is called directly by WordPress so we need to adhere to its
    // requirements and skip the JSON. WordPress expects to receive a ZIP file...
 
    $s3_url = Wp_License_Manager_S3::get_s3_url( $bucket, $file_name );
    wp_redirect( $s3_url, 302 );
}

Давайте рассмотрим функцию, чтобы увидеть, что она делает:

Строки 7-10 . Считайте настройки группы и имени файла из метаданных продукта.

Строки 12-15 : если метаданные не заданы, вернуть ответ об ошибке.

Строка 21 : используйте AWS SDK для создания подписанного URL-адреса для загружаемого файла запрашиваемого продукта.

Строка 22 : Перенаправление на подписанный URL-адрес загрузки на этапе S3. Как мы увидим в следующем шаге учебного руководства, WordPress ожидает, что этот запрос вернет фактический файл, поэтому нам нужно немного отклониться от нашего API на основе JSON и просто сделать то, что лучше всего подходит для WordPress.

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

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

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