1. Code
  2. WordPress
  3. Plugin Development

Creando Páginas Personalizadas de Administración en WordPress, Parte 3

En esta serie, hemos estado viendo cómo crear páginas personalizadas de administración en WordPress sin el uso de la Settings API. Esto no es decir que la Settings API no es útil (¡porque lo es!), pero habrá veces cuando necesitemos implementar alguna funcionalidad personalizada o implementaciones más especializadas de características que las APIs disponibles no pueden lograr.
Scroll to top
This post is part of a series called Creating Custom WordPress Administration Pages.
Creating Custom WordPress Administration Pages, Part 2

Spanish (Español) translation by Rafael Chavarría (you can also view the original English article)

En esta serie, hemos estado viendo cómo crear páginas personalizadas de administración en WordPress sin el uso de la Settings API. Esto no es decir que la Settings API no es útil (¡porque lo es!), pero habrá veces cuando necesitemos implementar alguna funcionalidad personalizada o implementaciones más especializadas de características que las APIs disponibles no pueden lograr.

Adicionalmente, estamos viendo algunos de los principios de desarrollo de software más importantes, tales como el principio de responsabilidad individual y aplicándolos a nuestro trabajo.

Si justo te estás uniendo a esta serie, te recomiendo leer los artículos anteriores para que te familiarices con lo que hemos hecho hasta este punto para que puedas entender por qué estamos haciendo algunas de las decisiones que estamos tomando cuando escribimos nuestro código.

Una Revisión Rápida

Aunque no puedo resumir todo lo que hemos cubierto hasta el momento en la serie, puedo asegurarme de resaltar los puntos más importantes.

  • Hemos introducido el complemento principal y agregado un elemento de submenú y página de opciones para el complemento en el tablero de WordPress.
  • Hemos discutido el principio de responsabilidad individual en y el rol que juega en nuestro desarrollo.
  • Un elemento input simple ha sido agregado que aceptará entrada del usuario.
  • Hemos agregado un valor nonce a la página, pero no hemos hecho nada con él.

Dicho eso, asumo que tienes la última versión del código fuente (el cuál está disponible como archivo adjunto en el artículo anterior) y estás listo para proseguir.

Antes de que Comencemos

Como con los otros artículos, asumo que tienes un entorno local de desarrollo WordPress configurado en tu máquina.

Además, asumo que tienes la última versión del código fuente y estás listo para continuar construyendo encima de este o estás cómo leyendo el código que tenemos aquí e implementándolo cuando tengas más tiempo.

Finalmente, estaremos escalando cada pedazo de código incrementalmente. Primero, hablaré sobre lo que vamos a hacer, después mostraré el código y después explicaré lo que sea que el código está haciendo para que no quede nada que pudiera ser confuso.

Sí, sin embargo, te encuentras confundido acerca de cualquier cosa en el código y el tutorial no hace un buen trabajo explicando que está pasando, por favor deja un comentario y me aseguraré de darle seguimiento.

Comencemos.

Implementando Nuevas Características

En el último artículo, nos quedamos con un complemento que se ve como si hiciera algo pero realmente no guarda nada en la base de datos, mucho menos toma nada de la base de datos.

En resumen, tenemos un complemento que se ve funcional pero no lo es. Y ahí es donde vamos a retomar con este tutorial.

Específicamente, vamos a abordar los siguientes temas:

  • Vamos a verificar el valor nonce que creamos y definimos en el tutorial anterior para ganar un entendimiento de cómo un componente de seguridad de WordPress funciona.
  • Verificaremos que el usuario existente tenga permiso de enviar información (y prevenirlo de hacerlo, si no lo tiene).
  • Si el envío es seguro y el usuario tiene permiso, entonces limpiaremos la información para asegurarnos de que no entre contenido malicioso a la base de datos.

Con eso como nuestro mapa, estamos listos para saltar de vuelta al código y continuar trabajando en el complemento.

Seguridad

Recordando de la publicación anterior, tomamos ventaja de la función wp_nonce_field de la API de WordPress. Esta función en particular hace lo siguiente:

El campo nonce es usado para validar que los contenidos de la petición de formulario vienen del sitio actual y no de algún otro lugar. Un nonce no ofrece protección absoluta, pero debería proteger en la mayoría de los casos. Es muy importante usar campos nonce en formularios.

Si intentas guardar la página de opciones, probablemente se te presentará una pantalla en blanco. Esto nunca es bueno, pero es lo que esperamos dado el estado actual de nuestro complemento.

Necesitamos introducir una función que se enganchará a uno de los ganchos disponibles de WordPress, y revisará si el valor nonce es válido. Si es valido, entonces nos dejará proceder guardando la información, de otro modo, no nos debería dejar proceder. 

Ya que estamos en crear una página personalizada de administración, vamos a necesitar un gancho diferente a lo que podríamos estar acostumbrados de usar en situaciones como esta. En este ejemplo, vamos a usar el gancho admin_post.

Dispara en una petición autenticada de publicación de admin en donde ninguna acción fue proporcionada.

Recuerda de nuestras discusiones previas, sin embargo, que no queremos sobrecargar nuestras clases con más responsabilidad de la necesaria. Recuerda, la pregunta que debemos hacernos constantemente es: "¿Qué razón tendría esta clase para cambiar?"

Justo ahora, no tenemos una clase que pueda guardar opciones. Así que introduzcamos una. En el directorio admin del complemento, creemos una clase Serializer. Está será responsable de guardar el valor de nuestras  opciones.

The Serializer class in our plugin directoryThe Serializer class in our plugin directoryThe Serializer class in our plugin directory

Como puedes ver, he nombrado mi archivo class-serializer.php. Sabemos por experiencia y del código de arriba que va a necesitar enganchar al gancho admin_post mencionado arriba, y sabemos que vamos a necesitar una función responsable de guardar la información.

Definamos esas ahora.

1
<?php
2
/**

3
 * Performs all sanitization functions required to save the option values to

4
 * the database.

5
 *

6
 * @package Custom_Admin_Settings

7
 */
8
9
/**

10
 * Performs all sanitization functions required to save the option values to

11
 * the database.

12
 *

13
 * This will also check the specified nonce and verify that the current user has

14
 * permission to save the data.

15
 *

16
 * @package Custom_Admin_Settings

17
 */
18
class Serializer {
19
20
    public function init() {
21
        add_action( 'admin_post', array( $this, 'save' ) );
22
    }
23
24
    public function save() {
25
26
        // First, validate the nonce.

27
        // Secondly, verify the user has permission to save.

28
        // If the above are valid, save the option.

29
30
    }
31
}

Obviamente, aún hay trabajo por hacer (de hecho, ¡ni siquiera hemos instanciado la clase!) pero el código de arriba podría ser suficiente para ver a dónde nos dirigimos.

Una Conversación Rápida Sobre Dependencias

Antes de agregar cualquier funcionalidad, continuemos y configuremos esto cuando nuestro complemento carga por primera vez. Primero, regresa el custom-admin-settings.php. Ahora, en este punto, necesitamos preguntarnos si cualquiera de nuestras clases existentes debería tener el Serializer como dependencia.

Pienso que un caso puede ser hecho de que el Submenu_Page debería tener una referencia al serializer ya que la página tiene las opciones para guardar.

De manera alternativa, también es posible dejar este archivo completamente separado y tenerlo disponible para otro patrón. Si hiciéramos eso, estaríamos desviándonos del tema en cuestión. Aunque creo que es importante, está fuera del alcance de lo que estamos buscando hacer.

Así que instanciemos la clase Serielizer, inicialicemosla y después pasémosla al constructor de la página de submenú. El código en el archivo boostrap del complemento debería ahora verse así:

1
<?php
2
3
add_action( 'plugins_loaded', 'tutsplus_custom_admin_settings' );
4
/**

5
 * Starts the plugin.

6
 *

7
 * @since 1.0.0

8
 */
9
function tutsplus_custom_admin_settings() {
10
11
    $serializer = new Serializer();
12
    $serializer->init();
13
14
    $plugin = new Submenu( new Submenu_Page( $serializer ) );
15
    $plugin->init();
16
17
}

Con eso, estamos listos para continuar guardando nuestras opciones.

De Vuelta al Desarrollo

Regresemos al Serializer. Ahora que lo tenemos enlazado al resto del complemento, es tiempo de escribir algo de código así qué, como el comentario sugiere, verifiquemos el valor nonce que hemos creado en el front-end.

Afortunadamente, WordPress hace esto fácil a través de una función de API integrada: wp_verify_nonce. Esta función acepta dos argumentos:

  1. La acción
  2. El nombre

Si recuerdas del artículo anterior, usamos acme-settings-save como nuestra acción y acme-custom-message como nuestro valor nonce. Para validarlo, necesitamos revisar que existe en la colección $_POST y que pasa las revisiones nativas de WordPress.

Para hacer esto, me gusta crear un método private que me permite encapsular esta lógica en una función que puedo usar en la función save que definimos arriba.

1
<?php
2
3
/**

4
 * Determines if the nonce variable associated with the options page is set

5
 * and is valid.

6
 *

7
 * @access private

8
 * 

9
 * @return boolean False if the field isn't set or the nonce value is invalid;

10
 *                 otherwise, true.

11
 */
12
private function has_valid_nonce() {
13
14
    // If the field isn't even in the $_POST, then it's invalid.

15
    if ( ! isset( $_POST['acme-custom-message'] ) ) { // Input var okay.

16
        return false;
17
    }
18
19
    $field  = wp_unslash( $_POST['acme-custom-message'] );
20
    $action = 'acme-settings-save';
21
22
    return wp_verify_nonce( $field, $action );
23
24
}

Una vez hecho, puedo incorporar una llamada a esta función que nos permitirá revisar la validez del envío y ya sea salir de la rutina o proceder a la siguiente revisión (a la que llegaremos momentáneamente).

Nota que simplemente regresando falso en este condicional no es una manera adecuada de manejar esto. En su lugar, sería más claro introducir un mensaje de error que se muestre en el tablero de WordPress. Esto es algo que estaremos revisitando en un tutorial futuro.

Por ahora, sin embargo, estamos preocupados primariamente con asegurar que podemos enviar datos exitósamente. Esto nos trae a la siguiente porción de nuestro código.

Permiso

A pesar de que el número usado una vez (o el nonce) validación retirado, todavía hay una cosa más, necesitamos comprobar: necesita para asegurarse de que el usuario actual tiene permiso para guardar los datos.

Para nuestros propósitos, queremos asegurarnos de que el usuario actual es un administrador. Para hacer esto, podemos ver las capacidades del usuario actual (puedes ver que esta página proporciona una referencia para cada rol y sus capacidades asociadas).

Nota que una de las capacidades de la administración es manejar opciones. Ahora podemos usar la función de la API de WordPress current_user_can para revisar si el usuario actual puede guardar las opciones en esta página.

Pero primero, esto levanta la pregunta: ¿Si el usuario no puede guardar opciones, por qué debería tener permitido ver la página en primer lugar?

Si recuerdas de antes en la serie, escribimos el siguiente pedazo de código:

1
<?php
2
3
public function add_options_page() {
4
5
    add_options_page(
6
        'Tuts+ Custom Administration Page',
7
        'Custom Administration Page',
8
        'manage_options',
9
        'custom-admin-page',
10
        array( $this->submenu_page, 'render' )
11
    );
12
}

Esto asegura que la página de opciones solo está disponible para administradores; sin embargo, queremos ser extra cuidadosos y colocar una revisión para esto durante nuestro proceso de serialización, también.

Ahora podemos actualizar el condicional en donde también estamos revisando el valor nonce para revisar los permisos del usuario actual.

1
<?php
2
3
/**

4
 * Validates the incoming nonce value, verifies the current user has

5
 * permission to save the value from the options page and saves the

6
 * option to the database.

7
 */
8
public function save() {
9
10
    // First, validate the nonce and verify the user as permission to save.

11
    if ( ! ( $this->has_valid_nonce() && current_user_can( 'manage_options' ) ) ) {
12
        // TODO: Display an error message.

13
    }
14
15
    // If the above are valid, save the option.

16
17
}

Ahora que tenemos el código en posición para asegurar que el valor nonce es establecido y que el usuario actual puede guardar el valor, podemos continuar con la limpieza.

Recuerda, regresaremos al lugar en donde dice que tenemos que mostrar un mensaje de error. Pero es no es en este tutorial.

Limpieza

"Pero espera," tu dices. "¡Pensé que nos estábamos preparando para la opción guardar!" Lo estamos, pero antes de que podamos hacer eso tenemos que pasar por el proceso de limpieza. En resumen, la limpieza es la idea de asegurar que los datos están limpios, seguros y, ahem, sanitarios para la base de datos.

Puesto en términos simples, previene a usuarios maliciosos de insertar información en la base de datos que podría ultimadamente afectar negativamente a nuestro sitio.

Afortunadamente, WordPress proporciona una bonita función helper que nos permite asegurar que es tan fácil como es posible. Para aquellos interesados, puedes leer todo sobre validación y limpieza de datos (aunque estaremos viendo validación en el siguiente tutorial).

En nuestro código, vamos a estar usando sanitize_text_field (como se enlazó arriba). Esta función hará lo siguiente:

  • Revisa UTF-8 inválido
  • Convierte caracteres simples '<' a entidades
  • Quita todas las etiquetas
  • Remueve saltos de línea, tabulaciones y espacio blanco extra
  • Remueve octetos

Bastante bien tener esto disponible, ¿no es así? Pongámoslo a trabajar. Para hacerlo, ubica la función save en la que hemos estado trabajando y actualizala para que se vea así:

1
<?php
2
/**

3
 * Validates the incoming nonce value, verifies the current user has

4
 * permission to save the value from the options page and saves the

5
 * option to the database.

6
 */
7
public function save() {
8
9
    // First, validate the nonce and verify the user as permission to save.

10
    if ( ! ( $this->has_valid_nonce() && current_user_can( 'manage_options' ) ) ) {
11
        // TODO: Display an error message.

12
    }
13
14
    // If the above are valid, sanitize and save the option.

15
    if ( null !== wp_unslash( $_POST['acme-message'] ) ) {
16
17
        $value = sanitize_text_field( $_POST['acme-message'] );
18
        update_option( 'tutsplus-custom-data', $value );
19
20
    }
21
}

Nota que estamos leyendo la entrada de la colección $_POST, limpiándola y después guardando el resultado en una variable por separado. Después, la variable está siendo escrita en la base de datos usando la función update_option.

Para este artículo, estoy optando por usar la llave tutsplus-custom-data. Sea lo que uses, es importante que esté prefijo con algo único de manera que otro complemento o tema no sobreescriba la opción y no sobreescribas una opción existente.

Finalmente, necesitamos redirigir de vuelta la página de opciones. Ya que no estamos usando la API integrada, necesitamos escribir una función que haga esto por nosotros. Afortunadamente, es bastante sencillo.

Primero, crea una función llamada redirect, y asegúrate que se ve así:

1
<?php
2
/**

3
 * Redirect to the page from which we came (which should always be the

4
 * admin page. If the referred isn't set, then we redirect the user to

5
 * the login page.

6
 *

7
 * @access private

8
 */
9
private function redirect() {
10
11
    // To make the Coding Standards happy, we have to initialize this.

12
    if ( ! isset( $_POST['_wp_http_referer'] ) ) { // Input var okay.

13
        $_POST['_wp_http_referer'] = wp_login_url();
14
    }
15
16
    // Sanitize the value of the $_POST collection for the Coding Standards.

17
    $url = sanitize_text_field(
18
        wp_unslash( $_POST['_wp_http_referer'] ) // Input var okay.

19
    );
20
21
    // Finally, redirect back to the admin page.

22
    wp_safe_redirect( urldecode( $url ) );
23
    exit;
24
25
}

El código de arriba debería explicarse por sí solo, pero para asegurarnos de que es claro, esta haciendo lo siguiente:

  1. Revisa para asegurar que un valor privado de WordPress está presente en la colección $_POST. Si no está establecido, entonces se igualará a la URL de login de WordPress. Esto forzará a las personas a la página de inicio de sesión si la URL no está establecida; sin embargo, no hay razón para que no lo esté.
  2. Después, tomamos el referenciador y limpiamos los datos. Esto es algo que piden los estándares de código, y asegura que los datos están limpios.
  3. Finalmente, inicializamos wp_safe_redirect a la URL de manera que seamos regresados a la página de opciones.

Una vez que todo está hecho, agrega esta última línea en la función save de arriba. La versión final del código debería verse así:

1
<?php
2
/**

3
 * Performs all sanitization functions required to save the option values to

4
 * the database.

5
 *

6
 * @package Custom_Admin_Settings

7
 */
8
9
/**

10
 * Performs all sanitization functions required to save the option values to

11
 * the database.

12
 *

13
 * This will also check the specified nonce and verify that the current user has

14
 * permission to save the data.

15
 *

16
 * @package Custom_Admin_Settings

17
 */
18
class Serializer {
19
20
    /**

21
   * Initializes the function by registering the save function with the

22
	 * admin_post hook so that we can save our options to the database.

23
	 */
24
	public function init() {
25
		add_action( 'admin_post', array( $this, 'save' ) );
26
	}
27
28
	/**

29
	 * Validates the incoming nonce value, verifies the current user has

30
	 * permission to save the value from the options page and saves the

31
	 * option to the database.

32
	 */
33
	public function save() {
34
35
		// First, validate the nonce and verify the user as permission to save.

36
		if ( ! ( $this->has_valid_nonce() && current_user_can( 'manage_options' ) ) ) {
37
			// TODO: Display an error message.

38
		}
39
40
		// If the above are valid, sanitize and save the option.

41
		if ( null !== wp_unslash( $_POST['acme-message'] ) ) {
42
43
			$value = sanitize_text_field( $_POST['acme-message'] );
44
			update_option( 'tutsplus-custom-data', $value );
45
46
		}
47
48
		$this->redirect();
49
50
	}
51
52
	/**

53
	 * Determines if the nonce variable associated with the options page is set

54
	 * and is valid.

55
	 *

56
	 * @access private

57
	 *

58
	 * @return boolean False if the field isn't set or the nonce value is invalid;

59
	 *                 otherwise, true.

60
	 */
61
	private function has_valid_nonce() {
62
63
		// If the field isn't even in the $_POST, then it's invalid.

64
		if ( ! isset( $_POST['acme-custom-message'] ) ) { // Input var okay.

65
			return false;
66
		}
67
68
		$field  = wp_unslash( $_POST['acme-custom-message'] );
69
		$action = 'acme-settings-save';
70
71
		return wp_verify_nonce( $field, $action );
72
73
	}
74
75
	/**

76
	 * Redirect to the page from which we came (which should always be the

77
	 * admin page. If the referred isn't set, then we redirect the user to

78
	 * the login page.

79
	 *

80
	 * @access private

81
	 */
82
	private function redirect() {
83
84
		// To make the Coding Standards happy, we have to initialize this.

85
		if ( ! isset( $_POST['_wp_http_referer'] ) ) { // Input var okay.

86
			$_POST['_wp_http_referer'] = wp_login_url();
87
		}
88
89
		// Sanitize the value of the $_POST collection for the Coding Standards.

90
		$url = sanitize_text_field(
91
				wp_unslash( $_POST['_wp_http_referer'] ) // Input var okay.

92
		);
93
94
		// Finally, redirect back to the admin page.

95
		wp_safe_redirect( urldecode( $url ) );
96
		exit;
97
98
	}
99
}

Aquí está la cosa: Tenemos seguridad, limpieza, serialización y redireccionamiento en lugar. Pero no estamos mostrando mensajes de error y no estamos trayendo datos.

Ahí es donde continuaremos con el siguiente tutorial.

Conclusión

En este punto, tenemos un complemento semi-funcional, pero aún hay más trabajo por hacer. Obviamente, la información que estamos enviando a la base de datos no se muestra en ningún lugar, y eso no es algo bueno.

Pero al igual que guardando información, hay cosas más importantes a considerar cuando se recupera información. En el siguiente tutorial, veremos recuperación de información, mostrarla en el front-end, mostrándola en la página de opciones y también actualizando la información mientras un usuario cambia el valor del elemento de entrada.

Mientras tanto, si estás buscando otras utilidades para ayudarte a construir tu creciente conjunto de herramientas para WordPress o código para estudiar y volverte mejor versado en WordPress, no olvides ver lo que tenemos disponible en Envato Market.

Recuerda, puedes ver todos mis cursos y tutoriales en mi página de perfil, y puedes seguirme en mi blog y/o Twitter en @tommcfarlin en donde hablo de varias prácticas de desarrollo de software y cómo podemos emplearlas en WordPress.

Finalmente, no dudes en dejar cualquier pregunta o comentarios en la sección de abajo. Hago lo mejor por participar y contestar cada pregunta o crítica que ofreces relacionada con este proyecto.

Recursos