La evolución de WordPress desde una plataforma de blogs a un CMS completo, a la vez, lo convierte en un sólido marco para que los desarrolladores creen proyectos y aplicaciones sobresalientes.

El núcleo de WordPress no solo impulsa el motor de publicación de los usuarios, sino que también proporciona a los desarrolladores un sólido conjunto de clases, API y ayudantes, diseñados para satisfacer una amplia gama de necesidades.

Una de las gemas ocultas de WordPress que permite a los desarrolladores realizar operaciones con el sistema de archivos local de una manera segura y robusta es la API del sistema de archivos de WordPress. Se abstrae la funcionalidad de manipulación de archivos en un conjunto de métodos comúnmente solicitados para que puedan ser utilizados de forma segura en diferentes entornos de alojamiento.

El alcance del problema

Puede haber varias razones para querer escribir archivos locales en el código:

  • Registro de eventos u operaciones realizadas
  • Intercambio de datos con sistemas que no son de WordPress
  • Apoyo

Independientemente de las motivaciones, escribir archivos locales desde el código PHP puede ser una operación arriesgada. Se deben tener en cuenta al menos dos inconvenientes muy importantes al implementar esto para un tema de WordPress, un complemento o una instalación personalizada:

  1. Seguridad. Existe un riesgo de propiedad incorrecta del archivo al escribir archivos locales con código (por el servidor web). Este problema se produce en entornos de alojamiento compartido mal configurados y podría provocar la pérdida de control de los archivos.
  2. Compatibilidad. Debido a la variedad de empresas de alojamiento, la configuración del servidor del usuario en particular es desconocida para el desarrollador. Por lo tanto, el desarrollador no puede estar seguro de que los permisos necesarios para una operación de escritura sean alcanzables por el usuario del complemento o tema.

Si un plugin de WordPress o un tema que necesita escribir archivos locales está destinado a ser lanzado al público, el desarrollador debe tener constantemente en cuenta estos problemas. La buena noticia es que WordPress ya cuenta con una herramienta para abordar estos problemas: la API del sistema de archivos.

Introducción a la API del sistema de archivos de WordPress

La API del sistema de archivos se agregó a WordPress en la versión 2.6 para habilitar la función de actualización propia de WordPress. Resume la funcionalidad necesaria para realizar operaciones de lectura / escritura de forma segura y en una variedad de tipos de host. Consiste en un conjunto de clases y le permite elegir automáticamente la forma correcta de conectarse al sistema de archivos local, dependiendo de la configuración del host individual.

La lógica detrás de la API es bastante simple; intenta escribir archivos locales directamente y, en el caso de una propiedad incorrecta del archivo, cambia a otro método basado en FTP. Dependiendo de las bibliotecas PHP disponibles, encuentra una forma apropiada de configurar una conexión FTP (a través de sockets de extensión, o más de SSH). En general, se requieren los siguientes pasos para trabajar con archivos locales:

Paso 1. Detectar qué método de conexión está disponible

WordPress usa get_filesystem_method para detectar la disponibilidad de los siguientes métodos (desde la más alta a la más baja) Direct, SSH2, FTP PHP Extension, FTP Sockets.

Paso 2. Obtenga las credenciales requeridas para el método detectado

Si el transporte detectado necesita credenciales de un usuario, WordPress usa la función request_filesystem_credentials para mostrar un formulario de solicitud. La función tiene una serie de parámetros que le permiten conservar datos entre envíos de formularios, solicitar credenciales varias veces si la conexión falló y dirigirse a un directorio particular dentro de la instalación de WordPress:

request_filesystem_credentials($form_post, $type, $error, $context, $extra_fields);

Al suministrar un parámetro $ tipo vacío a la función, podríamos forzarlo a realizar la detección de los métodos de conexión disponibles, por lo que llamaría get_filesystem_method por nosotros. Al mismo tiempo, podemos forzar a la función a utilizar cualquier tipo de conexión particular especificando el uso del argumento $ type.

Cuando no se proporcionan los datos de conexión requeridos por el método elegido, la función imprime el formulario para solicitarlo:

Conneciton information

Después de la primera solicitud, WordPress almacena el nombre de host FTP y el nombre de usuario en la base de datos para su uso futuro, pero no almacena la contraseña. Alternativamente, las credenciales de FTP podrían especificarse en el archivo wp-config.php utilizando las siguientes constantes:

  • FTP_HOST - el nombre de host del servidor para conectarse a
  • FTP_USER - el nombre de usuario para conectarse con
  • FTP_PASS - la contraseña para conectarse
  • FTP_PUBKEY - la ruta a la clave pública para usar para la conexión SSH2
  • FTP_PRIKEY - la ruta a la clave privada para usar para la conexión SSH2

Cuando estos datos se almacenan en el archivo wp-config.php, el formulario de solicitud de credenciales no aparece, pero los inconvenientes de seguridad son significativos y los procedimientos de seguridad deben verificarse con la mayor atención posible para la seguridad de este archivo.

Paso 3. Inicializa la clase del sistema de archivos de WordPress y conéctate al sistema de archivos

El corazón de la API del sistema de archivos de WordPress es la función WP_Filesystem. Carga e inicializa la clase de transporte adecuada, almacena una instancia obtenida en el objeto global $ wp_filesystem para su uso posterior e intenta conectarse al sistema de archivos con las credenciales proporcionadas:

WP_Filesystem($args, $context);

Paso 4. Usa los métodos del sistema de archivos de WordPress para realizar operaciones de lectura / escritura

Un objeto $ wp_filesystem correctamente inicializado tiene un conjunto de métodos para comunicarse con el sistema de archivos local que podría usarse sin ninguna otra preocupación sobre el tipo de conexión. En particular, existen los siguientes métodos comúnmente utilizados:

  • get_contents - lee el archivo en una cadena
  • put_contents - escribe una cadena en un archivo
  • mkdir - crea un directorio
  • mdir - elimina un directorio
  • wp_content_dir - devuelve la ruta en el sistema de archivos local a la carpeta wp-content
  • wp_plugins_dir - devuelve la ruta en el sistema de archivos local a la carpeta de complementos
  • wp_themes_dir - devuelve la ruta en el sistema de archivos local a la carpeta de temas

Poniéndolo todo junto, vamos a encontrar un ejemplo que realice los pasos mencionados anteriormente en una situación simple: escribiremos un texto enviado en un área de texto en un archivo .txt simple.

Tenga en cuenta que este ejemplo es para fines de demostración; en una situación del mundo real, no almacenaría datos de texto simples en un archivo .txt; en su lugar, sería una solución mucho más sólida almacenarlos en la base de datos.

La API del sistema de archivos de WordPress en acción

Vamos a envolver nuestro código en un complemento separado, al que se le asignará su propia carpeta de sistema de archivos y demostración. Eso nos proporciona una carpeta de destino para almacenar el archivo .txt y verificar los permisos de escritura.

En primer lugar, creemos la página de demostración para mostrar nuestro formulario en el menú Herramientas:

/*** Create Demo page (under Tools menu)***/add_action('admin_menu', 'filesystem_demo_page');function filesystem_demo_page() {add_submenu_page( 'tools.php', 'Filesystem API Demo page', 'Filesystem Demo', 'upload_files', 'filesystem_demo', 'filesystem_demo_screen' );}function filesystem_demo_screen() {$form_url = "tools.php?page=filesystem_demo";$output = $error = '';/*** write submitted text into file (if any)* or read the text from file - if there is no submission**/if(isset($_POST['demotext'])){//new submissionif(false === ($output = filesystem_demo_text_write($form_url))){return; //we are displaying credentials form - no need for further processing}  elseif (is_wp_error ($ output)) {$error = $output->get_error_message();$output = '';}  } else {// read from fileif (false === ($ output = filesystem_demo_text_read ($ form_url))) {return;  // estamos mostrando credenciales sin necesidad de procesamiento adicional} elseif (is_wp_error ($ output)) {$error = $output->get_error_message();$output = '';}  } $ output = esc_textarea ($ output);  // escapando para imprimir?> 

Página de demostración de la API del sistema de archivos

Al mostrar nuestra página (filesystem_demo_screen) verificamos la disponibilidad de envío de texto. Si existe intentamos escribirlo en un archivo test.txt, de lo contrario, tratamos de encontrar dicho archivo en la carpeta del plugin y leemos su contenido para incluirlo en textarea. Finalmente, imprimimos un formulario básico para ingresar texto. En aras de la legibilidad, estas operaciones de escritura y lectura se separaron en sus propias funciones.

Filesystem API demo

Para evitar la duplicación de los mismos pasos de inicialización, se ha creado el auxiliar compartido. Primero llama a request_filesystem_credentials para detectar el método de conexión disponible y obtener credenciales. Si fue exitoso, entonces llama a WP_Filesystem para iniciar $ wp_filesystem con datos dados.

/*** Initialize Filesystem object** @param str $form_url - URL of the page to display request form* @param str $method - connection method* @param str $context - destination folder* @param array $fields - fileds of $_POST array that should be preserved between screens* @return bool/str - false on failure, stored text on success**/function filesystem_init($form_url, $method, $context, $fields = null) {global $wp_filesystem;/* first attempt to get credentials */if (false === ($creds = request_filesystem_credentials($form_url, $method, false, $context, $fields))) {/*** if we comes here - we don't have credentials* so the request for them is displaying* no need for further processing**/return false;}/* now we got some credentials - try to use them*/if (!WP_Filesystem($creds)) {/* incorrect connection data - ask for credentials again, now with error message */request_filesystem_credentials($form_url, $method, true, $context);return false;}return true; //filesystem object successfully initiated}

La escritura en el código de archivo se ve así:

/*** Perform writing into file** @param str $form_url - URL of the page to display request form* @return bool/str - false on failure, stored text on success**/function filesystem_demo_text_write($form_url){global $wp_filesystem;check_admin_referer('filesystem_demo_screen');$demotext = sanitize_text_field($_POST['demotext']); //sanitize the input$form_fields = array('demotext'); //fields that should be preserved across screens$method = ''; //leave this empty to perform test for 'direct' writing$context = WP_PLUGIN_DIR . '/filesystem-demo'; //target folder$form_url = wp_nonce_url($form_url, 'filesystem_demo_screen'); //page url with nonce valueif(!filesystem_init($form_url, $method, $context, $form_fields))return false; //stop further processign when request form is displaying/** now $wp_filesystem could be used* get correct target file first**/$target_dir = $wp_filesystem->find_folder($context);$target_file = trailingslashit($target_dir).'test.txt';/* write into file */if(!$wp_filesystem->put_contents($target_file, $demotext, FS_CHMOD_FILE))return new WP_Error('writing_error', 'Error when writing file'); //return error objectreturn $demotext;}

En esta parte definimos algunos parámetros necesarios:

  • $ demotext - texto enviado para escribir
  • $ form_fields: elemento de la matriz $ _POST que almacena nuestro texto y debe conservarse
  • Método $ - método de transporte, lo dejamos en blanco para detectar automáticamente
  • $ context - carpeta de destino (la del complemento)

Después de eso, iniciamos el objeto global $ wp_filesystem utilizando la función auxiliar que describí anteriormente. En caso de éxito, detectamos la ruta correcta a la carpeta de destino y escribimos el texto enviado utilizando el método put_contents del objeto $ wp_filesystem.

El código para leer del archivo se ve así:

/*** Read text from file** @param str $form_url - URL of the page where request form will be displayed* @return bool/str - false on failure, stored text on success**/function filesystem_demo_text_read($form_url){global $wp_filesystem;$demotext = '';$form_url = wp_nonce_url($form_url, 'filesystem_demo_screen');$method = ''; //leave this empty to perform test for 'direct' writing$context = WP_PLUGIN_DIR . '/filesystem-demo'; //target folderif(!filesystem_init($form_url, $method, $context))return false; //stop further processing when request forms displaying/** now $wp_filesystem could be used* get correct target file first**/$target_dir = $wp_filesystem->find_folder($context);$target_file = trailingslashit($target_dir).'test.txt';/* read the file */if($wp_filesystem->exists($target_file)){ //check for existence$demotext = $wp_filesystem->get_contents($target_file);if(!$demotext)return new WP_Error('reading_error', 'Error when reading file'); //return error object}return $demotext;}

Esta función funciona de la misma manera que la descrita anteriormente, pero usa get_contents para leer desde el archivo de destino.

Conclusión

Cuando trabaje con archivos locales, un desarrollador de temas o complementos de WordPress entrará en contacto con problemas de seguridad y compatibilidad, lo que generará una gran tensión en el equipo y le agregará muchas horas al ciclo de vida del proyecto. Confiando en la API del Sistema de Archivos, estos problemas pueden desviarse de manera eficiente. Así que la próxima vez que se encuentre escribiendo fwrite en el código de su complemento, considere esta alternativa como la opción más saludable.

Usted puede descarga una demostración de este código aquí y adaptarlo a tus necesidades