Captcha simples com aúdio em PHP

Veja neste artigo como desenvolver um sistema de captcha simples com áudio na linguagem PHP, facilitando o acesso à funcionalidade para deficientes visuais.

Em muitos sites as vezes é necessário implementar “captchas”, ou seja, sistemas de verificação de autenticidade do usuário que consistem em um campo para o usuário digitar os caracteres visualizados em uma imagem.

No portal DevMedia existe um tutorial bastante simples de compreender e implementar utilizando a linguagem PHP, o qual pode ser encontrado nesse link.

Porém, levando em consideração as principais regras de acessibilidade para páginas web, percebemos que para deficientes visuais esse sistema pode se tornar bastante complicado e até mesmo impossível de utilizar. Para suprir essa necessidade, o autor Fernando Samboni publicou um artigo em seu blog onde ensina a fazer um captcha com áudio.

Neste artigo será apresentada uma implementação conjunta das duas soluções citadas, buscando aproveitar o melhor de cada um dos códigos.

Faremos 3 arquivos em PHP, precisaremos também de um arquivo de fonte, uma pasta com ícones e uma pasta com áudio de todas as letras do alfabeto e números de 0 a 9:

<?php class Captcha { var $palavra; // Variável que receberá o captcha var $entrada; // Variável que receberá o captcha digitado var $validado; //Variável que sinalizará se o captcha é valido ou não var $largura; // Definida no arquivo saída.php var $altura; // Definida no arquivo saída.php var $tamanho_fonte; // Definida no arquivo saída.php var $quantidade_letras; // Definida no arquivo saída.php function Captcha() { if (!isset($_SESSION)) session_start(); // Inicia uma nova sessão caso não tenha nenhuma já iniciada } function check($palavra) { $this->entrada = $palavra; //Recebe a palavra digitada $this->validar(); // Chama a função validar return $this->validado; // Retorna se é valido ou não (true or false) } function validar() { // Função que compara o código captcha digitado com o da sessão if ( isset($_SESSION['palavra']) && !empty($_SESSION['palavra']) ) { if ($_SESSION['palavra'] == strtolower(trim($this->entrada)) ) { $this->validado = true; // Se os códigos são iguais, a variável validado da classe recebe true $_SESSION['palavra'] = ''; } else { $this->validado = false; // Se são diferentes, a variável recebe false } } else { $this->validado = false; // Se não há um código captcha na sessão, a variável recebe false } } function palavra() { // Função que cria o código captcha $this->palavra = false; // Atribui false à variável da classe que receberá o código captcha // Gera e atribui à variável da sessão o código captcha de acordo com o número de letras desejado $this->palavra = substr(str_shuffle("ABCDEFGHIJKLMNPOQRSTUVYXWZ0123456789"),0, ($this->quantidade_letras)); $this->salvar(); // Chama a função que salva o código gerado na sessão } function getPalavra() { // Se houver um código captcha na sessão, retorna este código. Senão, retorna nada. if (isset($_SESSION['palavra']) && !empty($_SESSION['palavra'])) { return $_SESSION['palavra']; } else { return ''; } } function salvar() { // Função que salva o código captcha na sessão $_SESSION['palavra'] = strtolower($this->palavra); } function show() { header("Expires: Sun, 1 Jan 2000 12:00:00 GMT"); header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT"); header("Cache-Control: no-store, no-cache, must-revalidate"); header("Cache-Control: post-check=0, pre-check=0", false); header("Pragma: no-cache"); $tamanho = $this->tamanho_fonte; //Passa para a variável da função uma variável da classe $quantidade = $this->quantidade_letras; //Passa para a variável da função uma variável da classe $this->palavra(); //Passa para a variável da função uma variável da classe $palavra = $this->palavra; //Passa para a variável da função uma variável da classe $altura = $this->altura; //Passa para a variável da função uma variável da classe $largura = $this->largura; //Passa para a variável da função uma variável da classe $imagem = imagecreate($largura,$altura); // define a largura e a altura da imagem $fonte = "arial.ttf"; //voce deve ter essa ou outra fonte de sua preferencia em sua pasta $preto = imagecolorallocate($imagem,0,0,0); // define a cor preta $branco = imagecolorallocate($imagem,255,255,255); // define a cor branca for($i = 1; $i <= $quantidade; $i++){ // atribui as letras a imagem imagettftext($imagem,$tamanho,rand(-25,25),(($tamanho + 10)*$i), ($tamanho + 10),$branco,$fonte,substr($palavra,($i-1),1)); } imagejpeg($imagem); // gera a imagem imagedestroy($imagem); // limpa a imagem da memoria } function getAudibleCode() { $letras = array(); // Cria um array $palavra = $this->getPalavra(); // A variável recebe um valor retornado pela função getPalavra if ($palavra == '') { // Verifica se há um código captcha $this->palavra(); // Se não houver, chamamos a função palavra, que cria um código captcha $palavra = $this->getPalavra(); // A variável recebe um valor retornado pela função getPalavra } for($i = 0; $i < strlen($palavra); ++$i) { // Atribui uma letra do código captcha para cada item do array $letras[] = $palavra{$i}; } return $this->gerarWAV($letras); // Chama a função que gera o arquivo de som de acordo com o array letras } function gerarWAV($letras) { // Função que gera e retorna o arquivo de som $first = true; // use first file to write the header... $data_len = 0; $files = array(); $out_data = ''; $audio_path = './audio/'; foreach ($letras as $letra) { $filename = $audio_path . strtoupper($letra) . '.wav'; $fp = fopen($filename, 'rb'); $file = array(); $data = fread($fp, filesize($filename)); // read file in $header = substr($data, 0, 36); $body = substr($data, 44); $data = unpack('NChunkID/VChunkSize/NFormat/NSubChunk1ID/ VSubChunk1Size/vAudioFormat/vNumChannels/VSampleRate/ VByteRate/vBlockAlign/vBitsPerSample', $header); $file['sub_chunk1_id'] = $data['SubChunk1ID']; $file['bits_per_sample'] = $data['BitsPerSample']; $file['channels'] = $data['NumChannels']; $file['format'] = $data['AudioFormat']; $file['sample_rate'] = $data['SampleRate']; $file['size'] = $data['ChunkSize'] + 8; $file['data'] = $body; if ( ($p = strpos($file['data'], 'LIST')) !== false) { // If the LIST data is not at the end of the file, this will probably break your sound file $info = substr($file['data'], $p + 4, 8); $data = unpack('Vlength/Vjunk', $info); $file['data'] = substr($file['data'], 0, $p); $file['size'] = $file['size'] - (strlen($file['data']) - $p); } $files[] = $file; $data = null; $header = null; $body = null; $data_len += strlen($file['data']); fclose($fp); } $out_data = ''; for($i = 0; $i < sizeof($files); ++$i) { if ($i == 0) { // output header $out_data .= pack('C4VC8', ord('R'), ord('I'), ord('F'), ord('F'), $data_len + 36, ord('W'), ord('A'), ord('V'), ord('E'), ord('f'), ord('m'), ord('t'), ord(' ')); $out_data .= pack('VvvVVvv', 16, $files[$i]['format'], $files[$i]['channels'], $files[$i]['sample_rate'], $files[$i]['sample_rate'] * (($files[$i]['bits_per_sample'] * $files[$i]['channels']) / 8), ($files[$i]['bits_per_sample'] * $files[$i]['channels']) / 8, $files[$i]['bits_per_sample'] ); $out_data .= pack('C4', ord('d'), ord('a'), ord('t'), ord('a')); $out_data .= pack('V', $data_len); } $out_data .= $files[$i]['data']; } return $out_data; } }
Listagem 1. Arquivo classcaptcha.php

Neste arquivo, temos a classe Captcha que, por sua vez, possui várias variáveis e funções. Vamos listar variáveis e funções e ver uma explicação rápida do que cada uma faz.

Variáveis.

Estas variáveis estão declaradas fora das funções contidas na classe Captcha porque são utilizadas por mais de uma função ou porque são definidas em algum outro arquivo que chama a classe, que é o caso das variáveis que são utilizadas na construção da imagem e são definidas no arquivo saida.php.

Funções:

Vimos até aqui as variáveis e funções presentes na classe captcha. Para utilizar suas variáveis e funções em um arquivo diferente daquele que contém a classe, primeiro precisamos incluir o arquivo da classe no nosso arquivo externo (include("classcaptcha.php");). Depois, precisamos associá-la a uma variável, como visto no arquivo index.php ou saida.php ($img = new Captcha();). Para atribuir um valor a uma variável global da classe (aquelas que estão declaradas fora das funções), utilizamos o nome da variável à qual a classe foi associada em nosso arquivo externo seguido dos caracteres "->" e o nome da variável, como visto no arquivo saida.php, onde atribuímos valores a algumas variáveis relacionadas à imagem ($img->altura = 50;). Para chamar uma função desta classe no arquivo externo, o processo é o mesmo, sempre não se esquecendo de abrir e fechar parênteses depois do nome da função. Caso a função necessite de parâmetro(s), passamos este(s) entre os parênteses (sem parâmetro: $img->show(); com parâmetro: $img->check($_POST['code']);). Para utilizarmos as variáveis e funções dentro do arquivo da classe, trocamos o nome da variável associada à classe dos exemplos anteriores por $this, que representa o arquivo que contém as variáveis e funções (Variável: $this->entrada; Função sem parâmetro: $this->salvar(); Função com parâmetro: $this->gerarWAV($letras);).

Fora a adaptação do script para uma classe, vamos às modificações feitas em relação ao código do Darlan.

Primeira modificação

imagettftext($imagem, $tamanho_fonte,rand(-25,25), ($tamanho_fonte*$i), ($tamanho_fonte + 10), $branco, $fonte, substr($palavra,($i-1),1));
Listagem 2. Primeiro trecho do código original
imagettftext($imagem, $tamanho, rand(-25,25), (($tamanho + 10)*$i), ($tamanho + 10), $branco, $fonte, substr($palavra,($i-1),1));
Listagem 3. Primeiro trecho modificado

Notem que, na parte do código referente ao posicionamento x (horizontal) da letra na imagem ($tamanho_fonte*$i), soma-se 10 ao tamanho da fonte (($tamanho + 10)*$i). A explicação é simples. Dar um espaçamento maior entre as letras do código captcha na imagem, para melhor visualização de usuários com baixa visão.

Segunda Modificação

$palavra = substr(str_shuffle("AaBbCcDdEeFfGgHhIiJjKkLlMmNnPpQqRrSsTtUuVvYyXxWwZz23456789"), 0,($quantidade_letras));
Listagem 4. Segundo trecho de código original
$this->palavra = substr(str_shuffle("ABCDEFGHIJKLMNPOQRSTUVYXWZ0123456789"), 0,($this->quantidade_letras));
Listagem 5. Segundo trecho

Notem que, no código original, há letras maiúsculas e minúsculas juntas e não estão presentes o número “1” e a letra “O”, provavelmente pelo motivo de causarem confusão com o número “0” e as letras “I” maiúscula e “L” minúscula. A letra e número ausentes foram adicionados ao conjunto de caracteres disponível para o código. Em contrapartida, as letras minúsculas foram retiradas do mesmo conjunto, por dois motivos simples. O primeiro motivo é que usuários com baixa visão costumam aprender a ler e escrever com letra bastão, ou maiúscula. O segundo, é que no arquivo de áudio não será especificado ao usuário se a letra é maiúscula ou minúscula. Portanto, o código é gerado com letras maiúsculas, mas na parte que salva o código na sessão, as letras são passadas para minúsculo.

function salvar() { $_SESSION['palavra'] = strtolower($this->palavra); }
Listagem 6. Convertendo letras para minúsculo

Da mesma forma, quando é feita a validação, a comparação entre o código digitado e o gerado, o código digitado também é passado para minúsculo, por não haver distinção entre maiúsculo e minúsculo no arquivo de áudio.

function validar() { // Função que compara o código captcha digitado com o da sessão if ( isset($_SESSION['palavra']) && !empty($_SESSION['palavra']) ) { if ($_SESSION['palavra'] == strtolower(trim($this->entrada)) ) { $this->validado = true $_SESSION['palavra'] = ''; } else { $this->validado = false; } } else { $this->validado = false; } }
Listagem 7. Função de validação do código digitado
<?php if (isset($_POST["code"])) { // Se o formulário foi submetido, entraremos nesta condição include("classcaptcha.php"); // Inclui a classe captcha $img = new Captcha(); // Chamamos a classe $valid = $img->check($_POST['code']); // Checa se o captcha é válido de acordo com o que foi submetido por formulário if($valid == true) echo "<a href='#' onclick='return false;'> <font color='#006600'><strong>Código correto!</strong> </font></a><br />"; else echo "<a href='#' onclick='document.getElementById(\"code\").focus(); return false;'> <font color='#FF0000'><strong>Você entrou com um código inválido! </strong></font></a><br />"; } ?> <form method="post" action="índex.php"> Digite o texto da imagem abaixo:<br /> <!-- Como endereço da imagem, indicamos o arquivo saida.php com o parâmetro referente à imagem--> <img id="image" src="saida.php?show=true" alt="imagem"><br /> <a href="#" onclick="document.getElementById('image') .src = 'saida.php?show=true&sid=' + Math.random(); return false"> <img src="images/refresh.png" alt="Trocar Imagem" title="Trocar Imagem"></a> <!-- Como link para o arquivo de áudio, indicamos o arquivo saida.php com o parâmetro referente ao áudio--> <a href="saida.php?play=true"> <img src="images/audio.png" alt="Ouvir texto da imagem" title="Ouvir texto da imagem"></a><br /> <input type="text" name="code" id="code" /> <input type="submit" value="Validar"> </form>
Listagem 8. Arquivo index.php, dentro de body

Neste arquivo, verificamos inicialmente se existe a variável enviada pelo formulário (if (isset($_POST["code"]))). Se o formulário já foi enviado, incluímos o arquivo que contém a classe captcha (include("classcaptcha.php");), chamamos a classe ($img = new Captcha();) e, posteriormente, chamamos a função que valida o código digitado, passando como parâmetro o código submetido pelo formulário ($valid = $img->check($_POST['code']);). Se a variável $valid receber true, mostramos uma mensagem de sucesso, se receber false, uma mensagem de erro. Notem que a mensagem é retornada como um link. Isso porque os leitores de tela localizam com maior facilidade links e objetos, como campos de texto, botões, etc. E retornamos a mensagem antes do formulário para o deficiente encontrar primeiro a resposta de sucesso ou erro. Se a retornarmos depois, fica confuso para o mesmo saber se o código captcha por ele digitado foi aceito ou não, pois ele encontrará o formulário primeiro, podendo digitar novamente o código, sem encontrar a mensagem de sucesso ou erro caso ele não percorra a página toda.

Depois, há o formulário, responsável por enviar o código. Notem que enviamos o formulário para o mesmo arquivo(). A explicação é simples. Para simplificar a navegação do deficiente visual, não tendo que digitar o código, ser redirecionado a uma página que informa se o código estava certo ou não. Se errado, ele teria que voltar à página que contém o formulário, digitar novamente, verificar se estava correto. Para nós, isso é uma tarefa simples, basta alguns cliques com o mouse. Para o deficiente visual não, pois ele teria que percorrer item por item da página até chegar aonde ele quer.

Continuando, dentro do formulário, temos a imagem com o código. Notem que passamos como endereço da imagem o arquivo saida.php mais um parâmetro via URL (?show=true). Isso porque, de acordo com o parâmetro passado, o arquivo saida.php gera uma imagem ou um arquivo de áudio.

Depois, temos o link responsável por trocar de imagem, se for necessário () e o link onde obteremos o arquivo de áudio (). Notem que, no link para o arquivo de áudio, também passamos como endereço o arquivo saida.php. Porém, o parâmetro passado pela URL é diferente. Através desse novo parâmetro, o arquivo saida.php gera o arquivo de áudio, em vez de imagem.

Finalmente, o campo onde digitamos o código e o botão que enviará o formulário.

<?php include ("classcaptcha.php"); // inclui o arquivo de classe $img = new Captcha(); // atribui a classe a uma variável if(isset($_GET['play']) && $_GET['play'] == 'true'){ // Verifica se o parâmetro passado é referente ao link de áudio header('Content-type: audio/x-wav'); header('Content-Disposition: attachment; name="som.wav"'); header('Cache-Control: no-store, no-cache, must-revalidate'); header('Expires: Sun, 1 Jan 2000 12:00:00 GMT'); header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . 'GMT'); /* Chama a função da classe captcha responsável por verificar se há um código gerado e, dentro desta função, chama a função que cria o arquivo de áudio */ echo $img->getAudibleCode(); // Se o parâmetro passado não é referente ao áudio, verifica se é referente à imagem } else if(isset($_GET['show']) && $_GET['show'] == 'true') { $img->quantidade_letras = 5; // Atribui a quantidade de letras $img->tamanho_fonte = 20; // Atribui o tamanho da fonte $img->largura = 200; // Atribui a largura da imagem $img->altura = 50; // Atribui a altura da imagem $img->show(); // Chama a função que gera a imagem } ?>
Listagem 9. Arquivo saida.php

No último arquivo, incluímos o arquivo com a classe (include ("classcaptcha.php");) e a associamos a uma variável ($img = new Captcha();). Depois, temos uma condição que verifica se o parâmetro play, passado por url, existe e se seu conteúdo é o que desejamos (if(isset($_GET['play']) && $_GET['play'] == 'true')). Se a condição for verdadeira, chamamos a função que cria o arquivo de áudio ($img->getAudibleCode();). Se a condição não for satisfeita, entramos em uma nova condição, que verifica se o parâmetro show existe e se seu conteúdo é o que desejamos. Caso a condição seja verdadeira, definimos a altura, largura, o tamanho da fonte e a quantidade de letras que a imagem com o código captcha terá ($img->quantidade_letras = 5; $img->tamanho_fonte = 20; $img->largura = 200; $img->altura = 50;) e chamamos a função que irá gerar tal imagem ($img->show();).

Uma dica para quem for utilizar um captcha, não necessariamente esse: não utilizem somente a validação do captcha via Javascript ou Ajax. Se o navegador estiver com Javascript desativado, o captcha se torna inútil.

Espero que o conteúdo aqui apresentado seja útil a aquém precisar implementar esse tipo de funcionalidade em páginas web PHP. Até a próxima oportunidade.

Referências

Ebook exclusivo
Dê um upgrade no início da sua jornada. Crie sua conta grátis e baixe o e-book

Artigos relacionados