| Current Path : /var/www/html/libraries/noboss/src/Util/ |
| Current File : /var/www/html/libraries/noboss/src/Util/NbHtmlContentUtil.php |
<?php
/**
* @package No Boss Extensions
* @subpackage No Boss Library
* @author No Boss Technology <contact@nobosstechnology.com>
* @copyright Copyright (C) 2026 No Boss Technology. All rights reserved.
* @license GNU Lesser General Public License version 3 or later; see <https://www.gnu.org/licenses/lgpl-3.0.en.html>
*/
namespace Noboss\Library\Util;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Utilitario para preparacao de HTML de conteudo importado (ICS, Google Calendar, etc.).
*/
abstract class NbHtmlContentUtil
{
/**
* Tags permitidas no conteudo da modal (PHP 8.2+ inclui atributo href em anchor).
*/
public const ALLOWED_MODAL_TAGS = '<b><a href><i><br><ul><ol><li>';
/**
* Prepara HTML seguro para exibicao na modal a partir de descricao plana e/ou HTML alternativo.
*
* @param string $plain Descricao em texto plano (DESCRIPTION do ICS).
* @param string|null $htmlAlt HTML alternativo (ex.: X-ALT-DESC), quando disponivel.
*
* @return string
*/
public static function prepareImportedModalContent(string $plain, ?string $htmlAlt = null): string
{
if ($htmlAlt !== null && trim($htmlAlt) !== '') {
$content = html_entity_decode(trim($htmlAlt), ENT_QUOTES | ENT_HTML5, 'UTF-8');
$content = self::flattenAnchorsForLinkify($content);
$content = self::unwrapAngleBracketUrls($content);
$content = strip_tags($content, self::ALLOWED_MODAL_TAGS);
return self::linkifyPlainUrls($content);
}
$plain = trim($plain);
if ($plain === '') {
return '';
}
$content = html_entity_decode($plain, ENT_QUOTES | ENT_HTML5, 'UTF-8');
$content = self::unwrapAngleBracketUrls($content);
$content = nl2br($content);
$content = strip_tags($content, self::ALLOWED_MODAL_TAGS);
return self::linkifyPlainUrls($content);
}
/**
* Remove o rodape promocional dos calendarios publicos de feriados do Google Calendar.
*
* Ex.: "Data comemorativa\nPara ocultar as datas comemorativas, acesse Configuracoes
* do Google Agenda > Feriados no Brasil" → "Data comemorativa"
*
* @param string $text Descricao plana ou HTML (DESCRIPTION / X-ALT-DESC).
*
* @return string
*/
public static function stripGoogleHolidayBoilerplate(string $text): string
{
$text = trim($text);
if ($text === '') {
return '';
}
$plain = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
$plain = preg_replace('/<br\s*\/?>/i', ' ', $plain) ?? $plain;
$plain = strip_tags($plain);
$plain = preg_replace('/\s+/u', ' ', $plain) ?? $plain;
$plain = trim($plain);
if ($plain === '') {
return '';
}
$googleMarker = '/(?:Google\s+(?:Agenda|Calendar|Kalender)|Agenda\s+(?:de|do)\s+Google|Calendrier\s+Google)/iu';
if (preg_match($googleMarker, $plain) !== 1) {
return $text;
}
$hideIntro = implode(
'|',
[
'Para ocultar(?: as)? datas comemorativas',
'Para ocultar los d[ií]as festivos',
'Para ocultar las fechas festivas',
'Para esconder os feriados',
'To hide observances',
'To hide(?: public)? holidays',
'Pour masquer les jours f[eé]ri[eé]s',
'So blenden Sie Feiertage',
'Per nascondere (?:i )?giorni festivi',
'Om feestdagen te verbergen',
'Aby ukry[cć] [śs]wi[eę]ta',
'Skjul helligdager',
'Piilota juhlapyh[aä]t',
'Ukryj [śs]wi[eę]ta',
'Feiertage ausblenden',
]
);
if (preg_match('/^(.*?)\s*(?:' . $hideIntro . ')/iu', $plain, $matches) === 1) {
$kept = rtrim(trim($matches[1]), '.');
return $kept !== '' ? $kept : $text;
}
return $text;
}
/**
* Converte URLs entre angulos (ex.: Teams "<https://...>") em URL plana antes do strip_tags.
*
* @param string $text
*
* @return string
*/
protected static function unwrapAngleBracketUrls(string $text): string
{
$result = preg_replace(
'/<((?:https?|mailto):[^>\s]+)>/i',
' $1',
$text
);
return $result ?? $text;
}
/**
* Expoe texto e URL de anchors existentes para o linkify reconstruir links clicaveis.
*
* @param string $html
*
* @return string
*/
protected static function flattenAnchorsForLinkify(string $html): string
{
$result = preg_replace(
'/<a\b[^>]*href=[\'"]([^\'"]+)[\'"][^>]*>(.*?)<\/a>/is',
'$2 $1',
$html
);
return $result ?? $html;
}
/**
* Converte URLs em texto plano em links clicaveis, preservando anchors existentes.
*
* @param string $text Texto ou HTML parcial.
*
* @return string
*/
public static function linkifyPlainUrls(string $text): string
{
if ($text === '') {
return '';
}
$parts = preg_split('/(<a\b[^>]*>.*?<\/a>)/is', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
if ($parts === false) {
return $text;
}
$urlPattern = '/\b(?:(https?):\/\/[^\s<>"\'\)\]]+|mailto:[^\s<>"\'\)\]]+)/i';
foreach ($parts as $index => $part) {
if ($part === '' || preg_match('/^<a\b/i', $part) === 1) {
continue;
}
$parts[$index] = preg_replace_callback(
$urlPattern,
[self::class, 'buildLinkFromUrlMatch'],
$part
) ?? $part;
}
return implode('', $parts);
}
/**
* Callback do preg_replace_callback para montar um anchor a partir de URL detectada.
*
* @param array $matches Grupos capturados pela regex.
*
* @return string
*/
protected static function buildLinkFromUrlMatch(array $matches): string
{
$url = $matches[0];
// Remove pontuacao final comum fora da URL (ex.: "https://exemplo.com).")
$trailing = '';
if (preg_match('/^(.+?)([.,;:!?]+)$/u', $url, $trimMatch) === 1) {
$url = $trimMatch[1];
$trailing = $trimMatch[2];
}
if (!self::isAllowedUrlScheme($url)) {
return $matches[0];
}
$escaped = htmlspecialchars($url, ENT_QUOTES, 'UTF-8');
return "<a href='{$escaped}' target='_blank' rel='noopener noreferrer'>{$escaped}</a>{$trailing}";
}
/**
* Valida se a URL possui esquema permitido (http, https ou mailto).
*
* @param string $url
*
* @return bool
*/
protected static function isAllowedUrlScheme(string $url): bool
{
if (stripos($url, 'mailto:') === 0) {
return true;
}
$scheme = parse_url($url, PHP_URL_SCHEME);
return in_array(strtolower((string) $scheme), ['http', 'https'], true);
}
}