Uploadify e Flash morreram: como trocar o plugin por um upload que funciona hoje
Encontre os restos do Uploadify no projeto, troque por input de arquivo com envio via FormData, remova o truque de sessão por POST e apague a pasta antiga que ainda aceita upload.
O cliente liga dizendo que "o botão de enviar foto sumiu" do painel da imobiliária. O código não mudou há anos. O que mudou foi o mundo: o botão era um SWF do Uploadify, e nenhum navegador atual executa Flash. Não existe configuração que traga isso de volta, e não deveria existir.
A resposta curta: troque o plugin por um <input type="file" multiple> comum, envie com FormData quando quiser fazer isso sem recarregar a página, valide tudo no PHP e apague a pasta do Uploadify do servidor. O script de upload antigo, esquecido no servidor, é o pedaço mais perigoso dessa história.
Por que o tutorial parou de funcionar
O Uploadify clássico existia porque, por volta de 2010, o HTML não tinha seleção de vários arquivos nem progresso de upload em todos os navegadores. O Flash Player tinha. O plugin desenhava um botão em Flash por cima da página, e era o Flash que lia os arquivos e fazia o POST.
A Adobe encerrou o suporte ao Flash Player no fim de 2020 e os navegadores removeram o plugin. Desde então, o SWF simplesmente não carrega. Não aparece erro do PHP porque o PHP nunca é chamado: o botão some e nenhuma requisição sai. Quem segue o tutorial hoje acha que errou a configuração do servidor, quando o problema é que a peça central não existe mais.
O input de arquivo resolve hoje, nativamente, o que o Flash resolvia: atributo multiple, filtro com accept, arrastar e soltar, e progresso via XMLHttpRequest. Não há motivo para procurar "a versão HTML5 do plugin" se o objetivo é só subir fotos.
O que o Uploadify deixava para trás
Antes de trocar o botão, entenda o que mais o plugin trazia, porque parte disso é problema de segurança que continua ativo mesmo com o Flash morto.
O script de upload sem validação
Os exemplos da época vinham com um uploadify.php que movia o arquivo recebido para a pasta de destino, muitas vezes sem conferir tipo nem extensão. Esse script continua respondendo a qualquer POST, com ou sem Flash. Pastas do Uploadify esquecidas em servidores são alvo comum de varredura automática, justamente porque às vezes aceitam um arquivo .php e o deixam numa pasta pública.
A sessão passada pelo formulário
O Flash não enviava os cookies do navegador junto com o upload. Para o PHP saber quem estava logado, os tutoriais passavam o ID da sessão como parâmetro e faziam algo como session_id($_POST['PHPSESSID']) antes do session_start(). Isso permite que qualquer um escolha a sessão que o servidor vai usar, o que abre caminho para fixação de sessão. Com o envio pelo próprio navegador, o cookie vai sozinho e esse truque precisa sair.
Como migrar, passo a passo
- Inventarie os restos. Na raiz do projeto, rode
rg -n -i "uploadify|swf|PHPSESSID"e liste cada arquivo encontrado: JavaScript, CSS, SWF, script de upload e qualquersession_id(vindo de requisição. - Escreva o formulário que funciona sem script. Um
formcommethod="post",enctype="multipart/form-data"e o input commultiple. Ele já resolve o problema do cliente. - Reescreva o recebimento no PHP com validação de erro, tamanho e tipo por conteúdo, nome gerado pelo servidor e conferência de login pela sessão normal.
- Adicione o envio sem recarregar, se quiser, interceptando o submit e mandando o mesmo
FormData. - Apague o plugin do servidor, não só do repositório: a pasta, o SWF, o script antigo. Confirme que a URL antiga responde 404.
- Olhe os logs de acesso procurando POSTs para o script antigo e arquivos estranhos na pasta de destino.
O formulário e o envio com FormData
No painel da imobiliária, cada imóvel recebe até 20 fotos. O formulário abaixo funciona sozinho; o script só melhora a experiência.
<form id="form-fotos" action="/imoveis/fotos.php" method="post" enctype="multipart/form-data">
<input type="hidden" name="imovel_id" value="482">
<input type="hidden" name="csrf" value="TOKEN_GERADO_NO_SERVIDOR">
<label for="fotos">Fotos do imóvel (JPG, PNG ou WebP, até 5 MB cada)</label>
<input id="fotos" type="file" name="fotos[]" multiple accept="image/jpeg,image/png,image/webp">
<button type="submit">Enviar fotos</button>
<p id="resultado" aria-live="polite"></p>
</form>
<script>
const form = document.getElementById('form-fotos');
const resultado = document.getElementById('resultado');
form.addEventListener('submit', async function (e) {
e.preventDefault();
resultado.textContent = 'Enviando...';
try {
const resp = await fetch(form.action, { method: 'POST', body: new FormData(form) });
const corpo = await resp.json().catch(function () { return null; });
if (!resp.ok || !corpo) {
resultado.textContent = 'Falhou: ' + ((corpo && corpo.erro) || 'erro ' + resp.status);
return;
}
resultado.textContent = corpo.salvos + ' foto(s) salva(s). ' +
(corpo.recusados.length ? 'Recusadas: ' + corpo.recusados.join(', ') : '');
} catch (err) {
resultado.textContent = 'Sem conexão com o servidor. Tente de novo.';
}
});
</script>
O accept só filtra a janela de seleção; não é validação. O fetch com FormData envia o cookie de sessão do próprio domínio sem configuração extra. Se você precisa de barra de progresso, troque o fetch por XMLHttpRequest e siga o que está no post sobre barra de upload que não mente sobre o sucesso.
O recebimento no PHP
Com name="fotos[]", o PHP organiza $_FILES de um jeito pouco intuitivo: um array por propriedade, não um array por arquivo. O laço abaixo percorre pelo índice.
<?php
session_start();
header('Content-Type: application/json; charset=utf-8');
if (!isset($_SESSION['usuario_id'])) {
http_response_code(401);
echo json_encode(['erro' => 'Faça login de novo.']);
exit;
}
if (!hash_equals($_SESSION['csrf'] ?? '', $_POST['csrf'] ?? '')) {
http_response_code(403);
echo json_encode(['erro' => 'Formulário expirado. Recarregue a página.']);
exit;
}
$tipos = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp'];
$finfo = new finfo(FILEINFO_MIME_TYPE);
$salvos = 0;
$recusados = [];
$total = count($_FILES['fotos']['name'] ?? []);
for ($i = 0; $i < $total; $i++) {
$nomeOriginal = $_FILES['fotos']['name'][$i];
$tmp = $_FILES['fotos']['tmp_name'][$i];
if ($_FILES['fotos']['error'][$i] !== UPLOAD_ERR_OK
|| $_FILES['fotos']['size'][$i] > 5 * 1024 * 1024) {
$recusados[] = $nomeOriginal;
continue;
}
$mime = $finfo->file($tmp);
if (!isset($tipos[$mime])) {
$recusados[] = $nomeOriginal;
continue;
}
$destino = '/var/app/fotos/' . bin2hex(random_bytes(16)) . '.' . $tipos[$mime];
if (move_uploaded_file($tmp, $destino)) {
$salvos++;
} else {
error_log('fotos: falha ao mover ' . $tmp);
$recusados[] = $nomeOriginal;
}
}
echo json_encode(['salvos' => $salvos, 'recusados' => $recusados], JSON_UNESCAPED_UNICODE);
A pasta de destino fica fora da raiz pública, então mesmo um arquivo malicioso que passasse não seria executado por URL. A sessão vem do cookie, e não de um parâmetro. Para reprocessar a imagem e gravar a referência no banco, o guia de upload de arquivo em PHP cobre o restante.
Arrastar e soltar sem plugin
Se o cliente sentir falta de arrastar as fotos para a página, isso também é nativo. Uma área que escuta dragover (chamando preventDefault()) e drop recebe os arquivos em e.dataTransfer.files. Você pode atribuir essa lista ao próprio input com input.files = e.dataTransfer.files e reaproveitar o mesmo envio. Não precisa de biblioteca para isso.
Erros comuns
- Procurar como habilitar Flash: não há caminho suportado, e qualquer "player alternativo" baixado de site desconhecido é um risco maior que o problema.
- Trocar só o botão e manter o
uploadify.php: o script antigo continua aceitando POST de qualquer origem, sem validação. - Manter o
session_id($_POST[...]): o servidor continua aceitando sessão escolhida por quem envia, o que abre espaço para fixação de sessão. - Confiar no
acceptcomo filtro: ele só muda a janela do sistema; qualquer arquivo pode ser enviado direto para o endpoint. - Apagar do Git e esquecer o servidor: o deploy por cópia não remove arquivos antigos, e a pasta segue publicada.
- Seguir outro tutorial da mesma época: trocar Uploadify por outro plugin de 2012 só adia a próxima migração; veja por que resposta de fórum antigo não é manual.
Checklist
- A busca por
uploadify,swfePHPSESSIDno projeto não retorna nada. - A URL do script de upload antigo responde 404 no servidor de produção.
- O formulário envia fotos com o JavaScript desligado.
- O PHP confere login pela sessão normal e valida CSRF.
- Tipo é validado por conteúdo com
finfo, e o nome final é gerado pelo servidor. - Os arquivos ficam fora da pasta pública.
- A resposta diz quantos arquivos foram salvos e quais foram recusados.
- Os logs foram verificados em busca de POSTs para o script antigo.
Abra agora o servidor de produção e procure uma pasta chamada uploadify ou um arquivo .swf. Se encontrar, o script que vinha junto ainda aceita upload?
Referência: http://www.oficinadanet.com.br/post/9518-upload-de-imagens-php-com-uploadify