Desarrollo de plugins con WordPress Boilerplates: Construyendo un plugin
Spanish (Español) translation by Eva Collados Pascual (you can also view the original English article)
En el primer artículo de esta serie, analizamos cómo una base reutilizable o "boilerplate" puede mejorar tus esfuerzos de desarrollo proporcionandote una base a partir de la cual puedes construir tu proyecto.
Idealmente, los boilerplates deben proporcionar un marco suficiente para empezar, al mismo tiempo que te permiten centrarte en la lógica específica del negocio, la necesidad principal o el código del ámbito concreto que necesitas escribir.
Específicamente, echamos un vistazo a WordPress Widget Boilerplates y a WordPress Plugin Boilerplate. En este artículo, vamos a aprovechar Plugin Boilerplate para escribir nuestro propio plugin con el fin de ver cómo los boilerplates sientan las bases para escribir un buen código, y cómo podemos usarlos como punto de partida para nuestro futuro trabajo.
Un plugin de mensaje de entrada
En este artículo, vamos a crear un widget de notificación de entrada que permite al autor añadir un nuevo mensaje de entrada antes del contenido en tu página. Esto estará basado en un plugin que ya existe por ahí para que puedas tener un punto de referencia una vez que el proyecto se haya completado.
Al igual que con el resto de mis tutoriales, me gusta planificar el proyecto con antelación, así que vamos a establecer todo lo que haremos:
- Descargar una copia de WordPress Plugin Boilerplate
- Cumplimentar todos los requisitos con la información específica para tu propio proyecto
- Implementar el código necesario para mostrar y guardar información en una caja meta de una entrada
- Comprobar la existencia de post meta y luego renderizarlo en el contenido
- Finalizar el README y la localización
En general, es un plugin relativamente simple, pero debería proporcionar un sólido ejemplo de cómo Boilerplates te permite centrarte en tu funcionalidad concreta todo el tiempo mientras trabajas dentro del contexto de las mejores prácticas de WordPress.
Construyendo el widget
1. Descarga WordPress Plugin Boilerplate
Para empezar, necesitas descargar una copia de WordPress Plugin Boilerplate. Puedes hacerlo navegando a su página de GitHub, haciendo luego clic en el botón 'zip' situado cerca de la parte superior de la página o haciendo clic aquí.
A continuación, extrae el contenido de la descarga en tu directorio de plugins. Inicialmente deberías crear un directorio llamado plugin-boilerplate.



2. Cumplimenta los requisitos en el boilerplate
A continuación, abre el directorio post-message en tu IDE favorito. Lo primero que queremos hacer es abrir plugin.php y luego localizar todos los requisitos que existen en el código.
De forma predeterminada, el código tendrá un aspecto parecido a este:
1 |
<?php
|
2 |
/*
|
3 |
Plugin Name: TODO
|
4 |
Plugin URI: TODO
|
5 |
Description: TODO
|
6 |
Version: 1.0
|
7 |
Author: TODO
|
8 |
Author URI: TODO
|
9 |
Author Email: TODO
|
10 |
License:
|
11 |
|
12 |
Copyright 2013 TODO (email@domain.com)
|
13 |
|
14 |
This program is free software; you can redistribute it and/or modify
|
15 |
it under the terms of the GNU General Public License, version 2, as
|
16 |
published by the Free Software Foundation.
|
17 |
|
18 |
This program is distributed in the hope that it will be useful,
|
19 |
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
20 |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
21 |
GNU General Public License for more details.
|
22 |
|
23 |
You should have received a copy of the GNU General Public License
|
24 |
along with this program; if not, write to the Free Software
|
25 |
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
26 |
|
27 |
*/
|
28 |
|
29 |
// TODO: rename this class to a proper name for your plugin
|
30 |
class PluginName { |
31 |
|
32 |
/*--------------------------------------------*
|
33 |
* Constructor
|
34 |
*--------------------------------------------*/
|
35 |
|
36 |
/**
|
37 |
* Initializes the plugin by setting localization, filters, and administration functions.
|
38 |
*/
|
39 |
function __construct() { |
40 |
|
41 |
// Load plugin text domain
|
42 |
add_action( 'init', array( $this, 'plugin_textdomain' ) ); |
43 |
|
44 |
// Register admin styles and scripts
|
45 |
add_action( 'admin_print_styles', array( $this, 'register_admin_styles' ) ); |
46 |
add_action( 'admin_enqueue_scripts', array( $this, 'register_admin_scripts' ) ); |
47 |
|
48 |
// Register site styles and scripts
|
49 |
add_action( 'wp_enqueue_scripts', array( $this, 'register_plugin_styles' ) ); |
50 |
add_action( 'wp_enqueue_scripts', array( $this, 'register_plugin_scripts' ) ); |
51 |
|
52 |
// Register hooks that are fired when the plugin is activated, deactivated, and uninstalled, respectively.
|
53 |
register_activation_hook( __FILE__, array( $this, 'activate' ) ); |
54 |
register_deactivation_hook( __FILE__, array( $this, 'deactivate' ) ); |
55 |
register_uninstall_hook( __FILE__, array( $this, 'uninstall' ) ); |
56 |
|
57 |
/*
|
58 |
* TODO:
|
59 |
* Define the custom functionality for your plugin. The first parameter of the
|
60 |
* add_action/add_filter calls are the hooks into which your code should fire.
|
61 |
*
|
62 |
* The second parameter is the function name located within this class. See the stubs
|
63 |
* later in the file.
|
64 |
*
|
65 |
* For more information:
|
66 |
* http://codex.wordpress.org/Plugin_API#Hooks.2C_Actions_and_Filters
|
67 |
*/
|
68 |
add_action( 'TODO', array( $this, 'action_method_name' ) ); |
69 |
add_filter( 'TODO', array( $this, 'filter_method_name' ) ); |
70 |
|
71 |
} // end constructor |
72 |
|
73 |
/**
|
74 |
* Fired when the plugin is activated.
|
75 |
*
|
76 |
* @param boolean $network_wide True if WPMU superadmin uses "Network Activate" action, false if WPMU is disabled or plugin is activated on an individual blog
|
77 |
*/
|
78 |
public function activate( $network_wide ) { |
79 |
// TODO: Define activation functionality here
|
80 |
} // end activate |
81 |
|
82 |
/**
|
83 |
* Fired when the plugin is deactivated.
|
84 |
*
|
85 |
* @param boolean $network_wide True if WPMU superadmin uses "Network Activate" action, false if WPMU is disabled or plugin is activated on an individual blog
|
86 |
*/
|
87 |
public function deactivate( $network_wide ) { |
88 |
// TODO: Define deactivation functionality here
|
89 |
} // end deactivate |
90 |
|
91 |
/**
|
92 |
* Fired when the plugin is uninstalled.
|
93 |
*
|
94 |
* @param boolean $network_wide True if WPMU superadmin uses "Network Activate" action, false if WPMU is disabled or plugin is activated on an individual blog
|
95 |
*/
|
96 |
public function uninstall( $network_wide ) { |
97 |
// TODO: Define uninstall functionality here
|
98 |
} // end uninstall |
99 |
|
100 |
/**
|
101 |
* Loads the plugin text domain for translation
|
102 |
*/
|
103 |
public function plugin_textdomain() { |
104 |
|
105 |
// TODO: replace "plugin-name-locale" with a unique value for your plugin
|
106 |
load_plugin_textdomain( 'plugin-name-locale', false, dirname( plugin_basename( __FILE__ ) ) . '/lang' ); |
107 |
|
108 |
} // end plugin_textdomain |
109 |
|
110 |
/**
|
111 |
* Registers and enqueues admin-specific styles.
|
112 |
*/
|
113 |
public function register_admin_styles() { |
114 |
|
115 |
// TODO: Change 'plugin-name' to the name of your plugin
|
116 |
wp_enqueue_style( 'plugin-name-admin-styles', plugins_url( 'plugin-name/css/admin.css' ) ); |
117 |
|
118 |
} // end register_admin_styles |
119 |
|
120 |
/**
|
121 |
* Registers and enqueues admin-specific JavaScript.
|
122 |
*/
|
123 |
public function register_admin_scripts() { |
124 |
|
125 |
// TODO: Change 'plugin-name' to the name of your plugin
|
126 |
wp_enqueue_script( 'plugin-name-admin-script', plugins_url( 'plugin-name/js/admin.js' ) ); |
127 |
|
128 |
} // end register_admin_scripts |
129 |
|
130 |
/**
|
131 |
* Registers and enqueues plugin-specific styles.
|
132 |
*/
|
133 |
public function register_plugin_styles() { |
134 |
|
135 |
// TODO: Change 'plugin-name' to the name of your plugin
|
136 |
wp_enqueue_style( 'plugin-name-plugin-styles', plugins_url( 'plugin-name/css/display.css' ) ); |
137 |
|
138 |
} // end register_plugin_styles |
139 |
|
140 |
/**
|
141 |
* Registers and enqueues plugin-specific scripts.
|
142 |
*/
|
143 |
public function register_plugin_scripts() { |
144 |
|
145 |
// TODO: Change 'plugin-name' to the name of your plugin
|
146 |
wp_enqueue_script( 'plugin-name-plugin-script', plugins_url( 'plugin-name/js/display.js' ) ); |
147 |
|
148 |
} // end register_plugin_scripts |
149 |
|
150 |
/*--------------------------------------------*
|
151 |
* Core Functions
|
152 |
*---------------------------------------------*/
|
153 |
|
154 |
/**
|
155 |
* NOTE: Actions are points in the execution of a page or process
|
156 |
* lifecycle that WordPress fires.
|
157 |
*
|
158 |
* WordPress Actions: http://codex.wordpress.org/Plugin_API#Actions
|
159 |
* Action Reference: http://codex.wordpress.org/Plugin_API/Action_Reference
|
160 |
*
|
161 |
*/
|
162 |
function action_method_name() { |
163 |
// TODO: Define your action method here
|
164 |
} // end action_method_name |
165 |
|
166 |
/**
|
167 |
* NOTE: Filters are points of execution in which WordPress modifies data
|
168 |
* before saving it or sending it to the browser.
|
169 |
*
|
170 |
* WordPress Filters: http://codex.wordpress.org/Plugin_API#Filters
|
171 |
* Filter Reference: http://codex.wordpress.org/Plugin_API/Filter_Reference
|
172 |
*
|
173 |
*/
|
174 |
function filter_method_name() { |
175 |
// TODO: Define your filter method here
|
176 |
} // end filter_method_name |
177 |
|
178 |
} // end class |
179 |
|
180 |
// TODO: Update the instantiation call of your plugin to the name given at the class definition
|
181 |
$plugin_name = new PluginName(); |
A continuación, tenemos que hacer cada requisito específico del plugin para que coloquemos todas las instancias donde sea necesario con una variación del nombre del plugin.
Por ejemplo:
- Reemplaza el nombre del plugin por Post Message
- Reemplaza la URL del plugin por la URL de tu elección
- Rellena el nombre, la dirección de correo electrónico y toda la información personal con lo que corresponda
- Asigna un nombre a la clase
Post_Message
- Asigna un nombre a las cadenas, los nombres de clase y los IDs de
post-message
relacionados con la configuración regional
A continuación, también podemos eliminar todas las llamadas a JavaScript y a hojas de estilos, excepto los estilos admin.css. Usaremos este archivo más tarde. También asegúrate de eliminar los siguientes archivos del Boilerplate:
- views/display.php
- css/plugin.css
- js/admin.js
- js/plugin.js
Esto debería dejarte con la siguiente estructura de directorios:



Ten en cuenta que esto es habitual en un Boilerplate: en cualquier caso, deberías querer quitar algo de él. Si tienes que añadirle algo que te ayude a despegar del suelo, o que sea más fundamental, en ese caso podría ser una oportunidad para mejorarlo.
De todos modos, aunque aún no hemos terminado, y aunque vamos a añadir cosas al plugin, el archivo debería tener un aspecto similar a esto en este momento:
1 |
<?php
|
2 |
/*
|
3 |
Plugin Name: Tom McFarlin
|
4 |
Plugin URI: http://tommcfarlin.com/single-post-message/
|
5 |
Description: A simple way to add a message at the top of each of your posts.
|
6 |
Version: 1.0
|
7 |
Author: Tom McFarlin
|
8 |
Author URI: http://tommcfarlin.com/
|
9 |
Author Email: tom@tommcfarlin.com
|
10 |
License:
|
11 |
|
12 |
Copyright 2013 Tom McFarlin (om@tommcfarlin.com)
|
13 |
|
14 |
This program is free software; you can redistribute it and/or modify
|
15 |
it under the terms of the GNU General Public License, version 2, as
|
16 |
published by the Free Software Foundation.
|
17 |
|
18 |
This program is distributed in the hope that it will be useful,
|
19 |
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
20 |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
21 |
GNU General Public License for more details.
|
22 |
|
23 |
You should have received a copy of the GNU General Public License
|
24 |
along with this program; if not, write to the Free Software
|
25 |
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
26 |
|
27 |
*/
|
28 |
|
29 |
class Post_Message { |
30 |
|
31 |
/*--------------------------------------------*
|
32 |
* Constructor
|
33 |
*--------------------------------------------*/
|
34 |
|
35 |
/**
|
36 |
* Initializes the plugin by setting localization, filters, and administration functions.
|
37 |
*/
|
38 |
function __construct() { |
39 |
|
40 |
// Load plugin text domain
|
41 |
add_action( 'init', array( $this, 'plugin_textdomain' ) ); |
42 |
|
43 |
// Register admin styles and scripts
|
44 |
add_action( 'admin_print_styles', array( $this, 'register_admin_styles' ) ); |
45 |
|
46 |
} // end constructor |
47 |
|
48 |
/**
|
49 |
* Loads the plugin text domain for translation
|
50 |
*/
|
51 |
public function plugin_textdomain() { |
52 |
load_plugin_textdomain( 'post-message', false, dirname( plugin_basename( __FILE__ ) ) . '/lang' ); |
53 |
} // end plugin_textdomain |
54 |
|
55 |
/**
|
56 |
* Registers and enqueues admin-specific styles.
|
57 |
*/
|
58 |
public function register_admin_styles() { |
59 |
wp_enqueue_style( 'post-message-admin', plugins_url( 'post-message/css/admin.css' ) ); |
60 |
} // end register_admin_styles |
61 |
|
62 |
} // end class |
63 |
|
64 |
new Post_Message(); |
A continuación, tenemos que empezar a trabajar en la lógica de nuestro negocio principal.
3. Implementar el código necesario para mostrar y guardar información en una caja meta de un post
En primer lugar, tenemos que identificar exactamente cómo va a funcionar esto:
- Mostraremos una caja meta de post directamente bajo el editor de contenido
- Incluirá un área de texto que permite a los usuarios proporcionar su propio contenido
- Cuando se actualiza el post, debe guardar el contenido del área de texto
Así que lo primero que tenemos que hacer es introducir un gancho y una función con el fin de capturar los datos que contiene la caja meta. En el constructor, vamos a añadir la siguiente línea:
add_action( 'add_meta_boxes', array( $this, 'add_notice_metabox' ) );
A continuación, necesitamos definir la función add_notice_metabox
para que podamos representar realmente el cuadro meta, así que vamos a proporcionar ahora esa función:
1 |
function add_notice_metabox() { |
2 |
|
3 |
add_meta_box( |
4 |
'post_message', |
5 |
__( 'Post Message', 'post-message' ), |
6 |
array( $this, 'post_message_display' ), |
7 |
'post', |
8 |
'normal', |
9 |
'high'
|
10 |
);
|
11 |
|
12 |
} // end add_notice_metabox |
Después de eso, necesitamos escribir una función que sea responsable de representar realmente el contenido del post:
1 |
function post_message_display( $post ) { |
2 |
|
3 |
wp_nonce_field( plugin_basename( __FILE__ ), 'post_message_nonce' ); |
4 |
|
5 |
// The textfield and preview area
|
6 |
echo '<textarea id="post-message" name="post_message" placeholder="' . __( 'Enter your post message here. HTML accepted.', 'post-message' ) . '">' . esc_textarea( get_post_meta( $post->ID, 'post_message', true ) ) . '</textarea>'; |
7 |
|
8 |
} // end post_message_display |
Arriba, observa que hemos introducido un campo nonce por motivos de seguridad. Observa también que hemos dado a esta área de texto (textarea
) el ID post-message
para que podamos darle fácilmente estilo usando CSS, y le hemos dado el nombre post_message
que será útil a medida que guardemos el contenido del textarea
en los metadatos del post concreto.
Por último, hemos aprovechado la función esc_textarea
para asegurarnos de que estamos codificando correctamente nuestros datos a fin de representarlos en el área de texto.
En este punto, vamos a añadir un poco de estilo utilizando el archivo admin.css para dar al mensaje del post un aspecto ligeramente diferente al contenido:
1 |
#post-message { width: 100%; } |
Esto debería dar lugar a algo como lo siguiente:



Por supuesto, aún no hemos terminado. Necesitamos guardar y recuperar los datos cuando el usuario haga clic en el botón "Publicar" o "Actualizar". Para ello, necesitamos configurar una devolución de llamada que guarde los datos del post.
Así que lo último que tenemos que hacer es introducir una función save_notice
. En primer lugar, registraremos esto en el constructor con el siguiente código:
add_action( 'save_post', array( $this, 'save_notice' ) );
A continuación, definiremos la siguiente función:
1 |
function save_notice( $post_id ) { |
2 |
|
3 |
if ( isset( $_POST['post_message_nonce'] ) && isset( $_POST['post_type'] ) ) { |
4 |
|
5 |
// Don't save if the user hasn't submitted the changes
|
6 |
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { |
7 |
return; |
8 |
} // end if |
9 |
|
10 |
// Verify that the input is coming from the proper form
|
11 |
if ( ! wp_verify_nonce( $_POST['post_message_nonce'], plugin_basename( __FILE__ ) ) ) { |
12 |
return; |
13 |
} // end if |
14 |
|
15 |
// Make sure the user has permissions to post
|
16 |
if ( 'post' == $_POST['post_type'] ) { |
17 |
if ( ! current_user_can( 'edit_post', $post_id ) ) { |
18 |
return; |
19 |
} // end if |
20 |
} // end if/else |
21 |
|
22 |
// Read the post message
|
23 |
$post_message = isset( $_POST['post_message'] ) ? $_POST['post_message'] : ''; |
24 |
|
25 |
// If the value for the post message exists, delete it first. Don't want to write extra rows into the table.
|
26 |
if ( 0 == count( get_post_meta( $post_id, 'post_message' ) ) ) { |
27 |
delete_post_meta( $post_id, 'post_message' ); |
28 |
} // end if |
29 |
|
30 |
// Update it for this post.
|
31 |
update_post_meta( $post_id, 'post_message', $post_message ); |
32 |
|
33 |
} // end if |
34 |
|
35 |
} // end save_notice |
Hemos explicado cómo guardar el contenido de los metadatos del post en profundidad en artículos anteriores, así que no quiero repetir este punto aquí, basta con decir que la función hace lo siguiente:
- Comprueba que el usuario tiene permiso para guardar esta información
- Elimina cualquier metadato existentes en el post
- Guarda los datos en los metadatos del post asociados
En este punto, estamos listos para probar el plugin, así que rellena el textarea
, guarda los datos y, cuando la página se actualice, asegúrate de que los datos también se muestran en el textarea
.
Si es así, estamos listos para continuar; de lo contrario, asegúrate de que el código se parezca al código de más arriba.
4. Renderizar el aviso del post
A continuación, estamos listos para representar el mensaje del post en el contenido. El proceso para hacer esto será el siguiente:
- Registrar una función con el filtro
the_content
- Comprobar la existencia de metadatos de post
- En el caso de que existan, renderizarlos por encima del contenido
Así que vamos a hacer precisamente eso. En primer lugar, vamos a registrar una función con el filtro the_content
:
add_filter( 'the_content', array( $this, 'prepend_post_message' ) );
Después de eso, vamos a configurar la función real:
1 |
function prepend_post_message( $content ) { |
2 |
|
3 |
// If there is a notice, prepend it to the content
|
4 |
if ( '' != get_post_meta( get_the_ID(), 'post_message', true ) ) { |
5 |
|
6 |
$post_message = '<p class="post-message">'; |
7 |
$post_message .= get_post_meta( get_the_ID(), 'post_message', true ); |
8 |
$post_message .= '</p><!-- /.post-message -->'; |
9 |
|
10 |
$content = $post_message . $content; |
11 |
|
12 |
} // end if |
13 |
|
14 |
return $content; |
15 |
|
16 |
} // end prepend_post_message |
Ten en cuenta que el mensaje del post está contenido dentro de su propio elemento p
con su propio nombre de clase para que puedas aplicarle estilo fácilmente según desees, y si lo deseas.
En este punto, debemos tener todo lo que necesitamos para ver el mensaje del post, así que vamos a probarlo. Crea un mensaje de post, publícalo y, a continuación, observa el post en tu navegador.
A continuación, quita el contenido del post y, a continuación, comprueba que no aparezca nada.
Bastante fácil, ¿no?
5. Finalizar el README y la localización
Por último, tenemos que asegurarnos de limpiar el archivo README y localizar correctamente el plugin.
En primer lugar, el archivo README debe contener la información habitual. Esto es en gran parte subjetivo; no obstante, a continuación proporciono un ejemplo de README:
1 |
=== Post Message === |
2 |
Contributors: tommcfarlin |
3 |
Donate link: http://tommcfarlin.com/single-post-message/ |
4 |
Tags: post |
5 |
Requires at least: 3.4.1 |
6 |
Tested up to: 3.5 |
7 |
Stable tag: 1.0 |
8 |
License: GPLv2 or later |
9 |
License URI: http://www.gnu.org/licenses/gpl-2.0.html |
10 |
|
11 |
Easily add short messages and announcements above posts. Displays in the RSS feed and on the blog. |
12 |
|
13 |
== Description == |
14 |
|
15 |
Post Message is a plugin that allows you to add custom messages, announcements, and notices to individual posts. It's styled to grab the reader's attention and will render in both the browser *and* in RSS readers. |
16 |
|
17 |
Post Message... |
18 |
|
19 |
* Supports the use of HTML tags in the message content |
20 |
* Is available directly under the post content editor |
21 |
* Is fully localized and ready for translation |
22 |
|
23 |
== Installation == |
24 |
|
25 |
= Using The WordPress Dashboard = |
26 |
|
27 |
1. Navigate to the 'Add New' Plugin Dashboard |
28 |
1. Select `post-message.zip` from your computer |
29 |
1. Upload |
30 |
1. Activate the plugin on the WordPress Plugin Dashboard |
31 |
|
32 |
= Using FTP = |
33 |
|
34 |
1. Extract `post-message.zip` to your computer |
35 |
1. Upload the `post-messsage` directory to your `wp-content/plugins` directory |
36 |
1. Activate the plugin on the WordPress Plugins dashboard |
37 |
|
38 |
== Changelog == |
39 |
|
40 |
= 1.0 = |
41 |
* Initial release |
Y por último, la localización también debería ser fácil: Simplemente abre el archivo 'plugin.po' en el directorio 'lang' en tu IDE, asegúrate de cambiar el nombre del plugin y la información del autor, luego ábrelo en POEdit para registrar los archivos de localización.



¡Guarda tu trabajo y listo!
Conclusión
En esta serie, hemos examinado exactamente lo que se necesita para aprovechar WordPress Boilerplates, cómo pueden ayudar e impactar nuestro flujo de trabajo, y nos ayudan a centrarnos más en el núcleo de nuestro proyecto en lugar de reinventar la rueda y levantarse y seguir repitiendo mucho de lo que ya es necesario.
Además, hemos creado un plugin que se puede descargar para su posterior revisión.
Esperemos que esta serie haya proporcionado un ejemplo de por qué debes usar código reutilizable cuando sea aplicable, y haya ayudado a mostrar cómo pueden aliviar la frustración de tener que volver a escribir gran parte del mismo código en cada uno de tus proyectos.