Distribuye tus Plugins en GitHub con actualizaciones automáticas
() translation by (you can also view the original English article)
WordPress ha hecho que sea conveniente implementar plugins para el público. Simplemente coloca tu plugin en el repositorio de complementos de WordPress y las personas pueden encontrarlo fácilmente desde su tablero.
Probablemente una de las mejores características del repositorio es que facilita la actualización de complementos desde el tablero. Las notificaciones de actualización se muestran dentro del tablero, y realizar una actualización es tan fácil como hacer clic en el botón Actualizar ahora.
Pero, ¿qué tal si quisieras alojar el complemento por tu cuenta? La práctica función de actualización no estará disponible si alojas tu complemento fuera del repositorio de complementos de WordPress. Tampoco se notificaría a tus usuarios de una actualización disponible.
Este artículo te enseñará que con un poco de codificación creativa, puedes alojar tus propios complementos de WordPress en GitHub mientras conservas la función de actualización automática.
¿Por qué alojar tu complemento en GitHub?
Puede haber varias razones por las que desearías alojar tu complemento fuera del repositorio de complementos de WordPress. Una de las principales restricciones que da el repositorio es que se le requiere licenciar tu complemento como GPLv2. No me detendré en los detalles de las licencias, pero en pocas palabras significa que cualquiera puede compartir tu trabajo. Entonces, si deseas vender tu complemento, alojarlo en un repositorio privado de GitHub es ahora una opción que puedes considerar.
Sin embargo, debido a la forma en que se creó WordPress, alojar tu complemento en GitHub deshabilitaría las actualizaciones automáticas para nuestro complemento. Para entender por qué este es el caso, tenemos que entender cómo WordPress maneja las actualizaciones de complementos.
Cómo WordPress actualiza los complementos
Primero, investiguemos cómo WordPress realiza actualizaciones de complementos para comprender mejor por qué los complementos autohospedados no pueden tener actualizaciones automáticas
WordPress revisa periódicamente el repositorio de WordPress en busca de actualizaciones de tus complementos instalados. Recibe un montón de información sobre cada complemento, como su última versión y la URL del paquete del complemento. Esto es lo que generalmente vemos en la página de administración del complemento cuando se nos notifica una actualización:



Cuando hacemos clic en el enlace Ver detalles de la versión x.x, WordPress realiza otra búsqueda en el repositorio de complementos. Esta vez obtiene información más detallada sobre el complemento, como su descripción, registro de cambios, en qué versión de WordPress se ha probado y mucho más. Esta información se nos muestra en una caja de luz:



Por último, cuando se hace clic en el enlace Actualizar ahora, se descarga e instala el paquete de complemento actualizado.
Entonces, ¿por qué las actualizaciones automáticas no funcionan para los complementos autohospedados? ¡Es porque WordPress intenta encontrarlo en el repositorio de plugins de WordPress y no puede encontrarlo allí!
El plan de juego
Nuestro plan es habilitar actualizaciones automáticas con nuestro complemento alojado en GitHub.
Aquí hay una lista de lo que debemos hacer para que funcione:
- Necesitamos una forma de implementar actualizaciones de complementos en GitHub.
- Deberíamos mostrar notificaciones de actualización de la versión del complemento,
- Sería bueno mostrar los detalles del complemento cuando se hace clic en el enlace Ver detalles de la versión x.x.
- También queremos instalar con éxito la actualización del complemento cuando se hace clic en el enlace Actualizar ahora.
Cómo ejecutar el plan
Utilizaremos algunos filtros de WordPress para que podamos implementar nuestro plan de juego. Estos son:
-
pre_set_site_transient_update_plugins
. Se llama a este filtro cuando WordPress intenta buscar actualizaciones de complementos. -
plugins_api
. Este se usa cuando WordPress muestra los detalles de actualización del complemento. -
upgrader_post_install.
Por último, se llama después de que un complemento se haya instalado correctamente.
Vamos a conectar estos filtros y luego introducir nuestros datos en los resultados para hacer que WordPress piense que nuestro complemento está en el repositorio de complementos de WordPress. Los datos que ingresaremos provendrán de nuestro repositorio de GitHub y deben imitar los datos proporcionados por el repositorio de complementos.
Configurando el Proyecto GitHub
Antes de continuar con la codificación, hablemos primero sobre GitHub y cómo lo usaremos para proporcionar los datos que necesitamos para alimentar WordPress.
Necesitarás un repositorio GitHub privado o público. Tu repositorio debe contener todos los archivos del complemento y no una copia comprimida de tu complemento.
Usaremos una característica genial de GitHub llamada Releases.



Lo bueno de las versiones es que obtiene la base de código actual en el repositorio y crea un archivo zip descargable para cada versión específica. Podemos decirle a WordPress que descargue este archivo zip al actualizar nuestro complemento.
Otro aspecto positivo de las versiones es que podemos incluir los detalles de actualización de nuestro complemento en las notas de la versión. Luego podemos analizar esto y mostrarlo en la caja de luz que muestra WordPress para obtener detalles de actualización del complemento. Podemos ir más allá e incluso permitir el markdown de GitHub para nuestro registro de cambios.



Cuando llegue el momento de implementar una actualización de nuestro complemento, sigue el formato en la imagen de arriba cuando estés creando una nueva versión:
- Tag Name: versión del complemento (solo el número)
- Release notes: la descripción de la actualización
Crear nuestra clase de actualización
¡Ahora es el momento de codificar nuestro complemento!
Primero, creamos el punto de partida para nuestra clase:
1 |
class BFIGitHubPluginUpdater { |
2 |
|
3 |
private $slug; // plugin slug |
4 |
private $pluginData; // plugin data |
5 |
private $username; // GitHub username |
6 |
private $repo; // GitHub repo name |
7 |
private $pluginFile; // __FILE__ of our plugin |
8 |
private $githubAPIResult; // holds data from GitHub |
9 |
private $accessToken; // GitHub private repo token |
10 |
|
11 |
function __construct( $pluginFile, $gitHubUsername, $gitHubProjectName, $accessToken = '' ) { |
12 |
add_filter( "pre_set_site_transient_update_plugins", array( $this, "setTransitent" ) ); |
13 |
add_filter( "plugins_api", array( $this, "setPluginInfo" ), 10, 3 ); |
14 |
add_filter( "upgrader_post_install", array( $this, "postInstall" ), 10, 3 ); |
15 |
|
16 |
$this->pluginFile = $pluginFile; |
17 |
$this->username = $gitHubUsername; |
18 |
$this->repo = $gitHubProjectName; |
19 |
$this->accessToken = $accessToken; |
20 |
}
|
21 |
|
22 |
// Get information regarding our plugin from WordPress
|
23 |
private function initPluginData() { |
24 |
// code here
|
25 |
}
|
26 |
|
27 |
// Get information regarding our plugin from GitHub
|
28 |
private function getRepoReleaseInfo() { |
29 |
// code here
|
30 |
}
|
31 |
|
32 |
// Push in plugin version information to get the update notification
|
33 |
public function setTransitent( $transient ) { |
34 |
// code here
|
35 |
return $transient; |
36 |
}
|
37 |
|
38 |
// Push in plugin version information to display in the details lightbox
|
39 |
public function setPluginInfo( $false, $action, $response ) { |
40 |
// code ehre
|
41 |
return $response; |
42 |
}
|
43 |
|
44 |
// Perform additional actions to successfully install our plugin
|
45 |
public function postInstall( $true, $hook_extra, $result ) { |
46 |
// code here
|
47 |
return $result; |
48 |
}
|
49 |
}
|
Esta es la estructura de clases que vamos a usar. Definimos principalmente todas las funciones que vamos a utilizar y creamos nuestros ganchos de filtro. Esta clase no hace nada por el momento, excepto para asignar valores a las propiedades de la clase.
Los argumentos del constructor
Para que nuestra clase se ejecute, necesitaremos algunos argumentos:
-
$pluginFile
: Llamaremos a esta clase desde nuestro script de complemento principal, esto debería tener el valor__FILE__
. Obtendremos detalles sobre nuestro complemento de esto más adelante. -
$gitHubUsername
: tu nombre de usuario de GitHub -
$gitHubProjectName
: el nombre del repositorio de GitHub -
$accessToken
: un token de acceso que nos permitirá ver los detalles de un repositorio privado de GitHub. Si tu proyecto está alojado en un repositorio público de GitHub, simplemente deja esto en blanco.
Ahora completemos las funciones de nuestra clase con algún código.
La función initPluginData
Esta es la función más simple en nuestra clase. Necesitaremos el slug de nuestro complemento y otra información durante el resto del script, por lo que estamos poniendo las llamadas necesarias en una función para mayor comodidad.
1 |
$this->slug = plugin_basename( $this->pluginFile ); |
2 |
$this->pluginData = get_plugin_data( $this->pluginFile ); |
La función getRepoReleaseInfo
Esta función se comunica con GitHub para obtener nuestra información de lanzamiento. Utilizaremos la API de GitHub para obtener detalles sobre nuestra última versión. Luego, almacena todo lo que obtengamos en nuestra propiedad githubAPIResult
para su procesamiento futuro.
WordPress llama dos veces al filtro pre_set_site_transient_update_plugins
, una vez cuando busca actualizaciones de complementos y luego otra después de obtener resultados. Como usaremos esta función en ese filtro, consultaremos la API de GitHub dos veces. Solo necesitamos obtener información de GitHub una vez:
1 |
// Only do this once
|
2 |
if ( ! empty( $this->githubAPIResult ) ) { |
3 |
return; |
4 |
}
|
A continuación, utilizaremos la API de GitHub para obtener información sobre nuestros lanzamientos:
1 |
// Query the GitHub API
|
2 |
$url = "https://api.github.com/repos/{$this->username}/{$this->repo}/releases"; |
3 |
|
4 |
// We need the access token for private repos
|
5 |
if ( ! empty( $this->accessToken ) ) { |
6 |
$url = add_query_arg( array( "access_token" => $this->accessToken ), $url ); |
7 |
}
|
8 |
|
9 |
// Get the results
|
10 |
$this->githubAPIResult = wp_remote_retrieve_body( wp_remote_get( $url ) ); |
11 |
if ( ! empty( $this->githubAPIResult ) ) { |
12 |
$this->githubAPIResult = @json_decode( $this->githubAPIResult ); |
13 |
}
|
Por último, solo conservaremos los datos de la última versión del complemento:
1 |
// Use only the latest release
|
2 |
if ( is_array( $this->githubAPIResult ) ) { |
3 |
$this->githubAPIResult = $this->githubAPIResult[0]; |
4 |
}
|
Ahora podemos obtener nuestros datos del complemento desde GitHub. Analizaremos estos datos en las siguientes funciones.
La función setTransitent
Se llama a esta función cuando WordPress busca actualizaciones de complementos. Nuestro trabajo aquí es utilizar nuestros datos de lanzamiento de GitHub para proporcionar información para nuestra actualización de complementos.
Lo primero que hacemos es verificar si WordPress ya ha buscado actualizaciones de complementos antes. Si es así, entonces no tenemos que ejecutar el resto de la función nuevamente.
1 |
// If we have checked the plugin data before, don't re-check
|
2 |
if ( empty( $transient->checked ) ) { |
3 |
return $transient; |
4 |
}
|
A continuación, obtendremos la información del complemento que vamos a utilizar:
1 |
// Get plugin & GitHub release information
|
2 |
$this->initPluginData(); |
3 |
$this->getRepoReleaseInfo(); |
Después de llamar a estas dos funciones, podemos verificar la versión de nuestro complemento local a partir de la versión (el nombre de la etiqueta) que se encuentra en GitHub. Podemos usar la conveniente función version_compare
de PHP para comparar los dos valores:
1 |
// Check the versions if we need to do an update
|
2 |
$doUpdate = version_compare( $this->githubAPIResult->tag_name, $transient->checked[$this->slug] ); |
Por último, hay una actualización de complemento disponible, debemos solicitar al administrador que muestre una notificación de actualización. Hacemos esto completando la variable $transient
con nuestra información actualizada del complemento.
1 |
// Update the transient to include our updated plugin data
|
2 |
if ( $doUpdate == 1 ) { |
3 |
$package = $this->githubAPIResult->zipball_url; |
4 |
|
5 |
// Include the access token for private GitHub repos
|
6 |
if ( !empty( $this->accessToken ) ) { |
7 |
$package = add_query_arg( array( "access_token" => $this->accessToken ), $package ); |
8 |
}
|
9 |
|
10 |
$obj = new stdClass(); |
11 |
$obj->slug = $this->slug; |
12 |
$obj->new_version = $this->githubAPIResult->tag_name; |
13 |
$obj->url = $this->pluginData["PluginURI"]; |
14 |
$obj->package = $package; |
15 |
$transient->response[$this->slug] = $obj; |
16 |
}
|
17 |
|
18 |
return $transient; |
Después de que esta función procese nuestra información de GitHub, nuestro administrador podrá mostrar notificaciones en la página de administración del complemento:



La función setPluginInfo
El propósito de esta función es recopilar detalles sobre el complemento actualizado de las notas de la versión. Toda esta información se mostrará dentro de una caja de luz cuando se haga clic en el enlace Ver detalles de la versión x.x.
Primero, obtengamos nuestra información del complemento:
1 |
// Get plugin & GitHub release information
|
2 |
$this->initPluginData(); |
3 |
$this->getRepoReleaseInfo(); |
A continuación, verificamos si es hora de mostrar algo o no. Podemos verificar si estamos intentando cargar información para nuestro complemento actual verificando el slug
:
1 |
// If nothing is found, do nothing
|
2 |
if ( empty( $response->slug ) || $response->slug != $this->slug ) { |
3 |
return false; |
4 |
}
|
Para mostrar los detalles de nuestro complemento, necesitamos agregar nuestra información de complemento manualmente a la variable $response
, normalmente esta variable se completará con los resultados del repositorio de complementos de WordPress:
1 |
// Add our plugin information
|
2 |
$response->last_updated = $this->githubAPIResult->published_at; |
3 |
$response->slug = $this->slug; |
4 |
$response->plugin_name = $this->pluginData["Name"]; |
5 |
$response->version = $this->githubAPIResult->tag_name; |
6 |
$response->author = $this->pluginData["AuthorName"]; |
7 |
$response->homepage = $this->pluginData["PluginURI"]; |
8 |
|
9 |
// This is our release download zip file
|
10 |
$downloadLink = $this->githubAPIResult->zipball_url; |
11 |
|
12 |
// Include the access token for private GitHub repos
|
13 |
if ( !empty( $this->accessToken ) ) { |
14 |
$downloadLink = add_query_arg( |
15 |
array( "access_token" => $this->accessToken ), |
16 |
$downloadLink
|
17 |
);
|
18 |
}
|
19 |
$response->download_link = $downloadLink; |
Hasta ahora hemos agregado los detalles de nuestro complemento, pero aún no hemos analizado nuestras notas de lanzamiento de nuestra versión de GitHub. Veamos lo que tenemos en nuestras notas de lanzamiento:



En las notas de la versión, hemos especificado tres detalles con respecto a nuestra versión: nuestro registro de cambios, seguido de la versión mínima requerida de WordPress, luego la última versión de WordPress en la que se probó el complemento. Vamos a analizar este texto y extraer estos valores.
Dado que estamos alojando nuestro complemento en GitHub, sería bueno si pudiéramos tener la capacidad de usar GitHub Markdown en nuestras notas de lanzamiento. Voy a usar una clase PHP llamada ParseDown para convertir el texto de Markdown a HTML:
1 |
// We're going to parse the GitHub markdown release notes, include the parser
|
2 |
require_once( plugin_dir_path( __FILE__ ) . "Parsedown.php" ); |
También vamos a crear pestañas en la caja de luz para que se ajuste de la misma manera como los complementos alojados en el repositorio de WordPress muestran su información. Uno será para la descripción del complemento y el otro será para nuestro registro de cambios:
1 |
// Create tabs in the lightbox
|
2 |
$response->sections = array( |
3 |
'description' => $this->pluginData["Description"], |
4 |
'changelog' => class_exists( "Parsedown" ) |
5 |
? Parsedown::instance()->parse( $this->githubAPIResult->body ) |
6 |
: $this->githubAPIResult->body |
7 |
);
|
Finalmente vamos a extraer los valores para los requisitos y pruebas:
1 |
// Gets the required version of WP if available
|
2 |
$matches = null; |
3 |
preg_match( "/requires:\s([\d\.]+)/i", $this->githubAPIResult->body, $matches ); |
4 |
if ( ! empty( $matches ) ) { |
5 |
if ( is_array( $matches ) ) { |
6 |
if ( count( $matches ) > 1 ) { |
7 |
$response->requires = $matches[1]; |
8 |
}
|
9 |
}
|
10 |
}
|
11 |
|
12 |
// Gets the tested version of WP if available
|
13 |
$matches = null; |
14 |
preg_match( "/tested:\s([\d\.]+)/i", $this->githubAPIResult->body, $matches ); |
15 |
if ( ! empty( $matches ) ) { |
16 |
if ( is_array( $matches ) ) { |
17 |
if ( count( $matches ) > 1 ) { |
18 |
$response->tested = $matches[1]; |
19 |
}
|
20 |
}
|
21 |
}
|
22 |
|
23 |
return $response; |
La función postInstall
En esta última función nos ocuparemos de realizar procesos adicionales que necesitamos para instalar completamente nuestro complemento después de descargarlo.
Al crear una versión para nuestro repositorio de GitHub, crea automáticamente un archivo zip para esa versión específica. El nombre de archivo para el archivo zip es generado por GitHub con el formato reponame-tagname.zip. Esto también contiene un directorio donde se encuentran nuestros archivos de complemento. Del mismo modo, el nombre del directorio para esto también sigue el formato reponame-tagname.
Normalmente, cuando WordPress descarga y descomprime un archivo de complementos, el nombre del directorio de complementos no cambia. Si el directorio de tu plugin es my-awesome-plugin, después de eliminar los archivos antiguos del plugin y descomprimir el actualizado, tu directorio aún se llamaría my-awesome-plugin. Pero dado que GitHub cambia el nombre del directorio de nuestro complemento cada vez que implementamos una versión, WordPress no podrá encontrar nuestro complemento. Todavía podría instalarlo, pero no podrá volver a activarlo. Podemos solucionar este problema cambiando el nombre del nuevo directorio para que coincida con el anterior.
Lo primero es lo primero:
1 |
// Get plugin information
|
2 |
$this->initPluginData(); |
A continuación, debemos verificar si nuestro complemento está activado actualmente, para que podamos volver a activarlo después:
1 |
// Remember if our plugin was previously activated
|
2 |
$wasActivated = is_plugin_active( $this->slug ); |
Ahora cambiamos el nombre del directorio actualizado de nuestro complemento para que coincida con el anterior. Estamos usando la función move
aquí, pero como estamos especificando el mismo directorio, sería como renombrarlo:
1 |
// Since we are hosted in GitHub, our plugin folder would have a dirname of
|
2 |
// reponame-tagname change it to our original one:
|
3 |
global $wp_filesystem; |
4 |
$pluginFolder = WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . dirname( $this->slug ); |
5 |
$wp_filesystem->move( $result['destination'], $pluginFolder ); |
6 |
$result['destination'] = $pluginFolder; |
El último paso sería reactivar el complemento:
1 |
// Re-activate plugin if needed
|
2 |
if ( $wasActivated ) { |
3 |
$activate = activate_plugin( $this->slug ); |
4 |
}
|
5 |
|
6 |
return $result; |
Llamando a nuestra clase Updater de GitHub
Ahora que la clase ha terminado, todo lo que queda por hacer es llamarla en nuestro archivo del complemento principal:
1 |
require_once( 'BFIGitHubPluginUploader.php' ); |
2 |
if ( is_admin() ) { |
3 |
new BFIGitHubPluginUpdater( __FILE__, 'myGitHubUsername', "Repo-Name" ); |
4 |
}
|
Probándolo
¡Eso es! Solo incluye esta clase y llámala en tu complemento, y debería comenzar a buscar actualizaciones automáticamente.
Para probar si funciona, crea una nueva versión para tu repositorio de GitHub y sigue las guías indicadas anteriormente:



Una vez que hayas creado tu versión, puedes obligar a WordPress a buscar actualizaciones haciendo clic en el botón "Actualizar" que se encuentra en la barra de administración.
Conclusión
Espero que hayas aprendido una o dos cosas sobre cómo funciona WordPress y cómo se realiza todo el proceso de actualización de complementos. Puedes descargar el script completo desde los enlaces de descarga en la parte superior de este artículo.
Espero que hayas disfrutado este artículo. Agradezco mucho cualquier comentario, comentario y sugerencia. ¡Comparte tus pensamientos a continuación!