Solucionado (ver solução)
Solucionado
(ver solução)
9
respostas

erro ao ler/exibir dados html armazenados no BD mysql

estou enfrentando um problema ao tentar buscar no BD um loop de informações. Ao tentar exibir as postagens que possuem conteúdo html que contem algum erro por exemplo uma tabela que não teve sua tag fechada (</table>) acaba afetando a postagem seguinte que acaba entrando nessa tabela, existe alguma função que possa corrigir esse erro?

Post.php

class Posts
{

    private $id;
    private $titulo;
    private $descricao;
    private $conteudo;
    private $imagemDestaque;
    private $status;
    private $categoria;
    private $data;

    public function __construct($titulo = null, $descricao = null, $conteudo = null, $imagemDestaque = null, $status = null, $categoria = null, $data = false, $id = false)
    {
        $this->titulo = $titulo;
        $this->descricao = $descricao;
        $this->conteudo = $conteudo;
        $this->imagemDestaque = $imagemDestaque;
        $this->status = $status;
        $this->categoria = $categoria;
        if($data){
            $this->data = $data;
        }
        if($id){
            $this->id = $id;
        }
    }
    public static function getPosts()
    {

        $query = "SELECT * FROM tb_posts ORDER BY id";
        $connect = Connection::takeConnection();
        $result = $connect->prepare($query);
        $result->execute();
        $list = $result->fetchAll(PDO::FETCH_ASSOC);
        return $list;
9 respostas

Então, se não me engano tem como vc usar um método em JS para colocar ou tirar uma string, então acho que tem como tirar direto no seu front, agora em PHP tem que tentar achar algo como um método "replace()" do JS para que em todo começo que tenha uma string com a tag:

<table>

ele tranforme em:

</table><table>

Caso isso não ajude só reestruturando os dados no BD

estou pondo em prática o que aprendi nos cursos fazendo um sistema de blog porém me veio a dúvida de como tratar esse erro. Fiz um teste e inseri um post com o título :

<select>

observei que além de o retorno do titulo ser de fato um input de seleção esse erro impedia que os demais posts fossem listados. Daí encontrei uma função, a :

htmlspecialchars ();

que converte os caracteres especiais por exemplo

<

em

&lt;

e isso é viável no titulo porém no conteúdo que será armazenado texto formatado e imagem acredito que não seria, daí que pensei se seria possível no momento de gravar no BD fechar as tegs que não forma fechadas e evitar esse tipo de erro.

depois de um mês encontrei a solução para essa dúvida, o site está em inglês mas é facil compreender:

https://www.the-art-of-web.com/php/truncate/#section_5

código de exemplo:

<?PHP
  // Original PHP code by Chirp Internet: www.chirp.com.au
  // Please acknowledge use of this code by including this header.

  function restoreTags($input)
  {
    $opened = array();

    // loop through opened and closed tags in order
    if(preg_match_all("/<(\/?[a-z]+)>?/i", $input, $matches)) {
      foreach($matches[1] as $tag) {
        if(preg_match("/^[a-z]+$/i", $tag, $regs)) {
          // a tag has been opened
          if(strtolower($regs[0]) != 'br') $opened[] = $regs[0];
        } elseif(preg_match("/^\/([a-z]+)$/i", $tag, $regs)) {
          // a tag has been closed
          unset($opened[array_pop(array_keys($opened, $regs[1]))]);
        }
      }
    }

    // close tags that are still open
    if($opened) {
      $tagstoclose = array_reverse($opened);
      foreach($tagstoclose as $tag) $input .= "</$tag>";
    }

    return $input;
  }

Essa solução não é perfeit mas ela evita a maioria dos problemas e fecha no final da string enviada todas as tags que são abertas e que o usuário não fechou por exemplo:

$input = <p>João</p> <p>Maria

retorno:

$input =<p>João</p><p> maria</p>

só estou fazendo algumas alterações para incluir outras coisas que pensei depois mas dessa forma já é utilizavel.

Olá Marcos, obrigado por compartilhar a solução!

Isso realmente é algo um pouco complicado de ser feito. Eu encontrei esse código que também deve servir, não é 100% perfeito mas funciona na maioria dos casos (veja os últimos exemplos).

Normalmente o navegador já tenta corrigir o que pode quando recebe um HTML quebrado, então esse código "simula" a interpretação do HTML para receber o resultado com a tentativa de correção:

function fixHTML($html) {
    $doc = new DOMDocument();
    $doc->substituteEntities = false;
    $content = mb_convert_encoding($html, 'html-entities', 'utf-8');
    @$doc->loadHTML($content);
    $fix_html = $doc->saveHTML();

    return $fix_html;
}

$html = "<div><p>João</p> <p>Maria<";
$fix_html = fixHTML($html);
echo $fix_html;

// Resultado
// <div><p>João</p> <p>Maria</p></div>

Mais exemplos:

// Entrada
<span>Maria<span
// Resultado
<span>Maria<span></span></span>

// Entrada
<div><p>João</p> <p>Maria<div
// Resultado
<div><p>João</p> <p>Maria</p><div></div></div>

No começo eu pensava que a correção deveria acontecer no front-end, assim sempre que alguém acessar determinado post se fosse encontrado um erro ele fosse corrigido... porém pensei melhor e vi que seria mais adequado verificar isso antes de armazenar a informação no BD assim evitaria consumo de recursos e possível lentidão do lado do cliente. Confesso que ainda não intendo como funciona todo mecanismo por traz do servidor mas tenho a impressão que uma função verificando os dados aumentaria o tempo de resposta. Lógico que em um site com 100 acesso por dia não seria perceptível, mas como tenho aprendido aqui na alura você tem que ser megalomaníaco :) e construir sua solução pensando em grande escala.

Todo esse textão do facebook foi só pra explicar o resultado dos meus testes:

A função que postei tem um pequeno problema na hora de armazenar no BD que é:

exemplo:

<p>Dia um <span> 8:00</p>

retorno:

<p>Dia um <span> 8:00</p></span>

ela não procura o local correto para fechar a tag.

essa sua função corrige de fato esse problema além de ser mais resumida, porém quando ela é usada na hora de armazenar no BD ela faz isso:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><p>Dia um <span> 8:00</span></p></body></html>

o que aumenta o volume de dados armazenados.

Então cheguei à conclusão que a função que encontrei é mais adequada para checar antes de armazenar no BD e essa que você postou seria mais útil na hora de apresentar os dados.

Depois de alguns segundos veio o estalo, e se depois de usar a solução do Lucas eu usasse uma expressão regular para filtrar e remover essas tags extras? Resposta: vou tentar resolver assim

Estou pensando errado?

como não me garanto em expressões regulares acabei optando por fazer assim:

    public function restoreTags($input)
  {
    $doc = new \DOMDocument();
    $doc->substituteEntities = false;
    $content = mb_convert_encoding($input, 'html-entities', 'utf-8');
    @$doc->loadHTML($content);
    $html = $doc->saveHTML();
    $stringsReplace = ['<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//
EN" "http://www.w3.org/TR/REC-html40/loose.dtd">',
     '<html>','<body>', '</html>','</body>'];
    $fix_html =  str_replace($stringsReplace, '', $html);

    return $fix_html;
}

Verdade, eu não percebi que era adicionado esse conteúdo extra.

Mas o uso do regex é uma boa solução, simples e funciona. Como alternativa tem também esses parâmetros da função loadHTML que evitam desse conteúdo ser adicionado:

@$doc->loadHTML($content, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
solução!

Obrigado pelo interesse Lucas, cada solução que você trouxe acrescentou muito para resolução desse problema.

nos meus testes percebi que esse loadHTML usando os parâmetros LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD ela passava também a reorganizar o html (por exemplo mudar as tags de lugar). daí fui pesquisar mais sobre esses parâmetros e vi que era justamente o LIBXML_HTML_NOIMPLIED que fazia isso, e o LIBXML_HTML_NODEFDTD apenas remove a tag !DOCTYPE. O que já me ajudou a chegar a uma solução possível. a solução anterior que eu apresentei tinha um problema: se a biblioteca começar a usar o HTML 5.0 por exemplo ela deixaria de excluir o DOCTYPE.

acredito que assim consegui optimizar a função:

        public function restoreTags($input)
  {
    $doc = new \DOMDocument();
    $doc->substituteEntities = false;
    $content = mb_convert_encoding($input, 'html-entities', 'utf-8');
    @$doc->loadHTML($content, LIBXML_HTML_NODEFDTD);
    $html = $doc->saveHTML();

    $fix_html =  preg_replace('/<\/?(html|body|head)>?/', '', $html);

    return $fix_html;

Essa última solução ficou melhor mesmo, juntou o melhor de cada opção, acredito que por enquanto não precise mais de melhorias.