WordPress - Criando um formulário de contato sem plugin - Parte 3

Depois de aprendermos como criar a view para o formulário, de forma que ela converse com o WP, e inputar o resultado na tela, agora é hora de tratar os dados e enviar o e-mail! Follow-me! :D

A primeira coisa a se fazer, é verificar se essas informações estão vindo através de um verbo POST, pois é assim que o nosso formulário foi configurado para enviar a mensagem. O usuário não deve visualizar nenhuma mensagem antes de executar a ação de enviar o formulário.

No arquivo services/service-contact-form.php, faça uma adição no método send_contact_form():

1
2
3
4
5
6
7
8
9
public function send_contact_form() {
if( !isset( $_POST[ 'field_name' ] ) ) {
return false;
}
return (object) array(
'status' => 'success',
'message' => 'Seu e-mail foi enviado com sucesso!'
);
}

Pronto! Agora a mensagem só será mostrada se o campo field_name for enviado via POST. Poderíamos testar por qualquer campo do nosso formulário. Só não testamos por !isset( $_POST ), pois $_POST é uma superglobal do PHP, e está sempre disponível, ou seja, isso retornaria sempre true.

O próximo passo é verificar se todos os campos obrigatórios foram preenchidos:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public function send_contact_form() {
if( !isset( $_POST[ 'field_name' ] ) ) {
return false;
}

$all_fields = $this->get_fields();
$are_all_fields_ok = $this->are_all_fields_ok( $all_fields );

if( ! $fields_its_ok ) {
// do something
}

return (object) array(
'status' => 'success',
'message' => 'Seu e-mail foi enviado com sucesso!'
);
}

Passamos para a variável $all_fields o retorno do método get_fields(). Ainda precisamos criá-lo. Depois, passamos para $are_all_fields_ok o retorno do método are_all_fields_ok( $all_fields ). Com isso, podemos verificar se todos os campos que deveriam estar preenchidos, foram feitos corretamente.

Vamos criar então o método `get_fields(), que retorna um array dos campos, com o valor dele, e se ele é obrigatório. Assim fica mais fácil e organizado para tratar os dados:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private function get_fields() {
$fields = array(
'name' => array(
'value' => sanitize_text_field( $_POST[ 'field_name' ] ),
'is_required' => true
),
'email' => array(
'value' => sanitize_email( $_POST[ 'field_email' ] ),
'is_required' => true
),
'subject' => array(
'value' => sanitize_text_field( $_POST[ 'field_subject' ] ),
'is_required' => false
),
'message' => array(
'value' => wp_kses( $_POST[ 'field_message' ], 'br' ),
'is_required' => true
)
);

return $fields;
}

Se não estivéssemos obtendo o resultado de um POST, já teríamos retornado false na primeira verificação. Logo, podemos usar esse superglobal aqui com segurança.

Criamos o nosso método como private, pois não precisamos que ele seja acessado externamente. Basicamente o que esse método faz é criar um array com cada campo, onde o valor do campo é passado para value e se o campo é obrigatório ou não, é passado para is_required. O WordPress tem uma função chamada sanitize_email() (usada acima), que faz a limpeza dos caracteres do campo. Se houver qualquer tentativa de inserção de tags HTML, script, ou qualquer código malicioso, o valor não será enviado. Só serão aceitos caracteres válidos para um campo de e-mail. O mesmo para sanitize_text_field().

Já a função wp_kses() - KSES Strips Evil Scripts - remove tags HTML. No segundo parâmetro você pode passar as tags permitidas. No nosso caso, vamos permitir somente quebras de linha (br).

Em posts futuros, falaremos com mais detalhes sobre essas funções. Por enquanto é isso que você precisa saber :)

Já criamos nosso método que retorna todos os campos, agora precisamos verificar se eles são válidos. Crie o método are_all_fields_ok():

1
2
3
4
5
6
7
8
private function are_all_fields_ok( $fields ) {
foreach( $fields as $field ) {
if( $this->is_field_required_empty( $field ) ) {
return false;
}
}
return true;
}

Fazemos um foreach para passar por todos os campos passados como parâmetro e, se algum campo obrigatório não estiver preenchido, retornamos false. Se todos os campos obrigatórios estiverem preenchidos, então retornamos true.

Perceba que aqui, para verificar se algum campo obrigatório está em branco, nós usamos o método is_field_required_empty(), passando como parâmetro o campo que está sendo iterado no foreach.

Vamos criar esse método?

1
2
3
private function is_field_required_empty( $field ) {
return $field[ 'is_required' ] && empty( $field[ 'value' ] );
}

Pronto! Essa função verifica se nosso capo é obrigatório e se ele está vazio! Se estiver, então não devemos enviar o formulário! Vamos mostrar uma mensagem de erro ao usuário. Edite o método send_contact_form():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public function send_contact_form() {
if( !isset( $_POST[ 'field_name' ] ) ) {
return false;
}

$all_fields = $this->get_fields();
$are_all_fields_ok = $this->are_all_fields_ok( $all_fields );

if( ! $are_all_fields_ok ) {
return (object) array(
'message' => 'Preencha todos os campos!',
'status' => 'error'
);
}
}

Se você tentar fazer o envio do formulário agora, sem preencher todos os campos obrigatórios, você irá ver a mensagem de erro :D

Legal! E agora, qual o próximo passo?

O próximo passo é verificar se não estamos recebendo algum SPAM, testando aquele campo que deixamos oculto.

Vamos fazer primeiro a validação no nosso método send_contact_form():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public function send_contact_form() {
if( !isset( $_POST[ 'field_name' ] ) ) {
return false;
}

$all_fields = $this->get_fields();
$are_all_fields_ok = $this->are_all_fields_ok( $all_fields );

if( ! $are_all_fields_ok ) {
return (object) array(
'message' => 'Preencha todos os campos!',
'status' => 'error'
);
}

if( $this->is_spam() ) {
return (object) array(
'message' => 'Erro ao enviar mensagem. Tente novamente mais tarde.',
'status' => 'error'
);
}
}

É hora de criar um outro método para verificação. Vamos chamá-lo de is_spam(), para verificar se tem algum robô engraçadinho tentando nos sacanear:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private function is_spam() {
if( $this->has_malicious_content() ||
$this->has_more_than_three_links() ||
$this->spam_field_is_filled()
) {
return true;
}

return false;
}

private function has_malicious_content() {
return preg_match( '/bcc:|cc:|multipart|\[url|\[link|Content-Type:/i', implode( $_POST ) );
}

private function has_more_than_three_links() {
return preg_match_all( '/<a|http:/i', implode( $_POST ) ) > 3;
}

private function spam_field_is_filled() {
return !empty( $_POST[ 'field_mail' ] );
}

Criamos 3 novos métodos para verificação de SPAM: O método has_malicious_content(), irá verificar se foi inserido algo com bcc:, cc:, multipart, [url, [link e Content-Type, ignorando case sensitive (/i), em todos os campos.

O has_more_than_three_links(), verifica se existem mais de 3 links sendo enviados. Se você achar que 3 é muito, pode diminuir para o seu caso. Aqui usamos preg_match_all ao invés de preg_match, pois ele retorna a quantidade de vezes que um valor da regex foi encontrado;

E por último, mas não menos importante, temos o spam_field_is_filled(), onde verificamos o nosso campo de e-mail fake, pra saber se ele foi preenchido.

Se qualquer uma dessas condições for verdadeira, entendemos que estão tentando nos enviar SPAM, e ignoramos o envio, retornando true para is_spam(). Caso contrário, retornamos false.

Não precisamos do else pois, sempre que um valor é retornado (usando return), o PHP nem continua a leitura do que vem depois desse código. Assim deixamos também nosso método mais legível.

Bom, se olharmos para o nosso método principal, dá pra perceber que ele está um pouco verboso demais. Estamos retornando dois arrays (convertidos para objeto), com a mesma estrutura, mas que muda somente a mensagem interna. Vamos deixar isso mais organizado, e colocar essas mensagens também em um método. Até porque, ainda vamos precisar de mais uma mensagem: a de sucesso.

Editando então nosso método send_contact_form():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public function send_contact_form() {
if( !isset( $_POST[ 'field_name' ] ) ) {
return false;
}

$all_fields = $this->get_fields();
$are_all_fields_ok = $this->are_all_fields_ok( $all_fields );

if( ! $are_all_fields_ok ) {
return $this->get_status_message( 'error' );
}

if( $this->is_spam() ) {
return $this->get_status_message( 'error_sent' );
}
}

Vamos obter, através de um método chamado get_status_message(), as mensagens de status, conforme o parâmetro passado para esse método.

Nosso método get_status_message() vai ficar assim:

1
2
3
4
5
6
7
8
private function get_status_message( $status = 'success' ) {
$message = array(
'success' => $this->get_status_message_success(),
'error' => $this->get_status_message_error(),
'error_sent' => $this->get_status_message_error_sent()
);
return (object) $message[ $status ];
}

Não adianta nada tirar uma tripa de arrays do método principal e simplesmente jogar em outro método, concorda? Por isso, vamos criar um método para cada mensagem. Passamos como padrão o status success, ou seja, se esse método for chamado sem nenhum parâmetro, retornamos a mensagem de sucesso :)

No final, o array retornado é o objeto que precisamos na nossa view :D

Vamos ver como irão ficar esses 3 novos métodos com os status:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private function get_status_message_success() {
return array(
'message' => 'Mensagem enviada com sucesso!',
'status' => 'success'
);
}

private function get_status_message_error() {
return array(
'message' => 'Preencha todos os campos!',
'status' => 'error'
);
}

private function get_status_message_error_sent() {
return array(
'message' => 'Erro ao enviar mensagem. Tente novamente mais tarde.',
'status' => 'error'
);
}

Ah, agora sim! Bem melhor! Cada método fica pequeno, legível, e com uma responsabilidade única :)

Agora, se você tentar enviar o e-mail, preenchendo o campo de e-mail fake, vai receber uma mensagem de erro :D

Estamos quase lá! Vamos então fazer a última verificação: tentar enviar o e-mail, e verificar se este foi enviado com sucesso. No nosso método send_contact_form(), adicione:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public function send_contact_form() {
if( !isset( $_POST[ 'field_name' ] ) ) {
return false;
}

$all_fields = $this->get_fields();
$are_all_fields_ok = $this->are_all_fields_ok( $all_fields );

if( ! $are_all_fields_ok ) {
return $this->get_status_message( 'error' );
}

if( $this->is_spam() ) {
return $this->get_status_message( 'error_sent' );
}

if( ! $this->message_sent( $all_fields ) ) {
return $this->get_status_message( 'error_sent' );
}

return $this->get_status_message( 'success' );
}

Nessa última verificação, vamos ver se a mensagem foi enviada corretamente. Invocamos o método message_sent(), passando os campos como parâmetro já dentro de um if, pois o retorno dele vai nos dizer se a mensagem foi enviada com sucesso ou não.

Se não foi, retornamos a mesma mensagem que é enviada quando o usuário tenta enviar um SPAM. Aqui você pode criar sua própria mensagem se preferir. Como é a mesma mensagem, poderíamos ter juntado, no mesmo if do is_spam(). Mas assim facilita a leitura, e você consegue ver exatamente quais passos estão sendo feitos antes de tentar enviar a mensagem.

E o último return, envia a mensagem com status de sucesso. Se não deu nenhum erro, quer dizer que foi tudo bem :)
O parâmetro success é opcional, pois deixamos setado como padrão no método get_status_message(). Vamos deixar assim só para ficar mais legível ;)

Vamos então criar nosso método que envia o e-mail:

1
2
3
4
5
6
7
8
9
10
11
private function message_sent( $fields ) {
$to = 'quem-recebe-o@email.com';
$subject = 'E-mail enviado pelo site lalala.com.br - ' . $fields[ 'subject' ][ 'value' ];
$message = $this->message_to_sent( $fields );
$headers = array(
'Reply-To: ' . $fields[ 'email' ][ 'value' ]
);
$attachments = '';

return wp_mail( $to, $subject, $message, $headers, $attachments );
}

Aqui executamos o retorno da função wp_mail(), que retorna true se conseguiu enviar o e-mail, e false, caso contrário. Mas antes, nós configuramos algumas variáveis, até para saber a ordem correta dos parâmetros passados para a função wp_mail().

O primeiro é o $to, que é o e-mail que vai receber a mensagem. O segundo, $subject, é o assunto do e-mail. No nosso formulário já tem esse campo, mas ele não é obrigatório. Vamos então deixar uma mensagem padrão antes de concatenar o valor desse campo, para que a gente saiba que o e-mail foi enviado pelo formulário de contato do site.

Como nós vamos configurar nossa mensagem para ser enviada em HTML, vamos colocá-la em um método separado, para não deixar esse muito grande.

Ainda podemos configurar alguns $headers, como Reply-To:, que é o e-mail para quem nós devemos responder, ao clicar no botão Responder do seu e-mail. Podemos também configurar para recebermos uma cópia (Cc:), ou cópia oculta (Bcc:) em outro e-mail. Se esse formulário envia e-mail para o seu cliente, é importante saber se os e-mails estão chegando corretamente, então você pode configurar esses campos de cópia. Ou ainda simplesmente para enviar para mais de uma pessoa de uma só vez.

Não vamos enviar nenhum anexo, então deixamos o $attachments em branco.

Agora só falta nosso método que gera a mensagem:

1
2
3
4
5
6
7
private function message_to_sent( $fields ) {
$message = sprintf( '<h1>Assunto: %s</h1>', $fields[ 'subject' ][ 'value' ] );
$message .= sprintf( '<h2>De: %s</h2>', $fields[ 'email' ][ 'value' ] );
$message .= sprintf( '<h2>Mensagem:</h2><p>%s</p>', $fields[ 'message' ][ 'value' ] );

return $message;
}

Um HTML simples, que retorna uma string com algumas tags, para deixar o e-mail mais organizado. Utilizamos sprintf() aqui para substituir os %s, passados na string. A partir do segundo parâmetro dessa função, o PHP começa a substituir cada %s por um desses parâmetros, nessa ordem.

No final, retornamos a string da mensagem :)

E assim nós terminamos nosso formulário de contato, com várias validações importantes, e sem precisar de plugin algum! Claro que poderíamos fazer muitas outras melhorias, como por exemplo, colocar um metabox na página de contato para que o cliente adicione os e-mails que ele quer usar para receber o contato, mas isso é assunto para outro post!

Até o próximo! :D

Link para o índice dessa série:
https://blog.da2k.com.br/2015/01/11/indice-da-serie-como-criar-temas-para-wordpress/

Post anterior:
https://blog.da2k.com.br/2015/02/24/wordpress-criando-um-formulario-de-contato-sem-plugin-parte-2/

Próximo post:
EM BREVE!

Sobre o #1postperday: https://blog.da2k.com.br/2014/12/31/um-post-por-dia/

Tem alguma sugestão para os próximos posts do #1postperday? Deixe ela aqui: https://github.com/fdaciuk/fdaciuk.github.io/issues/1