A evolução do WordPress de uma plataforma de blog para um CMS completo transforma isso em uma estrutura sólida para que os desenvolvedores criem projetos e aplicativos excelentes.

O núcleo do WordPress, alimenta não apenas o mecanismo de publicação dos usuários, mas também fornece aos desenvolvedores um conjunto robusto de classes, APIs e ajudantes, projetado para atender a uma ampla gama de necessidades.

Uma das gemas ocultas do WordPress que permite aos desenvolvedores realizar operações com o sistema de arquivos local de uma maneira segura e robusta é a API do sistema de arquivos do WordPress. Ele abstrai a funcionalidade de manipulação de arquivos em um conjunto de métodos comumente solicitados para que possam ser usados ​​com segurança em diferentes ambientes de hospedagem.

O escopo do problema

Pode haver vários motivos para querer gravar arquivos locais no código:

  • Registro de eventos ou operações executadas
  • Troca de dados com sistemas não WordPress
  • Cópia de segurança

Independentemente das motivações, escrever arquivos locais a partir do código PHP pode ser uma operação arriscada. Pelo menos duas armadilhas muito importantes devem ser levadas em conta ao implementar isso para um tema, plug-in ou instalação personalizada do WordPress:

  1. Segurança. Há um risco de propriedade de arquivo incorreta ao gravar arquivos locais com código (pelo servidor da Web). Esse problema surge em ambientes de hospedagem compartilhada mal configurados e pode levar à perda de controle sobre os arquivos.
  2. Compatibilidade. Devido à variedade de empresas de hospedagem existentes, a configuração do servidor do usuário em particular é geralmente desconhecida para o desenvolvedor. Assim, o desenvolvedor não pode ter certeza de que as permissões necessárias para uma operação de escrita sejam alcançadas pelo usuário do plug-in ou do tema.

Se um plug-in ou tema do WordPress que precisa gravar arquivos locais for destinado a lançamento público, o desenvolvedor deve ter sempre esses problemas em mente. A boa notícia é que o próprio WordPress já tem uma ferramenta para resolver esses problemas: a API do sistema de arquivos.

Introdução à API do sistema de arquivos do WordPress

A API do sistema de arquivos foi adicionada ao WordPress na versão 2.6 para habilitar o recurso de atualização do próprio WordPress. Ele abstrai a funcionalidade necessária para executar operações de leitura / gravação com segurança e em vários tipos de host. Ele consiste em um conjunto de classes e permite que você escolha automaticamente a maneira correta de se conectar ao sistema de arquivos local, dependendo da configuração do host individual.

A lógica por trás da API é bem simples; ele tenta gravar arquivos locais diretamente e, no caso de propriedade incorreta de arquivos, ele alterna para outro método baseado em FTP. Dependendo das bibliotecas PHP disponíveis, ele encontra uma maneira apropriada de configurar uma conexão FTP (via sockets de extensão ou SSH). Geralmente, as etapas a seguir são necessárias para trabalhar com arquivos locais:

Etapa 1. Detectar qual método de conexão está disponível

O WordPress usa o get_filesystem_method para detectar a disponibilidade dos seguintes métodos (da mais alta prioridade para a mais baixa) Direct, SSH2, FTP PHP Extension, FTP Sockets.

Etapa 2. Obtenha as credenciais necessárias para o método detectado

Se o transporte detectado precisar de credenciais de um usuário, o WordPress usará a função request_filesystem_credentials para exibir um formulário de solicitação. A função possui vários parâmetros que permitem preservar os dados entre os envios de formulário, solicitar credenciais várias vezes se a conexão falhar e direcionar para um diretório específico dentro da instalação do WordPress:

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

Fornecendo um parâmetro $ type vazio para a função, poderíamos forçá-lo a realizar a detecção dos métodos de conexão disponíveis, assim ele chamaria o get_filesystem_method para nós. Ao mesmo tempo, podemos forçar a função a usar qualquer tipo de conexão específico, especificando-a usando o argumento $ type.

Quando os dados de conexão exigidos pelo método escolhido não são fornecidos, a função imprime o formulário para solicitá-lo:

Conneciton information

Após a primeira solicitação, o WordPress armazena o nome de host e o nome de usuário do FTP no banco de dados para uso futuro, mas não armazena a senha. Como alternativa, as credenciais de FTP podem ser especificadas no arquivo wp-config.php usando as seguintes constantes:

  • FTP_HOST - o nome do host do servidor ao qual se conectar
  • FTP_USER - o nome de usuário para se conectar
  • FTP_PASS - a senha para se conectar com
  • FTP_PUBKEY - o caminho para a chave pública a ser usada para conexão SSH2
  • FTP_PRIKEY - o caminho para a chave privada para usar na conexão SSH2

Quando esses dados são armazenados no arquivo wp-config.php, o formulário de solicitação de credenciais não aparece, mas as desvantagens de segurança são significativas e os procedimentos de segurança devem ser triplamente verificados, com a maior atenção possível deve ser dada à segurança desse arquivo.

Etapa 3. Inicialize a classe do Sistema de Arquivos do WordPress e conecte-se ao sistema de arquivos

O coração da API do sistema de arquivos do WordPress é a função WP_Filesystem. Ele carrega e inicializa a classe de transporte apropriada, armazena uma instância obtida no objeto global $ wp_filesystem para uso posterior e tenta se conectar ao sistema de arquivos com as credenciais fornecidas:

WP_Filesystem($args, $context);

Etapa 4. Use os métodos do Sistema de Arquivos do WordPress para executar operações de leitura / gravação

Um objeto $ wp_filesystem adequadamente inicializado tem um conjunto de métodos para se comunicar com o sistema de arquivos local que poderia ser usado sem qualquer ansiedade adicional sobre o tipo de conexão. Em particular, existem os seguintes métodos comumente usados:

  • get_contents - lê o arquivo em uma string
  • put_contents - grava uma string em um arquivo
  • mkdir - cria um diretório
  • mdir - remove um diretório
  • wp_content_dir - retorna o caminho no sistema de arquivos local para a pasta wp-content
  • wp_plugins_dir - retorna o caminho no sistema de arquivos local para a pasta de plugins
  • wp_themes_dir - retorna o caminho no sistema de arquivos local para a pasta de temas

Juntando tudo, vamos criar um exemplo que execute as etapas acima mencionadas em uma situação simples - escreveremos algum texto enviado em uma área de texto em um arquivo .txt simples.

Observe que este exemplo é para fins de demonstração, em uma situação do mundo real em que você não armazenaria dados de texto simples em um arquivo .txt, seria uma solução muito mais robusta armazená-lo no banco de dados.

A API do sistema de arquivos do WordPress em ação

Vamos envolver nosso código em um plug-in separado, que será alocado em sua própria pasta de sistema de arquivos-demo. Isso nos fornece uma pasta de destino para armazenar o arquivo .txt e verificar as permissões de gravação.

Primeiro de tudo, vamos criar a página de demonstração para exibir nosso formulário no menu Ferramentas:

/*** 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 {// leu de fileif (false === ($ output = filesystem_demo_text_read ($ form_url))) {return;  // estamos exibindo formulário de credenciais sem necessidade de processamento adicional} elseif (is_wp_error ($ output)) {$error = $output->get_error_message();$output = '';}  } $ output = esc_textarea ($ output);  // fugindo para impressão?> 

Página de demonstração da API do sistema de arquivos

Ao exibir nossa página (filesystem_demo_screen), verificamos a disponibilidade do envio de texto. Se existir, tentamos escrevê-lo em um arquivo test.txt, caso contrário, tentamos encontrar esse arquivo na pasta do plugin e ler seu conteúdo para ser incluído na textarea. Finalmente, imprimimos um formulário básico para inserir texto. Por uma questão de legibilidade, essas operações de escrita e leitura foram separadas em suas próprias funções.

Filesystem API demo

Para evitar a duplicação das mesmas etapas de inicialização, o auxiliar compartilhado foi criado. Ele chama request_filesystem_credentials primeiro para detectar o método de conexão disponível e obter credenciais. Se isso foi bem sucedido, então chama WP_Filesystem para iniciar $ wp_filesystem com dados 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}

Escrever no código do arquivo é assim:

/*** 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;}

Nesta parte definimos alguns parâmetros necessários:

  • $ demotext - texto enviado para escrever
  • $ form_fields - item na matriz $ _POST que armazena nosso texto e deve ser preservado
  • método $ - método de transporte, deixamos em branco para detectar automaticamente
  • $ context - pasta de destino (a do plugin)

Depois disso, iniciamos o objeto global $ wp_filesystem usando a função auxiliar descrita anteriormente. Em caso de sucesso, detectamos o caminho correto para a pasta de destino e escrevemos o texto enviado nele usando o método put_contents do objeto $ wp_filesystem.

O código para leitura do arquivo é semelhante ao seguinte:

/*** 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;}

Essa função funciona da mesma maneira descrita anteriormente, mas usa get_contents para ler o arquivo de destino.

Conclusão

Ao trabalhar com arquivos locais, um desenvolvedor de plugins ou temas do WordPress entrará em contato com questões de segurança e compatibilidade, colocando uma enorme pressão sobre a equipe e adicionando longas horas ao ciclo de vida do projeto. Confiando na API do sistema de arquivos, esses problemas podem ser resolvidos de maneira eficiente. Então, da próxima vez que você se encontrar escrevendo fwrite no código do seu plugin, considere esta alternativa a opção mais saudável.

Você pode baixe uma demonstração desse código aqui e adapte-o às suas necessidades.