1. Web Design
  2. HTML/CSS
  3. HTML

Creación de un sistema de actualización controlado por licencia: la API de License Manager

Scroll to top
This post is part of a series called Create a License Controlled Theme and Plugin Update System.
Create a License Controlled Update System: The License Manager Plugin
Create a License Controlled Update System: Doing the Update

Spanish (Español) translation by Eva Collados Pascual (you can also view the original English article)

Este es el segundo tutorial de una serie compuesta por tres partes sobre la construcción de un plugin de WordPress controlado por licencia y de un sistema de actualización de temas.

En la primera parte de la serie, creamos un plugin de WordPress para almacenar y gestionar licencias de software. En la tercera parte, haremos que un tema y un plugin de WordPress usen ese servidor administrador de licencias para buscar actualizaciones y descargarlas.

En este tutorial, crearemos la interfaz entre los dos: una API con dos acciones a las que se puede invocar desde un sitio externo para obtener información sobre un producto y su licencia y para descargar el producto.

Mientras trabajas en el plugin, aprenderás sobre los siguientes aspectos:

  • La construcción de una API sobre la base de un plugin de WordPress.
  • La creación de una página de configuración.
  • La carga de archivos en Amazon Simple Storage Service (S3)
  • El uso del SDK de AWS para crear URL firmadas que se pueden utilizar para descargar archivos privados desde S3.

Continuaremos construyendo el plugin desde el punto en el que dejamos la creación de su código durante la primera parte del tutorial, de manera que seas capaz de construir un funcional plugin siguiendo los pasos. Si deseas seguir el tutorial utilizando el código preparado de antemano, consulta el código fuente en el repositorio de Github de Tuts+ cuyo enlace aparece a la derecha de esta página.

Empecemos.

Crear la API de License Manager

Una API puede significar muchas cosas diferentes. En este tutorial, significa un conjunto de acciones (a menudo denominadas métodos) que se pueden invocar a través de una conexión HTTP para tener acceso a una parte bien definida y limitada de la funcionalidad de la aplicación.

En la tercera parte de esta serie, usaremos esta API en un plugin o tema premium de WordPress para comprobar la validez de la licencia y para descargar actualizaciones, pero la propia API no establece limitaciones para esto. La aplicación que utiliza la API también podría ser un juego que necesita ser activado con una clave de licencia antes de poder jugar con él.

Nuestra API será accesible a través de la URL https://<yoursite>/api/license-manager/v1/, donde "yoursite" es la URL de tu sitio WordPress que ejecuta el plugin WP License Manager. La API tendrá dos funciones:

  • info: devuelve información sobre el producto solicitado si la clave de licencia dada es válida.
  • get: devuelve el archivo descargable si la clave de licencia proporcionada es válida.

¡Vamos a construirlo!

Paso 1: Crear una clase para la funcionalidad de la API

Hasta ahora, hemos actuado bien al usar las clases existentes de WordPress Plugin Boilerplate, pero ahora, para mantener las cosas limpias, vamos a añadir una nueva clase para almacenar la funcionalidad específica de la API.

Una API es una parte pública del plugin, por lo que la nueva clase debe ir al directorio public del plugin boilerplate. Llama a la clase de la API Wp_License_Manager_API y colócala en un archivo llamado class-wp-license-manager-api.php.

Por ahora, haz que sea una clase vacía:

1
/**

2
 * The API handler for handling API requests from themes and plugins using

3
 * the license manager.

4
 *

5
 * @package    Wp_License_Manager

6
 * @subpackage Wp_License_Manager/public

7
 * @author     Jarkko Laine <jarkko@jarkkolaine.com>

8
 */
9
class Wp_License_Manager_API {
10
11
}

Vincula la clase recién creada a la clase Wp_License_Manager_Public para que esté disponible cuando sea necesario. En primer lugar, añade un campo para la clase de la API:

1
/**

2
 * @var     License_Manager_API     The API handler

3
 */
4
private $api;

A continuación, inicialízalo en el constructor (la línea 11 es la nueva, todo lo demás ya está allí desde la primera parte):

1
/**
2
 * Initialize the class and set its properties.
3
 *
4
 * @var      string    $plugin_name       The name of the plugin.
5
 * @var      string    $version    The version of this plugin.
6
 */
7
public function __construct( $plugin_name, $version ) {
8
    $this->plugin_name = $plugin_name;
9
    $this->version = $version;
10
11
    $this->api = new Wp_License_Manager_API();	
12
}

Ahora, estamos preparados para crear la funcionalidad de la API.

Paso 2: Definir variables de consulta para la API

Como mencioné anteriormente, comenzaremos creando un formato de URL especial para acceder a la API. De esta manera, podemos separar las llamadas de la API de otras solicitudes al sitio sin confundirnos con el resto del contenido del sitio de WordPress.

Por defecto (y bajo el capó), las URLs de WordPress son una combinación de index.php y un conjunto de parámetros de consulta, por ejemplo: http://<yoursite>/?p-123 (index.php se omite en la URL, pero ahí es donde va la solicitud). La mayoría de los sitios utilizan ajustes de enlace permanente más bonitos (pretty permalinks), nosotros también lo haremos, para que las URLs sean más legibles, pero eso es solo una capa decorativa superpuesta a la funcionalidad principal.

Por lo tanto, para crear nuestra API, lo primero es añadir una variable de consulta personalizada, __wp_license_api, que usaremos en lugar de p. A continuación, una vez que esto esté funcionando, añadiremos la configuración del permalink personalizado para que la API sea más legible.

Al final de la función define_public_hooks en la clase Wp_License_Manager, añade las siguientes líneas para vincular un filtro al gancho query_vars :

1
// The external API setup

2
$this->loader->add_filter( 'query_vars', $plugin_public, 'add_api_query_vars' );

A continuación, añade la función en la clase Wp_License_Manager_Public:

1
/**

2
 * Defines the query variables used by the API.

3
 *

4
 * @param $vars     array   Existing query variables from WordPress.

5
 * @return array    The $vars array appended with our new variables

6
 */
7
public function add_api_query_vars( $vars ) {
8
    // The parameter used for checking the action used

9
    $vars []= '__wp_license_api';
10
11
    // Additional parameters defined by the API requests

12
    $api_vars = $this->api->get_api_vars();
13
14
    return array_merge( $vars, $api_vars );
15
}

Esta función añade nuestras variables de consulta a la lista blanca de WordPress agregándolas a la matriz $vars que es pasada por el filtro y devuelve la matriz anexada:

Línea 9: añade la variable para especificar la acción de la API (y reconocer que la solicitud está realmente pensada para la API) __wp_license_api.

Línea 12: dá a la clase API la oportunidad de añadir cualquier parámetro adicional utilizado por sus funciones.

Línea 14: devuelve la matriz $vars actualizada.

Para que el código funcione, todavía necesitamos crear la función get_api_vars:

1
/**

2
 * Returns a list of variables used by the API

3
 *

4
 * @return  array    An array of query variable names.

5
 */
6
public function get_api_vars() {
7
    return array( 'l',  'e', 'p' );
8
}

Por ahora, como la API sigue siendo muy simple, decidí crear esta sencilla función y simplemente incluir todos los parámetros en una matriz y devolverlos.

Paso 3: Captura las solicitudes de API

Ahora hemos incluido en la lista blanca nuestras variables de consulta, pero aún no hacen nada. Para que las variables hagan una diferencia, necesitamos crear la funcionalidad que detecte las solicitudes de API.

Esto se puede hacer usando la acción de WordPress parse_request.

En define_public_hooks de Wp_License_Manager, vincula una función a la acción:

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

Ahora, todas las solicitudes futuras a esta instalación de WordPress pasarán por esta función antes de pasar a las propias funciones de gestión de solicitudes de WordPress. Eso significa que necesitamos mantener la función de controlador ligera: Comprueba si se ha establecido __wp_license_api y si no es así, regresa rápidamente y deja que WordPress continúe. En caso afirmativo, está bien hacer una gestión más compleja, esta es nuestra solicitud y nos encargaremos.

La función, sniff_api_requests, entra en Wp_License_Manager_Public:

1
/**

2
 * A sniffer function that looks for API calls and passes them to our API handler.

3
 */
4
public function sniff_api_requests() {
5
    global $wp;
6
    if ( isset( $wp->query_vars['__wp_license_api'] ) ) {
7
        $action = $wp->query_vars['__wp_license_api'];
8
        $this->api->handle_request( $action, $wp->query_vars );
9
10
        exit;
11
    }
12
}

Línea 6: comprueba si la variable de consulta de acción de la API está presente. En caso afirmativo, esta es una llamada para que esta API la controle.

Líneas 7-8: pase la solicitud a la función de la clase API handle_request que se va a controlar. Crearemos esta función a continuación.

Línea 10: detiene la ejecución de la solicitud de WordPress una vez que hayamos manejado la llamada API. Esto es importante ya que de lo contrario, la ejecución continuaría hacia WordPress en sí y representaría una página de error 404 después de la salida de la API.

A continuación, añadamos la función handle_request.

Por ahora, la implementación es como un andamio que podemos usar para comprobar que nuestro framework está funcionando. He incluido marcadores de posición para las dos acciones que vamos a crear en este tutorial y una respuesta predeterminada que será enviada en caso de que un usuario use una acción de la API que no admitimos.

1
/**

2
 * The handler function that receives the API calls and passes them on to the

3
 * proper handlers.

4
 *

5
 * @param $action   string  The name of the action

6
 * @param $params   array   Request parameters

7
 */
8
public function handle_request( $action, $params ) {
9
    switch ( $action ) {
10
        case 'info':
11
            break;
12
13
        case 'get':
14
            break;
15
16
        default:
17
            $response = $this->error_response( 'No such API action' );
18
            break;
19
    }
20
21
    $this->send_response( $response );
22
}

Línea 17: $response es una matriz con la información que se debe pasar a la aplicación que llama a la API.

Línea 21: Imprime la respuesta como una cadena codificada en JSON.

Para que la función funcione, todavía tenemos que añadir las funciones auxiliares que utiliza: send_response y error_response.

En primer lugar, send_response:

1
/**

2
 * Prints out the JSON response for an API call.

3
 *

4
 * @param $response array   The response as associative array.

5
 */
6
private function send_response( $response ) {
7
    echo json_encode( $response );
8
}

Después error_response. Esta función crea una matriz con el mensaje de error especificado. Mediante el uso de una función para generar la matriz, podemos asegurarnos de que todos los mensajes de error se formatean de la misma manera y cambian fácilmente el formato si más adelante fuese necesario en el ciclo de vida del plugin.

Por ahora, decidí continuar con una versión básica con tan solo un mensaje de error dentro de la matriz:

1
/**

2
 * Generates and returns a simple error response. Used to make sure every error

3
 * message uses same formatting.

4
 *

5
 * @param $msg      string  The message to be included in the error response.

6
 * @return array    The error response as an array that can be passed to send_response.

7
 */
8
private function error_response( $msg ) {
9
    return array( 'error' => $msg );
10
}

Ahora, has creado la estructura dentro de la cual podemos empezar a crear las acciones de la API. Para probarlo, intenta llamar a una acción de API no existente, por ejemplo, http://<yoursite>/?__wp_license_api=dummy-action, en una ventana del explorador.

Deberías ver algo como esto:

Incluso aunque la API siga devolviendo solo un mensaje de error, la prueba muestra que la solicitud se dirige a la función de controlador correcta y no se mezcla con el resto del flujo de WordPress. Justo lo que queríamos.

Ahora, antes de empezar a añadir la funcionalidad de la API, hagamos que la URL de la API sea un poco más fácil de recordar.

Paso 4: Crear un formato de URL personalizado para la API

Aunque http://<yoursite>funciona /?__wp_license_api=<action> no es muy bonito, además expone un poco demasiado de los interiores de la implementación de la API en mi opinión. Por lo tanto, vamos a crear un formato de URL más agradable para usarlo en su lugar.

Como recordatorio desde el principio de esta sección, aquí está la URL que usaremos: http://<yoursite>/api/license-manager/v1/

Añadí v1 a la URL de la API sólo para que en el caso de que en algún momento nos encontrásemos soportando varias versiones diferentes de la API. Tener la información de la versión allí desde el principio hace que añadir nuevas versiones más adelante sea más fácil.

En primer lugar, para hacer que las URLs "bonitas" como esta funcionen, establece la opción Permalinks en los ajustes de WordPress a algo distinto del valor predeterminado (todas las demás opciones excepto "Predeterminado" son válidas).

A continuación, añadimos un filtro para crear una nueva regla de reescritura. Agrega el registro del filtro a define_public_hooks de Wp_License_Manager:

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

A continuación, crea la función (en Wp_License_Manager_Public):

1
/**

2
 * The permalink structure definition for API calls.

3
 */
4
public function add_api_endpoint_rules() {
5
    add_rewrite_rule( 'api/license-manager/v1/(info|get)/?',
6
        'index.php?__wp_license_api=$matches[1]', 'top' );
7
8
    // If this was the first time, flush rules

9
    if ( get_option( 'wp-license-manager-rewrite-rules-version' ) != '1.1' ) {
10
        flush_rewrite_rules();
11
        update_option( 'wp-license-manager-rewrite-rules-version', '1.1' );
12
    }
13
}

Línea 5: usando una expresión regular, define una nueva regla de reescritura de URL para enviar las solicitudes formateadas de acuerdo con lo que teníamos en mente a la URL que usamos en el paso anterior. (info-get) define los métodos de api que la API debería aceptar.

Líneas 9-12: guarda las reglas de reescritura para activarlas. flush_rewrite_rules es una llamada que consume recursos, así que he añadido una comprobación alrededor de ella para asegurarnos de que el vaciado solo se realiza cuando se han cambiado las reglas.

Ahora, completado este paso, cualquier solicitud realizada a http://<yoursite>/api/license-manager/v1/<action> se convierte en una solicitud para http://<yoursite>/?__wp_license_api=<action> Prueba esto con la misma acción ficticia del paso anterior: ahora deberías obtener el mismo resultado con ambas direcciones URL.

El framework de la API ya está en su lugar y podemos empezar a añadir funcionalidad en ella.

Paso 5: Crear la funcionalidad para verificar licencias

Las dos acciones de API que vamos a crear en este tutorial comienzan comprobando la licencia del usuario.

Al principio, escribí el código de verificación de licencia para ambas por separado, pero observando los dos controladores, pronto me di cuenta de que eran exactamente los mismos. Para mí, esto es una buena señal de que es hora de hacer algunas refactorizaciones.

Para ahorrar parte de tu tiempo, omitamos el paso en donde escribimos el mismo código dos veces y en su lugar vayamos directamente a la versión final y pongamos el código común en una función propia.

En Wp_License_Manager_API, crea una función denominada verify_license_and_execute. Esta función se utilizará como un paso adicional entre la función que creamos anteriormente y la gestión real de las solicitudes.

1
/**

2
 * Checks the parameters and verifies the license, then forwards the request to the

3
 * actual API request handlers.

4
 *

5
 * @param $action_function  callable    The function (or array with class and function) to call

6
 * @param $params           array       The WordPress request parameters.

7
 * @return array            API response.

8
 */
9
private function verify_license_and_execute( $action_function, $params ) {
10
    if ( ! isset( $params['p'] ) || ! isset( $params['e'] ) || ! isset( $params['l'] ) ) {
11
        return $this->error_response( 'Invalid request' );
12
    }
13
14
    $product_id = $params['p'];
15
    $email = $params['e'];
16
    $license_key = $params['l'];
17
18
    // Find product

19
    $posts = get_posts(
20
        array (
21
            'name' => $product_id,
22
            'post_type' => 'wplm_product',
23
            'post_status' => 'publish',
24
            'numberposts' => 1
25
        )
26
    );
27
28
    if ( ! isset( $posts[0] ) ) {
29
        return $this->error_response( 'Product not found.' );
30
    }
31
32
    // Verify license

33
    if ( ! $this->verify_license( $posts[0]->ID, $email, $license_key ) ) {
34
        return $this->error_response( 'Invalid license or license expired.' );
35
    }
36
37
    // Call the handler function

38
    return call_user_func_array( $action_function, array( $posts[0], $product_id, $email, $license_key ) );
39
}

Líneas 10-12: verifica que todos los parámetros relacionados con la licencia estén presentes. p es el slug del producto, e es la dirección de correo electrónico del usuario, y l es la clave de licencia.

Líneas 14-16: recoge y desinfecta los parámetros.

Líneas 19-26: busca la entrada que coincida con el ID del producto dado. Como mencioné anteriormente, el identificador del producto es en realidad el slug de la entrada del producto para que le resulte más fácil al usuario saber su valor adecuado.

Líneas 28-30: si no se encuentra el producto, devuelve un error.

Líneas 33-35: realiza la validación real de la clave de licencia. Crearemos esta función a continuación.

Línea 38: llama a la función responsable de controlar la acción de la API. Como idea de desarrollo para el futuro, este sería un estupendo lugar para un filtro que otros plugins podrían utilizar para reemplazar la función de controlador si fuese necesario...

A continuación, añadamos la función para verificar la licencia:

1
/**

2
 * Checks whether a license with the given parameters exists and is still valid.

3
 *

4
 * @param $product_id   int     The numeric ID of the product.

5
 * @param $email        string  The email address attached to the license.

6
 * @param $license_key  string  The license key.

7
 * @return bool                 true if license is valid. Otherwise false.

8
 */
9
private function verify_license( $product_id, $email, $license_key ) {
10
    $license = $this->find_license( $product_id, $email, $license_key );
11
    if ( ! $license ) {
12
        return false;
13
    }
14
15
    $valid_until = strtotime( $license['valid_until'] );
16
    if ( $license['valid_until'] != '0000-00-00 00:00:00' && time() > $valid_until ) {
17
        return false;
18
    }
19
20
    return true;
21
}

Líneas 10-13: busca la licencia en la base de datos (añadiremos esta función a continuación) utilizando los parámetros enviados. Si no se encuentra la licencia, no puede ser válida, por lo que podemos devolver false.

Líneas 15-18: si la licencia existe, comprueba si ha expirado. En la primera parte del tutorial, definimos que 0000-00-00 00:00:00 se utiliza para una licencia que nunca expira.

Línea 20: pasadas todas las comprobaciones, la licencia es válida.

Por último, para completar el código de validación de licencia, necesitamos la función que recupera una licencia de la base de datos.

Añade la función, find_license:

1
/**

2
 * Looks up a license that matches the given parameters.

3
 *

4
 * @param $product_id   int     The numeric ID of the product.

5
 * @param $email        string  The email address attached to the license.

6
 * @param $license_key  string  The license key

7
 * @return mixed                The license data if found. Otherwise false.

8
 */
9
private function find_license( $product_id, $email, $license_key ) {
10
    global $wpdb;
11
    $table_name = $wpdb->prefix . 'product_licenses';
12
13
    $licenses = $wpdb->get_results(
14
        $wpdb->prepare( "SELECT * FROM $table_name WHERE product_id = %d AND email = '%s' AND license_key = '%s'",
15
            $product_id, $email, $license_key ), ARRAY_A );
16
17
    if ( count( $licenses ) > 0 ) {
18
        return $licenses[0];
19
    }
20
21
    return false;
22
}

En esta función, realizamos una consulta SQL para buscar una licencia con exactamente los mismos parámetros enviados, ID de producto, correo electrónico y clave de licencia. De esta manera, no necesitamos realizar ningún análisis extra de la respuesta a excepción de comprobar si existe un resultado o no.

Si se encuentra una licencia, la función la devuelve. De lo contrario, devuelve false.

Paso 6: Añadir la acción API de información del producto

A continuación, añadamos la primera de las dos acciones, info. En la función handle_request que creamos hace un rato, rellena la rama case para la acción:

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

La línea que hemos añadido llama a nuestra función verify_license_and_execute con dos parámetros:

  • El primer parámetro debe resultarte familiar por las acciones y filtros de WordPress que hemos estado creando a lo largo de esta serie de tutoriales. Pasa el nombre de la función que queremos utilizar para manejar la acción una vez que se ha verificado la licencia. Dado que la función está dentro de una clase, necesitamos pasar una matriz en lugar de solo el nombre de la función.
  • El segundo parámetro es la lista de todos los parámetros de solicitud enviados a WordPress.

Ya hemos visto lo que sucede dentro de verify_license_and_execute, así que ahora, vamos a añadir la función product_info. Esta función recopilará información sobre el producto y la devolverá en forma de matriz.

Como probablemente recuerdes de la primera parte del tutorial, los productos se almacenan en el plugin administrador de licencias como un tipo de entrada personalizada con un meta box para introducir información adicional del producto. En esta función, aprovecharemos esos datos y rellenaremos la matriz $response con información sobre el producto solicitado:

1
/**
2
 * The handler for the "info" request. Checks the user's license information and
3
 * returns information about the product (latest version, name, update url).
4
 *
5
 * @param   $product        WP_Post   The product object
6
 * @param   $product_id     string    The product id (slug)
7
 * @param   $email          string    The email address associated with the license
8
 * @param   $license_key    string  The license key associated with the license
9
 *
10
 * @return  array           The API response as an array.
11
 */
12
private function product_info( $product, $product_id, $email, $license_key ) {
13
    // Collect all the metadata we have and return it to the caller
14
    $meta = get_post_meta( $product->ID, 'wp_license_manager_product_meta', true );
15
16
    $version = isset( $meta['version'] ) ? $meta['version'] : '';        
17
    $tested = isset( $meta['tested'] ) ? $meta['tested'] : '';
18
    $last_updated = isset( $meta['updated'] ) ? $meta['updated'] : '';
19
    $author = isset( $meta['author'] ) ? $meta['author'] : '';
20
    $banner_low = isset( $meta['banner_low'] ) ? $meta['banner_low'] : '';
21
    $banner_high = isset( $meta['banner_high'] ) ? $meta['banner_high'] : '';
22
23
    return array(
24
        'name' => $product->post_title,
25
        'description' => $product->post_content,
26
        'version' => $version,
27
        'tested' => $tested,
28
        'author' => $author,
29
        'last_updated' => $last_updated,
30
        'banner_low' => $banner_low,
31
        'banner_high' => $banner_high,
32
        "package_url" => home_url( '/api/license-manager/v1/get?p=' . $product_id . '&e=' . $email . '&l=' . urlencode( $license_key ) ),
33
        "description_url" => get_permalink( $product->ID ) . '#v=' . $version
34
    );
35
}

Línea 14: recuperar información del producto de los metadatos de la entrada.

Líneas 16-21: recopilar los datos de los campos de metadatos.

Líneas 23-34: construye la respuesta. Sobre todo se trata de colocar los datos de las líneas 16-21 en la matriz. Una línea, sin embargo, la línea 32, merece un poco más de atención.

Esto es para el sistema de actualización de WordPress que usaremos en el siguiente tutorial de la serie. Para facilitar las cosas para ese paso, la acción info devuelve una dirección URL que apunta a la otra acción de la API, get, con todos los parámetros necesarios.

Paso 7: Prueba la acción de la API

Ahora has creado la primera de nuestras dos funciones de API. Si lo deseas, puedes comprobar la funcionalidad llamando al método API en un explorador web. Simplemente, asegúrate de configurar los datos correctamente antes de realizar la llamada: para que la acción de la API funcione debe existir un producto y una licencia en el sistema.

Otra trampa es que tendrás que codificar los parámetros de correo electrónico y clave de licencia antes de colocarlos en la URL, ya que contienen caracteres especiales.

La salida tendrá un aspecto similar a lo siguiente, dependiendo de lo que hayas guardado como datos de tu producto:

Almacenamiento de archivos en Amazon S3

Con la primera mitad de la API completada, es el momento de buscar en la segunda función de API, get. Esta función de API comprobará la clave de licencia utilizando el mismo método que usamos con info y después devolverá el archivo descargable.

Para que la acción de la API tenga sentido, es importante que no se pueda acceder a la descarga sin una clave de licencia válida simplemente omitiendo la API y yendo directamente al propio archivo. El sencillo servicio Storage Service (S3) de Amazon nos ofrece esta funcionalidad de forma fácil y asequible (casi gratuita para la mayoría de los usuarios) al mismo tiempo que hace que la descarga sea fiable.

En los siguientes pasos, veremos primero cómo cargar los archivos en Amazon S3 mediante el panel online de AWS (Amazon Web Services). A continuación, una vez cargado el archivo, añadiremos la funcionalidad de descarga a nuestra API.

Si ya estás utilizando Amazon S3 y conoces el camino, puedes pasar directamente a la siguiente sección y empezar a implementar la acción get. Si eres nuevo en S3, sigue leyendo.

Paso 1: Crear una cuenta

La creación de una cuenta de AWS está bien documentada, pero consta de muchos pasos, así que vamos a revisarlos rápidamente.

Primero, dirígete a aws.amazon.com y haz clic sobre el botón "Cree una cuenta de AWS" situado en la esquina superior derecha.

En la siguiente pantalla, tiene la opción de crear una nueva cuenta de Amazon o utilizar una ya existente que hayas utilizado para otros servicios de Amazon (por ejemplo, su propia tienda online):

La elección depende de ti: Si decides crear una nueva cuenta, eso es lo que vamos a hacer a continuación. Si utilizas tu cuenta preexistente, salta directamente al proceso de cinco pasos de la configuración de tu cuenta de AWS, pulsando el enlace "Iniciar sesión en una cuenta de AWS existente". Durante el registro, se te pedirá que introduzcas la información de tu tarjeta de crédito, para los usos más comunes que no tendrás que preocuparte por el precio.

Después de introducir la información de tu tarjeta de crédito, antes de que tu cuenta sea aprobada, tendrás que verificar tu número de teléfono. Amazon realiza automáticamente una llamada a tu número de teléfono y te pide que introduzcas el código de cuatro dígitos que se muestra en la pantalla. Cuando lo intenté de nuevo mientras escribía este tutorial, tuve problemas al introducir el código con el teclado del iPhone. Sin embargo, decir los números en voz alta uno por uno funcionó perfectamente.

El último paso consiste en elegir un plan de soporte. Te sugiero que, por ahora, elijas la opción gratuita. Realmente no hay muchas necesidades de soporte para S3 y podrás reconsiderar la opción más adelante en caso de que decidas utilizar características más avanzadas.

Ahora que has creado y activado tu cuenta, añadiremos un archivo.

Paso 2: Crear el bucket

Inicia la consola de administración de AWS y elige S3 en la pantalla de opciones:

En el administrador de AWS existen un montón de características disponibles, pero por ahora puedes ignorar el resto, son cosas avanzadas que la mayoría de los desarrolladores de WordPress nunca van a necesitar: en este momento yo solo uso S3 y EC2.

Ahora que estás en la consola de administración de S3, comienza por crear un bucket. Un bucket es un contenedor de nivel superior para los archivos de S3 que pueden contener carpetas y archivos. A continuación se muestra cómo describe el concepto la documentación de AWS:

Cada objeto que almacenes en Amazon S3 reside en un bucket. Puedes utilizar buckets para agrupar objetos relacionados de la misma manera que utilizas un directorio para agrupar archivos en un sistema de archivos. Los buckets tienen propiedades, como permisos de acceso y estado de control de versiones, y puedes especificar la región en la que deseas que residan.

Asigna a tu bucket un nombre que consista en letras minúsculas (a-z), números, guiones y puntos (para requisitos más detallados, consulta la documentación) y haz clic en "Crear".

Si lo deseas, también puedes elegir una Región para determinar dónde almacenar tus archivos. Sin embargo, está perfectamente bien dejar la configuración a su opción predeterminada.

Una vez hayas creado el bucket, verás una vista del explorador de archivos de ese bucket que acabas de crear:

En esta pantalla, puedes crear tantos buckets nuevos como desees. Por ahora, con uno tendremos suficiente. Haz clic en el nombre del bucket para añadir archivos en él.

Paso 3: Cargar un archivo

El bucket que acabamos de crear contendrá las descargas de instalación del administrador de licencias. No vamos a crear los temas y plugins descargables hasta la tercera y última parte del tutorial, así que para las pruebas, sólo tienes que crear un archivo zip con algo en él (por ejemplo, un archivo de texto).

Haz clic en el botón Cargar situado en la esquina superior izquierda de la pantalla de administración de S3. Aparecerá una pantalla de carga:

Elige Añadir archivos para seleccionar los archivos que deseas cargar. A continuación, haz clic en Iniciar carga.

Podríamos usar Establecer detalles para establecer los permisos de archivo, pero como no queremos que la descarga esté disponible públicamente, las opciones predeterminadas son justo lo que queremos.

Una vez cargado el archivo, lo verás en tu bucket:

Cierra la ventana de transferencia haciendo clic en la cruz de la esquina superior derecha. A continuación, selecciona el archivo y elige Propiedades en el menú superior derecho:

En esta pantalla, todavía puedes modificar los permisos del archivo y otros ajustes. También encontrarás la URL del archivo en la información. Copia el enlace y ábrelo en un navegador para probar lo que sucede cuando intentas descargar el archivo.

Si todo está correctamente configurado, no podrás hacerlo: incluso aunque un visitante aleatorio conociese la URL exacta de tu archivo de descarga, no podrá descargarla omitiendo la comprobación de licencia en la llamada a la API.

Si ves un error como este, significa que todo está configurado correctamente y podrás empezar a crear el código para descargar este archivo:

Como último paso antes de escribir un poco más de código, crea un producto en WordPress usando nuestro plugin de administración de licencias (o actualiza uno existente) y añade la información del archivo de descarga:

Servir la descarga desde S3

Ahora que has cargado correctamente un archivo en Amazon S3, hagamos que la API del administrador de licencias devuelva el archivo cuando le sea solicitado, pero solo si la licencia del usuario es válida.

Para ello, rellenaremos el marcador de posición de acción de la API que añadimos a nuestra función de control de API handle_request anteriormente en este tutorial. La acción comprobará primero la licencia mediante verify_license_and_execute y, a continuación, utilizará la biblioteca oficial de AWS de Amazon para crear y devolver un enlace de descarga firmado al archivo descargable del producto.

Paso 1: Descargar e incluir la biblioteca de AWS

Para empezar, descargue primero AWS SDK para PHP desde Amazon.

Hay muchas maneras de incluir el SDK en tu proyecto, pero yo te sugiero que en este proyecto utilices la descarga zip, aunque ya sea un poco anticuado: el SDK contiene funcionalidad para usar todas las características de AWS, como viste antes, hay muchas de ellas, y solo estamos usando una, S3. Descargar la versión zip e incluirla en tu proyecto manualmente te da la oportunidad de eliminar todos los archivos adicionales que solo ocupan espacio en la descarga del plugin.

Una vez hayas descargado el SDK de AWS, crea un directorio denominado lib en el directorio wp-license-manager. Dentro de él, crea un directorio denominado aws que albergue el contenido del paquete zip del SDK.

Ahora, si lo deseas, puedes reducir el contenido del directorio, dejando solo los directorios Common y S3 dentro de Aws. Si el tamaño del plugin no es un problema, también puedes omitir este paso y dejar el SDK como está.

A continuación, conecta el SDK de AWS al plugin WP License Manager a nivel de código.

El SDK utiliza namespaces, una característica PHP que se añadió solo en PHP 5.3. El requisito oficial de PHP para WordPress es 5.2.6, por lo que el uso del SDK no funcionará para todos los usuarios de WordPress.

Una manera de superar esta limitación es usar una biblioteca de S3 de terceros o escribirla tú mismo. Sin embargo, como el uso del SDK oficial es, en general, la forma más recomendada, esta vez vamos a seguir esa ruta. En la versión publicada del plugin en el repositorio de plugins de WordPress, he incluido tanto la versión del SDK de AWS como una versión alternativa utilizando esta biblioteca independiente de S3. El plugin elige el que debe usar adecuadamente en tiempo de ejecución, basándose en la versión PHP del sistema en el que se ejecute el plugin.

Sin embargo, en este tutorial mantendremos las cosas un poco más simples y solo tendremos una versión del SDK de AWS.

Crea una nueva clase, Wp_License_Manager_S3 y colócala en el directorio includes del plugin:

1
<?php
2
/**

3
 * A wrapper for our Amazon S3 API actions.

4
 *

5
 * @package    Wp_License_Manager

6
 * @subpackage Wp_License_Manager/includes

7
 * @author     Jarkko Laine <jarkko@jarkkolaine.com>

8
 */
9
class Wp_License_Manager_S3 {
10
11
}

Para incluir la clase en el proyecto, agrega las siguientes líneas a la clase de la función load_dependencies de Wp_License_Manager:

1
/**

2
 * A wrapper class for our Amazon S3 connectivity.

3
 */
4
require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-wp-license-manager-s3.php';
5
require_once plugin_dir_path( dirname( __FILE__ ) ) . 'lib/aws/aws-autoloader.php';

La primera línea require incluye la clase contenedora creada anteriormente y la segunda incluye el cargador automático del SDK de AWS, un fragmento de código responsable de solicitar los archivos de origen del SDK según sea necesario.

Paso 2: Crear la función para generar el enlace de descarga

Dentro de la clase Wp_License_Manager_S3 que acabas de crear, añade la siguiente función para generar un enlace de descarga firmado que se pueda utilizar para descargar el archivo protegido de Amazon S3:

1
/**

2
 * Returns a signed Amazon S3 download URL.

3
 *

4
 * @param $bucket       string  Bucket name

5
 * @param $file_name    string  File name (URI)

6
 * @return string       The signed download URL

7
 */
8
public static function get_s3_url( $bucket, $file_name ) {
9
    $options = get_option( 'wp-license-manager-settings' );
10
11
    $s3_client = Aws\S3\S3Client::factory(
12
        array(
13
            'key'    => $options['aws_key'],
14
            'secret' => $options['aws_secret']
15
        )
16
    );
17
18
    return $s3_client->getObjectUrl( $bucket, $file_name, '+10 minutes' );
19
}

Vamos revisar la función para ver lo que hace el ID:

Líneas 11-16: crea una instancia de clase de cliente de Amazon S3 para crear el enlace de descarga. El SDK requiere credenciales de seguridad de AWS, que almacenaremos en las opciones de WordPress, utilizando una pantalla de configuración que crearemos en el siguiente paso.

Línea 18: utiliza el SDK para generar un enlace de descarga para el bucket y el nombre de archivo especificados. El enlace de descarga será válido durante 10 minutos a partir de este momento para que el usuario tenga tiempo suficiente para iniciar la descarga.

Paso 3: Crear una página de ajustes para la configuración de AWS

Como habrás notado en el paso anterior, necesitamos almacenar una clave de API de AWS y un secreto en las opciones de WordPress de forma que el plugin pueda ser configurado para utilizarse con diferentes cuentas de AWS.

Para ello, vamos a usar la API de ajustes. La creación de una página de configuración mediante la API de ajustes consta de tres pasos: inicializar los campos de ajustes, añadir la página de ajustes y definir funciones para representar las diferentes partes de esta página.

Todo comienza añadiendo dos acciones, una para admin_init y otra para admin_menu:

1
// Plugin settings menu$this->loader->add_action( 'admin_init', $plugin_admin, 'add_plugin_settings_fields');$this->loader->add_action( 'admin_menu', $plugin_admin, 'add_plugin_settings_page');

Ahora que has añadido las acciones, crea las funciones que las controlan. Ambas deben añadirse a la clase Wp_License_Manager_Admin.

En primer lugar, añade la función add_plugin_settings_fields que inicializará una sección de ajustes y después dos campos de configuración que irán dentro de ella:

1
/**

2
 * Creates the settings fields for the plugin options page.

3
 */
4
public function add_plugin_settings_fields() {
5
    $settings_group_id = 'wp-license-manager-settings-group';
6
    $aws_settings_section_id = 'wp-license-manager-settings-section-aws';
7
    $settings_field_id = 'wp-license-manager-settings';
8
9
    register_setting( $settings_group_id, $settings_field_id );
10
11
    add_settings_section(
12
        $aws_settings_section_id,
13
        __( 'Amazon Web Services', $this->plugin_name ),
14
        array( $this, 'render_aws_settings_section' ),
15
        $settings_group_id
16
    );
17
18
    add_settings_field(
19
        'aws-key',
20
        __( 'AWS public key', $this->plugin_name ),
21
        array( $this, 'render_aws_key_settings_field' ),
22
        $settings_group_id,
23
        $aws_settings_section_id
24
    );
25
26
    add_settings_field(
27
        'aws-secret',
28
        __( 'AWS secret', $this->plugin_name ),
29
        array( $this, 'render_aws_secret_settings_field' ),
30
        $settings_group_id,
31
        $aws_settings_section_id
32
    );
33
}

Líneas 5-7: inicializa tres variables con los nombres del campo de ajuste y los nombres de sección. Esto nos ayuda a asegurarnos de que no insertamos errores tipográficos...

Línea 9: registra el elemento de configuración 'wp-license-manager-settings'. Será una matriz en la que se almacenarán los dos ajustes, aws-key y aws-secret.

Líneas 11-16: añade la sección de configuración que contendrá ambos campos de ajustes.

Líneas 18-32: añade nuestros dos campos de ajustes (aws-key y aws-secret), colocándolos en la sección de configuración creada en las líneas 11-16.

Ahora, añade la segunda función, add_plugin_settings_page. Esta función creará la página de configuración como un elemento secundario del menú principal de Ajustes, utilizando la función add_options_page.

1
/**

2
 * Adds an options page for plugin settings.

3
 */
4
public function add_plugin_settings_page() {
5
    add_options_page(
6
        __( 'License Manager', $this->plugin_name ),
7
        __( 'License Manager Settings', $this->plugin_name ),
8
        'manage_options',
9
        'wp-license-settings',
10
        array( $this, 'render_settings_page' )
11
    );
12
}

Cada uno de los elementos de las anteriores funciones de la API de ajustes (la página de configuración, la sección de configuración y ambos campos de ajustes) toman una función de representación como parámetro.

Para completar la construcción de la página de configuración, crea esas funciones de visualización:

1
/**

2
 * Renders the plugin's options page.

3
 */
4
public function render_settings_page() {
5
    $settings_group_id = 'wp-license-manager-settings-group';
6
    require plugin_dir_path( dirname( __FILE__ ) ) . 'admin/partials/settings_page.php';
7
}
8
9
/**

10
 * Renders the description for the AWS settings section.

11
 */
12
public function render_aws_settings_section() {
13
    // We use a partial here to make it easier to add more complex instructions

14
    require plugin_dir_path( dirname( __FILE__ ) ) . 'admin/partials/aws_settings_group_instructions.php';
15
}
16
17
/**

18
 * Renders the settings field for the AWS key.

19
 */
20
public function render_aws_key_settings_field() {
21
    $settings_field_id = 'wp-license-manager-settings';
22
    $options = get_option( $settings_field_id );
23
    ?>
24
        <input type='text' name='<?php echo $settings_field_id; ?>[aws_key]' value='<?php echo $options['aws_key']; ?>' class='regular-text'>
25
    <?php
26
}
27
28
/**

29
 * Renders the settings field for the AWS secret.

30
 */
31
public function render_aws_secret_settings_field() {
32
    $settings_field_id = 'wp-license-manager-settings';
33
    $options = get_option( $settings_field_id );
34
    ?>
35
       <input type='text' name='<?php echo $settings_field_id; ?>[aws_secret]' value='<?php echo $options['aws_secret']; ?>' class='regular-text'>
36
    <?php
37
}

Las funciones de representación de campos de ajustes render_aws_key_settings_field y render_aws_secret_settings_field son básicamente una copia una de otra: primero, recuperan las opciones del plugin y luego imprimen un campo de texto con el nombre y el valor actual de la configuración.

Las funciones para representar la página de configuración (render_settings_page) y la sección de configuración (render_aws_settings_section) son similares, pero en lugar de imprimir el HTML allí mismo en la función, utilizan plantillas HTML independientes. Esta no es de ninguna manera la única forma correcta de hacerlo, yo elegí este enfoque porque estas funciones renderizan algo más HTML, y podría ser necesario extenderlo en algún momento futuro.

Esto es lo que entra en las plantillas. En primer lugar, admin/partials/settings_page.php, el parcial para la página de configuración:

1
<?php
2
/**

3
 * The view for the plugin's options page.

4
 *

5
 * @package    Wp_License_Manager

6
 * @subpackage Wp_License_Manager/admin/partials

7
 */
8
?>
9
10
<div class="wrap">
11
    <div id="icon-edit" class="icon32 icon32-posts-post"></div>
12
13
    <h2>
14
        <?php _e( 'WP License Manager Settings', $this->plugin_name ); ?>
15
    </h2>
16
17
    <form action='options.php' method='post'>
18
        <?php
19
        settings_fields( $settings_group_id );
20
        do_settings_sections( $settings_group_id );
21
        submit_button();
22
        ?>
23
    </form>
24
</div>

La parte interesante está al final del archivo PHP, donde creamos el formulario e insertamos los campos de ajustes, las secciones y el botón de envío.

El parcial para la sección de configuración (admin/partials/aws_settings_group_instructions.php) está casi vacío actualmente, imprimiendo una breve línea de instrucciones:

1
<?php
2
/**

3
 * The view for the AWS settings section's description on the plugin's settings page.

4
 *

5
 * @package    Wp_License_Manager

6
 * @subpackage Wp_License_Manager/admin/partials

7
 */
8
?>
9
10
<?php _e( 'Enter your AWS credentials below.', $this->plugin_name ); ?>

Ahora, hemos creado la página de configuración. Visita el panel de WordPress para verlo en acción:

Paso 4: Obtener una clave de API de AWS y un secreto

Para probar la funcionalidad, aún tienes que recuperar una clave de API y un secreto de AWS y almacenarlos en los campos de configuración que acabas de crear.

Para ello, vuelve a la consola de administración de AWS y haz clic en su nombre en el menú superior derecho. En el menú desplegable que se muestra, elige las Credenciales de seguridad.

A continuación, se te mostrará la siguiente ventana emergente:

Selecciona la opción Empieza con los usuarios IAM. De esta manera, en lugar de utilizar tus credenciales de seguridad global en todas partes, puedes crear nombres de usuario (y credenciales de acceso) independientes para cualquier uso distinto de AWS que puedas tener.

A continuación, crea un usuario para utilizarlo en tu instalación de WP License Manager.

En primer lugar, haz clic sobre Crear usuarios en la parte superior de la página. A continuación, en la siguiente página, escribe un nombre de usuario que tenga sentido para ti (yo he utilizado simplemente test_user, pero probablemente administrador de licencias sea una mejor opción, o algo que te indique para qué se utiliza el usuario) y haz clic en Crear.

Se crea el usuario y, en la página siguiente, verás tus credenciales de seguridad. Cópialos y colócalos en los campos de ajustes que creamos en el paso anterior.

Paso 5: Crear la función de la API

Ahora, hemos creado todas las piezas necesarias para la acción get de la API. Vamos a ponerlas juntas y a crear la acción en sí.

Primero, rellena el bloque switch..case que dejamos para la acción get en la función handle_request dentro de Wp_License_Manager_API:

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

La función verify_license_and_execute ya está creada, así que lo que falta es añadir la función de controlador, get_product.

Aquí está la función:

1
/**

2
 * The handler for the "get" request. Redirects to the file download.

3
 *

4
 * @param   $product    WP_Post     The product object

5
 */
6
private function get_product( $product, $product_id, $email, $license_key ) {
7
    // Get the AWS data from post meta fields

8
    $meta = get_post_meta( $product->ID, 'wp_license_manager_product_meta', true );
9
    $bucket = isset ( $meta['file_bucket'] ) ? $meta['file_bucket'] : '';
10
    $file_name = isset ( $meta['file_name'] ) ? $meta['file_name'] : '';
11
12
    if ( $bucket == '' || $file_name == '' ) {
13
        // No file set, return error

14
        return $this->error_response( 'No download defined for product.' );
15
    }
16
17
    // Use the AWS API to set up the download

18
    // This API method is called directly by WordPress so we need to adhere to its

19
    // requirements and skip the JSON. WordPress expects to receive a ZIP file...

20
21
    $s3_url = Wp_License_Manager_S3::get_s3_url( $bucket, $file_name );
22
    wp_redirect( $s3_url, 302 );
23
}

Vamos a ir a través de la función para ver qué hace:

Líneas 7-10: lee la configuración del bucket y del nombre de archivo de los metadatos del producto.

Líneas 12-15: si no se han establecido metadatos, devuelve una respuesta de error.

Línea 21: utiliza el SDK de AWS para crear una URL firmada para el archivo descargable del producto solicitado.

Línea 22: redirige a la URL de descarga firmada en S3. Como veremos en la siguiente parte del paso en el tutorial, WordPress espera que esta solicitud devuelva el archivo real por lo que tenemos que desviar aquí un poco nuestra API basada en JSON y simplemente hacer lo que funcione mejor para WordPress.

Ya has terminado de crear el plugin de administración de licencias. Si lo deseas, puedes probar la descarga de la misma manera que antes probamos la solicitud de información para ver si todo está funcionando y en lugar de mostrar el error que vimos al acceder directamente al archivo en S3, tu navegador descarga ahora el archivo zip que cargaste en S3.

Conclusión

Una vez terminada la API, hemos completado el plugin de administración de licencias. Aunque hay muchas características que podrían ser añadidas, la versión actual es totalmente funcional y se puede utilizar para gestionar correctamente las licencias.

En la siguiente, y última, parte de esta serie de tutoriales, vamos a ponerlo todo junto y a hacer que un tema y un plugin de WordPress utilice nuestro servidor de administración de licencias para comprobar si hay actualizaciones.