diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0e7b5a3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.{md,yml}] +indent_size = 2 diff --git a/Gemfile b/Gemfile index 8590dbd..e723a41 100644 --- a/Gemfile +++ b/Gemfile @@ -1,40 +1,40 @@ -source "https://rubygems.org" -# Hello! This is where you manage which Jekyll version is used to run. -# When you want to use a different version, change it below, save the -# file and run `bundle install`. Run Jekyll with `bundle exec`, like so: -# -# bundle exec jekyll serve -# -# This will help ensure the proper Jekyll version is running. -# Happy Jekylling! -gem "jekyll", "~> 4.3.3" -# This is the default theme for new Jekyll sites. You may change this to anything you like. -# gem "minima", "~> 2.5" -# If you want to use GitHub Pages, remove the "gem "jekyll"" above and -# uncomment the line below. To upgrade, run `bundle update github-pages`. -# gem "github-pages", group: :jekyll_plugins -# If you have any plugins, put them here! -group :jekyll_plugins do - gem "jekyll-feed" - gem "jekyll-toc" - gem "jekyll-sitemap" - gem "jekyll-minifier" -end - -# Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem -# and associated library. -platforms :mingw, :x64_mingw, :mswin, :jruby do - gem "tzinfo", ">= 1", "< 3" - gem "tzinfo-data" -end - -# Performance-booster for watching directories on Windows -gem "wdm", "~> 0.1.1", :platforms => [:mingw, :x64_mingw, :mswin] - -# Lock `http_parser.rb` gem to `v0.6.x` on JRuby builds since newer versions of the gem -# do not have a Java counterpart. -gem "http_parser.rb", "~> 0.6.0", :platforms => [:jruby] - -gem "rouge" - -gem "webrick", "~> 1.8" +source "https://rubygems.org" +# Hello! This is where you manage which Jekyll version is used to run. +# When you want to use a different version, change it below, save the +# file and run `bundle install`. Run Jekyll with `bundle exec`, like so: +# +# bundle exec jekyll serve +# +# This will help ensure the proper Jekyll version is running. +# Happy Jekylling! +gem "jekyll", "~> 4.3.3" +# This is the default theme for new Jekyll sites. You may change this to anything you like. +# gem "minima", "~> 2.5" +# If you want to use GitHub Pages, remove the "gem "jekyll"" above and +# uncomment the line below. To upgrade, run `bundle update github-pages`. +# gem "github-pages", group: :jekyll_plugins +# If you have any plugins, put them here! +group :jekyll_plugins do + gem "jekyll-feed" + gem "jekyll-toc" + gem "jekyll-sitemap" + gem "jekyll-minifier" +end + +# Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem +# and associated library. +platforms :mingw, :x64_mingw, :mswin, :jruby do + gem "tzinfo", ">= 1", "< 3" + gem "tzinfo-data" +end + +# Performance-booster for watching directories on Windows +gem "wdm", "~> 0.1.1", :platforms => [:mingw, :x64_mingw, :mswin] + +# Lock `http_parser.rb` gem to `v0.6.x` on JRuby builds since newer versions of the gem +# do not have a Java counterpart. +gem "http_parser.rb", "~> 0.6.0", :platforms => [:jruby] + +gem "rouge" + +gem "webrick", "~> 1.8" diff --git a/_config.yml b/_config.yml index ec806f1..de646b5 100644 --- a/_config.yml +++ b/_config.yml @@ -72,6 +72,8 @@ exclude: - LICENSE - rollup.config.js - package*.json + - .gitignore + - .editorconfig include: - _pages diff --git a/_posts/2018-03-30-html-purifier-um-pouco-a-mais-da-simples-limpeza.markdown b/_posts/2018-03-30-html-purifier-um-pouco-a-mais-da-simples-limpeza.md similarity index 96% rename from _posts/2018-03-30-html-purifier-um-pouco-a-mais-da-simples-limpeza.markdown rename to _posts/2018-03-30-html-purifier-um-pouco-a-mais-da-simples-limpeza.md index 7dee3ee..f0f9aea 100644 --- a/_posts/2018-03-30-html-purifier-um-pouco-a-mais-da-simples-limpeza.markdown +++ b/_posts/2018-03-30-html-purifier-um-pouco-a-mais-da-simples-limpeza.md @@ -1,567 +1,567 @@ ---- -title: "HTML Purifier - Um pouco a mais da simples limpeza" -description: Efetuando uma limpeza poderosa no HTML para evitar dores de cabeça com conteúdo indesejado e possíveis quebras de layout. -date: 2018-03-30 19:00:00 -0400 -categories: php tutorial -tags: - - desenvolvimento - - php - - html - - purifier ---- - -Tratar entrada de usuário é obrigação, principalmente se permitirmos que ela seja em HTML. Embora os editores WYSIWYG -possuam configurações de limpeza nós não podemos escapar dessa árdua tarefa no back-end. - -Há alguns anos conheci o [HTML Purifier](http://htmlpurifier.org/) e ele virou a ferramenta que mais utilizo para -"limpar" o HTML. - -Infelizmente quase sempre usei a configuração padrão do HTML Purifier. -Em partes por que me atendia bem, mas também por que a documentação não é muito atrativa. - -> Porém, a vida é uma caixinha de surpresas e numa bela manhã de Sol [...] - -Recentemente estava trabalhando numa versão antiga do [Moodle](https://moodle.org/), que está utilizando -o framework [Bootstrap](https://getbootstrap.com/), e comecei a perceber problemas com conteúdo HTML dos alunos, -principalmente quando eles copiavam o texto de páginas Web. - -Devido ao texto vir com "sujeira" (muito CSS inline e elementos com tamanhos definidos com width e height) o -layout ficava destoante e em alguns casos ficava quebrado mesmo, impedindo que o grid se ajustasse à largura da tela. - -E como a necessidade nos força a sair da zona de conforto resolvi estudar mais a documentação do HTML Purifier -e aqui estou pra entregar um pouco do que aprendi. - -## Funcionamento básico - -O exemplo mais simples de uso do HTML Purifier, após incluir o autoload, é: - -```php -purify($html_sujo); -``` - -A configuração padrão do HTML Purifier atende muito bem na maioria dos casos. -Na página de [demonstração](http://htmlpurifier.org/demo.php) é possível testar as opções de configuração. - -Mas é na [documentação da configuração](http://htmlpurifier.org/live/configdoc/plain.html) que tudo começa a -ficar mais interessante. - -## Configurando o HTML Purifier - -Para definir um item da configuração basta copiar seu nome na página de configuração e passar o valor desejado -à variável de configuração, que sempre trarei na variável **$config**. Os nomes são bem explicativos. - -```php -set('AutoFormat.RemoveEmpty', true); -$config->set('AutoFormat.RemoveEmpty.RemoveNbsp', true); -``` - -### Uma configuração menos permissiva - -Para o meu caso eu precisava remover várias propriedades, classes e até elementos do HTML que o usuário postava. Basicamente eu poderia permitir somente algumas propriedades como cores, negrito, alinhamento etc. - -Pensando em tudo que era permitido e proibido eu fiz a seguinte configuração: - -```php -set('CSS.AllowedProperties', $allowedStyleProperties); -$config->set('Attr.AllowedClasses', $allowedClasses); -$config->set('HTML.ForbiddenElements', $forbiddenElements); -$config->set('HTML.ForbiddenAttributes', $forbiddenAttributes); -$config->set('AutoFormat.RemoveEmpty', true); -$config->set('AutoFormat.RemoveSpansWithoutAttributes', true); -$config->set('Output.Newline', "\n"); -$config->set('AutoFormat.RemoveEmpty.RemoveNbsp', true); -$config->set('HTML.TidyLevel', 'heavy'); -``` - -## As transformações do HTML Purifier - -Se você prestou atenção nas configurações deve ter notado a `HTML.TidyLevel`. - -O HTML Purifier utiliza essa configuração para substituir as tags e atributos descontinuados por tags e -atributos atualizados. Ou seja, ele pega um HTML assim: - -```html -

Centralizado e Branco

-
Centralizado!
-``` - -E deixa assim: - -```html -

Centralizado e Branco

-
Centralizado!
-``` - -A ótima notícia é que podemos usar a ferramenta de análise e transformação do HTML Purifier -para fazer as nossas modificações. - -Podemos personalizar as definições do HTML, olha só a -[documentação de personalização](http://htmlpurifier.org/docs/enduser-customize.html), -mas isso é tema pra outro post. Por enquanto usar as transformações é o suficiente. - -### Transformando Atributos - -Para alterar os atributos basta criar uma classe que estenda a `HTMLPurifier_AttrTransform` e -implemente o método `transform`, o qual recebe os atributos da tag, além da configuração e contexto, -realiza as mudanças e então retorna os atributos. - -No diretório `library/HTMLPurifier/AttrTransform` há várias classes utilizadas pelo HTML Purifier e -que podemos usar como exemplo para desenvolver as nossas. Todas estendem a classe `HTMLPurifier_AttrTransform` e -implementam o método `transform`. - -### Transformando Elementos - -De forma semelhante às transformações de atributos podemos realizar transformações completas nos elementos. - -Podemos criar uma classe que estenda a `HTMLPurifier_TagTransform` e que -implemente o método `transform`, o qual recebe a tag, a configuração e o contexto. - -No diretório `library/HTMLPurifier/TagTransform` há duas classes utilizadas pelo HTML Purifier, a classe que -transforma a tag **font** e uma classe mais "genérica" que trata todas as outras transformações simples, -a `HTMLPurifier_TagTransform_Simple`. - -### Configurando o HTML Purifier para usar as transformações - -Toda a configuração das transformações ficam nas definições do HTML. -Para pegar as definições basta usar o método `getHTMLDefinition` na **$config** e você receberá uma -instância da `HTMLPurifier_HTMLDefinition`. - -Nesse objeto de definições há 3 propriedades públicas que recebem as nossas instâncias de transformações. - -- **info_tag_transform** é um array cujo índice é o nome da tag a transformar -- **info_attr_transform_pre** é um array de índice numérico que transformará os atributos no início do processo -- **info_attr_transform_post** é um array de índice numérico que transformará os atributos no final do processo - -Poderíamos configurar o HTML Purifier para fazer algumas trocas de teste assim: - -```php -getHTMLDefinition(); - -// Transformará toda tag p em uma tag div com CSS inline para deixar o texto com sublinhado -$htmlDefinition->info_tag_transform['p'] = new HTMLPurifier_TagTransform_Simple( - 'div', - 'text-decoration:underline;' -); - -// Converte o atributo bgcolor em um CSS inline -$htmlDefinition->info_attr_transform_pre[] = new HTMLPurifier_AttrTransform_BgColor(); - -// Tudo configurado basta instanciar o HTML Purifier -$purifier = new HTMLPurifier($config); -$html_limpo = $purifier->purify($html_sujo); -``` - -## Criando as nossas transformações - -Para o meu caso eu precisava cumprir esses objetivos: - -- Adicionar a classe **img-responsive** às tags **img** -- Adicionar as classes **table** e **table-condensed** às tags **table** -- Adicionar a classe **table-bordered** se a tag **table** possuir o atributo **border** -- Cercar a tabela com uma **div** que possua a classe **table-responsive** - -Com isso em mente vamos ao trabalho. - -### Adicionando as classes básicas nas tags - -Para adicionar as classes padrões que não dependem de alguma verificação criei uma classe genérica baseada na -classe `HTMLPurifier_TagTransform_Simple`. - -```php -classes = array_unique(array_map('trim', $classes)); - } - - /** - * @param HTMLPurifier_Token_Tag $tag - * @param HTMLPurifier_Config $config - * @param HTMLPurifier_Context $context - * @return HTMLPurifier_Token_Tag - */ - public function transform($tag, $config, $context) - { - // Se for a tag de fim não precisa alterar nada - if (empty($this->classes) || $tag instanceof HTMLPurifier_Token_End) { - return $tag; - } - - $new_tag = clone $tag; - - if (empty($new_tag->attr['class'])) { - $new_tag->attr['class'] = ''; - } - - $new_tag->attr['class'] = $this->appendClasses($new_tag->attr['class']); - return $new_tag; - } - - /** - * Concatena as classes da instância à string de classes já definidas - * @param string $classes - * @return string - */ - protected function appendClasses($classes = '') - { - return implode( - ' ', - array_unique( - array_merge( - array_map('trim', explode(' ', $classes)), - $this->classes - ) - ) - ); - } -} -``` - -Então configuramos a definição do HTML com as novas instâncias de transformações. - -```php -info_tag_transform['table'] = new TagClassTransform('table table-condensed'); -$htmlDefinition->info_tag_transform['img'] = new TagClassTransform('img-responsive'); -``` - -Com essa configuração já podemos converter esse HTML: - -```html -

- - - - - - - - - -
Hey!Ho!Lets Go!
-``` - -Nessa versão quase pronta: - -```html -

hello_world.jpg

- - - - - - - - - -
Hey!Ho!Lets Go!
-``` - -### Trocando o atributo border pela classe table-bordered - -Precisamos fazer a troca do atributo **border**, independente do seu valor, pela classe -**table-bordered**. Para isso basta criar uma classe estendendo a `HTMLPurifier_AttrTransform` e -então adicionar uma instância dela ao array `$htmlDefinition->info_attr_transform_pre`. - -```php -get('CurrentToken')->name !== 'table') { - return $attr; - } - - // A intenção é trocar o atributo, então já podemos removê-lo - unset($attr['border']); - - if (empty($attr['class'])) { - $attr['class'] = ''; - } - - $attr['class'] = $this->appendClasses($attr['class']); - - return $attr; - } - - /** - * Concatena as classes da instância à string de classes já definidas - * Ps: sim, poderíamos fazer uma trait - * @param string $classes - * @return string - */ - protected function appendClasses($classes = '') - { - return implode( - ' ', - array_unique( - array_merge( - array_map('trim', explode(' ', $classes)), - $this->classes - ) - ) - ); - } -} - -// Então adicionar nas definições HTML -$htmlDefinition->info_attr_transform_pre[] = new TableBorderAttrTransform(); -``` - -E agora aquele HTML sujo: - -```html -

- - - - - - - - - -
Hey!Ho!Lets Go!
-``` - -Fica assim: - -```html -

hello_world.jpg

- - - - - - - - - -
Hey!Ho!Lets Go!
-``` - -## Criando um wrapper para a tabela - -Para a tabela não extrapolar os limites da área devemos cercá-la com uma **div.table-responsive**. - -Agora é hora de bater um pouco a cabeça. Como usar a transformação para criar um wrapper na nossa tabela? - -Simplesmente não vamos utilizar, afinal não queremos transformar uma table e sim inserir algo antes e depois dela. - -O HTML Purifier usa a classe `HTMLPurifier_Injector` para fazer inserções no HTML. -Por exemplo a opção `AutoFormat.Linkify` usa injector para inserir a tag **a** com o atributo **href** -em torno de um texto que case com a regra de URL. - -Utilizar injector é mais complexo do que uma TagTransform por que você analisará os tokens que formam o HTML, -mas é possível resolver mais fácil do que os quebra-cabeças do novo filme da Lara Croft. - -Dê uma olhada no diretório `library\HTMLPurifier\Injector` e ficará surpreso com as inserções que o Purifier já possui. - -Basicamente a `HTMLPurifier_Injector` possui 3 métodos para trabalhar com os tokens analisados. - -- **handleText**: chamado quando o token é um texto -- **handleElement**: chamado quando o token é uma tag de início ou vazia -- **handleEnd**: chamado quando o token é uma tag de fim - -Todos os métodos recebem uma referência de `HTMLPurifier_Token` e ela será modificada caso queira adicionar algo. -Geralmente será convertida em um array e então adicionará tokens neste array na sequência necessária. - -É importante observar as propriedades **name** e **needed** da `HTMLPurifier_Injector`. - -A propriedade `name` indica o nome do seu injector e a `needed` indica quais tags e atributos são necessários para ela -funcionar (a `HTMLPurifier_Injector` identifica o `needed` e caso exista alguma regra que proíba as tags e -atributos já cancela a sua execução). - - Nosso injector ficou assim: - - ```php - ['class'], - ]; - - /** - * @param HTMLPurifier_Token_Start $token - */ - public function handleElement(&$token) - { - /* - * Esse método é executado em todo token que indica um início - * Se não for uma tag table simplesmente ignoramos - */ - if (!$token->is_tag || $token->name !== 'table') { - return; - } - - // Copiando o token para não o perdermos - $table = clone $token; - - /* - * Transformando o token original em um array e - * inserindo os tokens na sequência necessária - */ - $token = [ - new HTMLPurifier_Token_Start('div', ['class' => 'table-responsive']), - $table, - ]; - } - - /** - * @param HTMLPurifier_Token_End $token - */ - public function handleEnd(&$token) - { - /* - * Esse método é executado em todo token que indica um fim - * Se não for uma tag table simplesmente ignoramos - */ - if (!$token->is_tag || $token->name !== 'table') { - return; - } - - // Copiando o token para não o perdermos - $table = clone $token; - - /* - * Transformando o token original em um array e - * inserindo os tokens na sequência necessária - */ - $token = [ - $table, - new HTMLPurifier_Token_End('div'), - ]; - } -} -``` - -Vamos adicionar nosso injector na definição HTML: - -```php -info_injector[] = new TableResponsiveWrapperInjector(); -``` - -E agora nosso HTML limpo ficou pronto: - -```html -

hello_world.jpg

- -
- - - - - - - -
Hey!Ho!Lets Go!
-``` - -### Código fonte completo - -Você pode visualizar o código completo no meu [Gist](https://gist.github.com/brunogasparetto/63f42e44388f13153b57e4c02df33f27). - -## Algumas dicas extras - -Use o HTML Purifier antes de persistir os dados (indiferente se utilizar arquivos ou banco de dados), pois o processo de analisar e converter o HTML é custoso e não deve ser executado todas as vezes que for exibir na tela. Então o melhor é já salvar limpo. - -Reduza o HTML antes de salvá-lo para economizar espaço. Durante esse processo eu utilizei a [Tiny Html Minifier](https://github.com/jenstornell/tiny-html-minifier) e gostei do resultado. - -Caso você permita edição é bom salvar o HTML original. +--- +title: "HTML Purifier - Um pouco a mais da simples limpeza" +description: "Efetuando uma limpeza poderosa no HTML para evitar dores de cabeça com conteúdo indesejado e possíveis quebras de layout." +date: 2018-03-30 19:00:00 -0400 +categories: php tutorial +tags: + - desenvolvimento + - php + - html + - purifier +--- + +Tratar entrada de usuário é obrigação, principalmente se permitirmos que ela seja em HTML. Embora os editores WYSIWYG +possuam configurações de limpeza nós não podemos escapar dessa árdua tarefa no back-end. + +Há alguns anos conheci o [HTML Purifier](http://htmlpurifier.org/) e ele virou a ferramenta que mais utilizo para +"limpar" o HTML. + +Infelizmente quase sempre usei a configuração padrão do HTML Purifier. +Em partes por que me atendia bem, mas também por que a documentação não é muito atrativa. + +> Porém, a vida é uma caixinha de surpresas e numa bela manhã de Sol [...] + +Recentemente estava trabalhando numa versão antiga do [Moodle](https://moodle.org/), que está utilizando +o framework [Bootstrap](https://getbootstrap.com/), e comecei a perceber problemas com conteúdo HTML dos alunos, +principalmente quando eles copiavam o texto de páginas Web. + +Devido ao texto vir com "sujeira" (muito CSS inline e elementos com tamanhos definidos com width e height) o +layout ficava destoante e em alguns casos ficava quebrado mesmo, impedindo que o grid se ajustasse à largura da tela. + +E como a necessidade nos força a sair da zona de conforto resolvi estudar mais a documentação do HTML Purifier +e aqui estou pra entregar um pouco do que aprendi. + +## Funcionamento básico + +O exemplo mais simples de uso do HTML Purifier, após incluir o autoload, é: + +```php +purify($html_sujo); +``` + +A configuração padrão do HTML Purifier atende muito bem na maioria dos casos. +Na página de [demonstração](http://htmlpurifier.org/demo.php) é possível testar as opções de configuração. + +Mas é na [documentação da configuração](http://htmlpurifier.org/live/configdoc/plain.html) que tudo começa a +ficar mais interessante. + +## Configurando o HTML Purifier + +Para definir um item da configuração basta copiar seu nome na página de configuração e passar o valor desejado +à variável de configuração, que sempre trarei na variável **$config**. Os nomes são bem explicativos. + +```php +set('AutoFormat.RemoveEmpty', true); +$config->set('AutoFormat.RemoveEmpty.RemoveNbsp', true); +``` + +### Uma configuração menos permissiva + +Para o meu caso eu precisava remover várias propriedades, classes e até elementos do HTML que o usuário postava. Basicamente eu poderia permitir somente algumas propriedades como cores, negrito, alinhamento etc. + +Pensando em tudo que era permitido e proibido eu fiz a seguinte configuração: + +```php +set('CSS.AllowedProperties', $allowedStyleProperties); +$config->set('Attr.AllowedClasses', $allowedClasses); +$config->set('HTML.ForbiddenElements', $forbiddenElements); +$config->set('HTML.ForbiddenAttributes', $forbiddenAttributes); +$config->set('AutoFormat.RemoveEmpty', true); +$config->set('AutoFormat.RemoveSpansWithoutAttributes', true); +$config->set('Output.Newline', "\n"); +$config->set('AutoFormat.RemoveEmpty.RemoveNbsp', true); +$config->set('HTML.TidyLevel', 'heavy'); +``` + +## As transformações do HTML Purifier + +Se você prestou atenção nas configurações deve ter notado a `HTML.TidyLevel`. + +O HTML Purifier utiliza essa configuração para substituir as tags e atributos descontinuados por tags e +atributos atualizados. Ou seja, ele pega um HTML assim: + +```html +

Centralizado e Branco

+
Centralizado!
+``` + +E deixa assim: + +```html +

Centralizado e Branco

+
Centralizado!
+``` + +A ótima notícia é que podemos usar a ferramenta de análise e transformação do HTML Purifier +para fazer as nossas modificações. + +Podemos personalizar as definições do HTML, olha só a +[documentação de personalização](http://htmlpurifier.org/docs/enduser-customize.html), +mas isso é tema pra outro post. Por enquanto usar as transformações é o suficiente. + +### Transformando Atributos + +Para alterar os atributos basta criar uma classe que estenda a `HTMLPurifier_AttrTransform` e +implemente o método `transform`, o qual recebe os atributos da tag, além da configuração e contexto, +realiza as mudanças e então retorna os atributos. + +No diretório `library/HTMLPurifier/AttrTransform` há várias classes utilizadas pelo HTML Purifier e +que podemos usar como exemplo para desenvolver as nossas. Todas estendem a classe `HTMLPurifier_AttrTransform` e +implementam o método `transform`. + +### Transformando Elementos + +De forma semelhante às transformações de atributos podemos realizar transformações completas nos elementos. + +Podemos criar uma classe que estenda a `HTMLPurifier_TagTransform` e que +implemente o método `transform`, o qual recebe a tag, a configuração e o contexto. + +No diretório `library/HTMLPurifier/TagTransform` há duas classes utilizadas pelo HTML Purifier, a classe que +transforma a tag **font** e uma classe mais "genérica" que trata todas as outras transformações simples, +a `HTMLPurifier_TagTransform_Simple`. + +### Configurando o HTML Purifier para usar as transformações + +Toda a configuração das transformações ficam nas definições do HTML. +Para pegar as definições basta usar o método `getHTMLDefinition` na **$config** e você receberá uma +instância da `HTMLPurifier_HTMLDefinition`. + +Nesse objeto de definições há 3 propriedades públicas que recebem as nossas instâncias de transformações. + +- **info_tag_transform** é um array cujo índice é o nome da tag a transformar +- **info_attr_transform_pre** é um array de índice numérico que transformará os atributos no início do processo +- **info_attr_transform_post** é um array de índice numérico que transformará os atributos no final do processo + +Poderíamos configurar o HTML Purifier para fazer algumas trocas de teste assim: + +```php +getHTMLDefinition(); + +// Transformará toda tag p em uma tag div com CSS inline para deixar o texto com sublinhado +$htmlDefinition->info_tag_transform['p'] = new HTMLPurifier_TagTransform_Simple( + 'div', + 'text-decoration:underline;' +); + +// Converte o atributo bgcolor em um CSS inline +$htmlDefinition->info_attr_transform_pre[] = new HTMLPurifier_AttrTransform_BgColor(); + +// Tudo configurado basta instanciar o HTML Purifier +$purifier = new HTMLPurifier($config); +$html_limpo = $purifier->purify($html_sujo); +``` + +## Criando as nossas transformações + +Para o meu caso eu precisava cumprir esses objetivos: + +- Adicionar a classe **img-responsive** às tags **img** +- Adicionar as classes **table** e **table-condensed** às tags **table** +- Adicionar a classe **table-bordered** se a tag **table** possuir o atributo **border** +- Cercar a tabela com uma **div** que possua a classe **table-responsive** + +Com isso em mente vamos ao trabalho. + +### Adicionando as classes básicas nas tags + +Para adicionar as classes padrões que não dependem de alguma verificação criei uma classe genérica baseada na +classe `HTMLPurifier_TagTransform_Simple`. + +```php +classes = array_unique(array_map('trim', $classes)); + } + + /** + * @param HTMLPurifier_Token_Tag $tag + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_Token_Tag + */ + public function transform($tag, $config, $context) + { + // Se for a tag de fim não precisa alterar nada + if (empty($this->classes) || $tag instanceof HTMLPurifier_Token_End) { + return $tag; + } + + $new_tag = clone $tag; + + if (empty($new_tag->attr['class'])) { + $new_tag->attr['class'] = ''; + } + + $new_tag->attr['class'] = $this->appendClasses($new_tag->attr['class']); + return $new_tag; + } + + /** + * Concatena as classes da instância à string de classes já definidas + * @param string $classes + * @return string + */ + protected function appendClasses($classes = '') + { + return implode( + ' ', + array_unique( + array_merge( + array_map('trim', explode(' ', $classes)), + $this->classes + ) + ) + ); + } +} +``` + +Então configuramos a definição do HTML com as novas instâncias de transformações. + +```php +info_tag_transform['table'] = new TagClassTransform('table table-condensed'); +$htmlDefinition->info_tag_transform['img'] = new TagClassTransform('img-responsive'); +``` + +Com essa configuração já podemos converter esse HTML: + +```html +

+ + + + + + + + + +
Hey!Ho!Lets Go!
+``` + +Nessa versão quase pronta: + +```html +

hello_world.jpg

+ + + + + + + + + +
Hey!Ho!Lets Go!
+``` + +### Trocando o atributo border pela classe table-bordered + +Precisamos fazer a troca do atributo **border**, independente do seu valor, pela classe +**table-bordered**. Para isso basta criar uma classe estendendo a `HTMLPurifier_AttrTransform` e +então adicionar uma instância dela ao array `$htmlDefinition->info_attr_transform_pre`. + +```php +get('CurrentToken')->name !== 'table') { + return $attr; + } + + // A intenção é trocar o atributo, então já podemos removê-lo + unset($attr['border']); + + if (empty($attr['class'])) { + $attr['class'] = ''; + } + + $attr['class'] = $this->appendClasses($attr['class']); + + return $attr; + } + + /** + * Concatena as classes da instância à string de classes já definidas + * Ps: sim, poderíamos fazer uma trait + * @param string $classes + * @return string + */ + protected function appendClasses($classes = '') + { + return implode( + ' ', + array_unique( + array_merge( + array_map('trim', explode(' ', $classes)), + $this->classes + ) + ) + ); + } +} + +// Então adicionar nas definições HTML +$htmlDefinition->info_attr_transform_pre[] = new TableBorderAttrTransform(); +``` + +E agora aquele HTML sujo: + +```html +

+ + + + + + + + + +
Hey!Ho!Lets Go!
+``` + +Fica assim: + +```html +

hello_world.jpg

+ + + + + + + + + +
Hey!Ho!Lets Go!
+``` + +## Criando um wrapper para a tabela + +Para a tabela não extrapolar os limites da área devemos cercá-la com uma **div.table-responsive**. + +Agora é hora de bater um pouco a cabeça. Como usar a transformação para criar um wrapper na nossa tabela? + +Simplesmente não vamos utilizar, afinal não queremos transformar uma table e sim inserir algo antes e depois dela. + +O HTML Purifier usa a classe `HTMLPurifier_Injector` para fazer inserções no HTML. +Por exemplo a opção `AutoFormat.Linkify` usa injector para inserir a tag **a** com o atributo **href** +em torno de um texto que case com a regra de URL. + +Utilizar injector é mais complexo do que uma TagTransform por que você analisará os tokens que formam o HTML, +mas é possível resolver mais fácil do que os quebra-cabeças do novo filme da Lara Croft. + +Dê uma olhada no diretório `library\HTMLPurifier\Injector` e ficará surpreso com as inserções que o Purifier já possui. + +Basicamente a `HTMLPurifier_Injector` possui 3 métodos para trabalhar com os tokens analisados. + +- **handleText**: chamado quando o token é um texto +- **handleElement**: chamado quando o token é uma tag de início ou vazia +- **handleEnd**: chamado quando o token é uma tag de fim + +Todos os métodos recebem uma referência de `HTMLPurifier_Token` e ela será modificada caso queira adicionar algo. +Geralmente será convertida em um array e então adicionará tokens neste array na sequência necessária. + +É importante observar as propriedades **name** e **needed** da `HTMLPurifier_Injector`. + +A propriedade `name` indica o nome do seu injector e a `needed` indica quais tags e atributos são necessários para ela +funcionar (a `HTMLPurifier_Injector` identifica o `needed` e caso exista alguma regra que proíba as tags e +atributos já cancela a sua execução). + + Nosso injector ficou assim: + + ```php + ['class'], + ]; + + /** + * @param HTMLPurifier_Token_Start $token + */ + public function handleElement(&$token) + { + /* + * Esse método é executado em todo token que indica um início + * Se não for uma tag table simplesmente ignoramos + */ + if (!$token->is_tag || $token->name !== 'table') { + return; + } + + // Copiando o token para não o perdermos + $table = clone $token; + + /* + * Transformando o token original em um array e + * inserindo os tokens na sequência necessária + */ + $token = [ + new HTMLPurifier_Token_Start('div', ['class' => 'table-responsive']), + $table, + ]; + } + + /** + * @param HTMLPurifier_Token_End $token + */ + public function handleEnd(&$token) + { + /* + * Esse método é executado em todo token que indica um fim + * Se não for uma tag table simplesmente ignoramos + */ + if (!$token->is_tag || $token->name !== 'table') { + return; + } + + // Copiando o token para não o perdermos + $table = clone $token; + + /* + * Transformando o token original em um array e + * inserindo os tokens na sequência necessária + */ + $token = [ + $table, + new HTMLPurifier_Token_End('div'), + ]; + } +} +``` + +Vamos adicionar nosso injector na definição HTML: + +```php +info_injector[] = new TableResponsiveWrapperInjector(); +``` + +E agora nosso HTML limpo ficou pronto: + +```html +

hello_world.jpg

+ +
+ + + + + + + +
Hey!Ho!Lets Go!
+``` + +### Código fonte completo + +Você pode visualizar o código completo no meu [Gist](https://gist.github.com/brunogasparetto/63f42e44388f13153b57e4c02df33f27). + +## Algumas dicas extras + +Use o HTML Purifier antes de persistir os dados (indiferente se utilizar arquivos ou banco de dados), pois o processo de analisar e converter o HTML é custoso e não deve ser executado todas as vezes que for exibir na tela. Então o melhor é já salvar limpo. + +Reduza o HTML antes de salvá-lo para economizar espaço. Durante esse processo eu utilizei a [Tiny Html Minifier](https://github.com/jenstornell/tiny-html-minifier) e gostei do resultado. + +Caso você permita edição é bom salvar o HTML original. diff --git a/_posts/2024-06-01-criando-notificacoes-personalizadas-no-fluig.md b/_posts/2024-06-01-criando-notificacoes-personalizadas-no-fluig.md new file mode 100644 index 0000000..d9e7f5a --- /dev/null +++ b/_posts/2024-06-01-criando-notificacoes-personalizadas-no-fluig.md @@ -0,0 +1,170 @@ +--- +title: "Criando notificações personalizadas no Fluig" +description: "Cansado de só enviar notificações por e-mail? Vamos aprender a criar notificações personalizadas no sistema." +date: 2024-06-01 15:00:00 -0400 +categories: fluig tutorial +tags: + - fluig + - desenvolvimento + - java + - javascript +--- + +Notificações personalizadas por e-mail são ótimas (utilizando o método `notifier.notify`), mas há situações nas +quais queremos que o alerta seja exibido no próprio Fluig, no ícone de notificações. + +Tive essa necessidade de notificações no sistema durante o desenvolvimento de um processo para **Gestão de Frotas**, +pois não queria incomodar demais os gestores avisando sobre CNHs vencidas, afinal os motoristas já receberiam a +notificação por e-mail, então aos gestores era interessante simplesmente alertar que havia alguma coisa acontecendo. + +Após muita pesquisa e testes consegui fazer essa *mágica* no Fluig e compartilho com você para que não sofra tanto +quanto eu. + +## Como criar as notificações personalizadas + +O Fluig não possuí, de forma simples, um modo de criar as notificações personalizadas. No máximo temos como configurar +quais são as notificações padrões dos novos usuários (no menu **Painel de Controle → Pessoas → Notificações padrões para novos usuários**). +No mesmo lugar que configuramos os processo de limpeza das notificações. + +Os usuários também podem clicar no ícone de Notificações, ir em Configurações, e marcar quais notificações receberá por +e-mail, mas não é possível criar/modificar as permissões. + +Felizmente o Fluig possuí métodos no WS REST que permite a criação das notificações personalizadas. + +Basicamente temos que executar 2 processos para isso: + +1. Criar um módulo de Notificações; +1. Criar os eventos de Notificação; + +O módulo de notificações é como se fosse um agrupador dos tipos das notificações, enquanto os eventos indicam sobre o que +é a notificação disparada. + +Exemplo de Módulos e Eventos + +Para facilitar o processo de chamada ao REST farei isso nas **Ferramentas do desenvolvedor** no navegador, já autenticado no Fluig +com um usuário administrador. + +No Chrome basta apertar `F12` ou `Ctrl + Shift + i` ou ir no menu *Mais ferramentas → Ferramentas do desenvolvedor*. +Quase todos os navegadores modernos possuem alguma ferramenta similar. + +Com as Ferramentas do Desenvolvedor aberta temos que ir na aba `Console`, onde podemos digitar código JavaScript, para realizar +as chamadas ao Fluig. + +Tudo isso é possível fazer por qualquer ferramenta de requisições (Postman, Thunder Client, etc.), porém executando no navegador +com usuário já autenticado não precisará se preocupar com a autenticação. + +## Criando o módulo de Notificações Personalizadas + +O endpoint `/api/public/alert/module/create` nos permite criar o módulo de notificações no Fluig. + +Então, na aba Console da Ferramenta do desenvolvedor basta colar esse código e teclar Enter. + +```javascript +fetch("/api/public/alert/module/create", { + method: "POST", + redirect: "follow", + headers: { + "Content-Type": "application/json;charset=utf-8", + }, + body: JSON.stringify({ + moduleKey: "PERSONALIZADAS", // É o Código do Módulo que estamos criando + descriptionKey: "Personalizadas" // Descrição do Módulo + }), +}); +``` + +As propriedades `moduleKey` e `descriptionKey` identificam o código (como se fosse o userCode de um usuário) +do módulo e a sua descrição, respectivamente, então você pode colocar o nome que mais agradar. + +Agora podemos acessar o endpoint `/api/public/alert/module/findVoList` para verificar se o módulo foi criado +e pegar o ID do módulo, pois precisaremos dele para adicionar os eventos. + +No navegador, em uma aba que está com uma página do Fluig aberta, pode acessar a URL indicada. +Por exemplo: . + +Módulo criado + +De posse dessa informação, que o ID é **36**, podemos criar os eventos. + +## Criando os eventos de Notificações Personalizadas + +O endpoint `/api/public/alert/event/createEvent` permite criar o evento de notificação. Então voltemos ao Console +da Ferramenta do Desenvolvedor, no Fluig com usuário autenticado, e basta colar o seguinte código: + +```javascript +fetch("/api/public/alert/event/createEvent", { + method: "POST", + redirect: "follow", + headers: { + "Content-Type": "application/json;charset=utf-8", + }, + body: JSON.stringify({ + eventKey: "FROTA_CNH_VENCIDA", // Este Código é o que usaremos para disparar a notificação + required: true, + descriptionKey: "Frota: CNH Vencida", + singleDescriptionKey: "A CNH está vencida.", + groupDescriptionKey: "A CNH está vencida.", + eventIcon: "/globalalertapi/resources/images/exclamation-sign.png", + moduleId: 36, // Este é o ID do módulo que criamos + grouped: false, + canRemove: false, + removeAfterExecAction: true, + onlyAdmin: false, + }), +}); +``` + +Podemos conferir no painel de controle que agora aparece o módulo e evento criado. Aproveito para desmarcar o envio por e-mail, +pois é justamente um dos motivos pelo qual fiz a notificação personalizada. Caso deixe marcado o usuário também receberá um e-mail. + +Módulo criado + +## Disparando a Notificação + +Para disparar a notificação é necessário que o código seja executado no back-end do Fluig. Isso pode ser feito em algum evento +ou criar um dataset para isso. + +O código para disparar é bem simples. Vou demonstrar em um dataset. + +```javascript +function createDataset(fields, constraints, sorts) { + var dataset = DatasetBuilder.newDataset(); + dataset.addColumn("executado"); + + var alertService = fluigAPI.getAlertService(); + + var objeto = new com.totvs.technology.foundation.alert.GenericAlertObject( + -1, + "FROTA_CNH_VENCIDA", + "O motorista XPTO foi notificado por e-mail que está com a CNH Vencida!", + null, + null, + null + ); + + alertService.sendNotification( + "FROTA_CNH_VENCIDA", + null, + "userCode de quem vai receber a notificação", + objeto, + null, + null, + null + ); + + return dataset; +} + +``` + +E agora podemos ver a notificação disparada. + +Módulo criado + +## Considerações Finais + +A possibilidade de enviar notificações personalizadas auxilia muito em várias atividades, mas tenha cuidado para não +poluir demais os usuários com notificações e e-mails personalizados ao mesmo tempo. + +Para quem estiver curioso de como utilizei na prática pode conferir esse [Gist](https://gist.github.com/brunogasparetto/a56010eaf4bf43e3d29b523ddca9ca0c#exemplo-pr%C3%A1tico). Nele eu demonstro o meu Dataset jornalizado que busca as CNHs inativas, dispara os +e-mails personalizados e notifica os gestores. diff --git a/assets/img/2024-06-01-criando-notificacoes-personalizadas-no-fluig/evento_criado.jpg b/assets/img/2024-06-01-criando-notificacoes-personalizadas-no-fluig/evento_criado.jpg new file mode 100644 index 0000000..ef83a91 Binary files /dev/null and b/assets/img/2024-06-01-criando-notificacoes-personalizadas-no-fluig/evento_criado.jpg differ diff --git a/assets/img/2024-06-01-criando-notificacoes-personalizadas-no-fluig/modulo_criado.jpg b/assets/img/2024-06-01-criando-notificacoes-personalizadas-no-fluig/modulo_criado.jpg new file mode 100644 index 0000000..8d1f457 Binary files /dev/null and b/assets/img/2024-06-01-criando-notificacoes-personalizadas-no-fluig/modulo_criado.jpg differ diff --git a/assets/img/2024-06-01-criando-notificacoes-personalizadas-no-fluig/modulos_eventos_padrao.jpg b/assets/img/2024-06-01-criando-notificacoes-personalizadas-no-fluig/modulos_eventos_padrao.jpg new file mode 100644 index 0000000..babcb2f Binary files /dev/null and b/assets/img/2024-06-01-criando-notificacoes-personalizadas-no-fluig/modulos_eventos_padrao.jpg differ diff --git a/assets/img/2024-06-01-criando-notificacoes-personalizadas-no-fluig/notificacao_disparada.jpg b/assets/img/2024-06-01-criando-notificacoes-personalizadas-no-fluig/notificacao_disparada.jpg new file mode 100644 index 0000000..b12468f Binary files /dev/null and b/assets/img/2024-06-01-criando-notificacoes-personalizadas-no-fluig/notificacao_disparada.jpg differ