* @license GPL-2.0+ https://www.gnu.org/licenses/gpl-2.0.html
* @link https://aisk.chat
*/
if ( ! defined('ABSPATH') ) {
exit;
}
/**
* AISK Response Formatter Class
*
* Handles formatting of chatbot responses for better readability and user experience.
*
* @category Class
* @package AISK
* @author AISK Team
', $text); $text = '
' . $text . '
'; // Remove empty paragraphs $text = preg_replace('/\s*<\/p>/', '', $text);
return $text;
}
/**
* Process and convert URLs to clickable links (Enhanced)
*
* @param string $text The text containing URLs
* @return string The text with clickable links
*/
private static function process_links($text) {
// Enhanced URL pattern for matching various URL formats
$url_pattern = '/(https?:\/\/[^\s<>"{}|\\^`\[\]]+)/i';
// Replace URLs with clickable links
$text = preg_replace_callback($url_pattern, function($matches) {
$url = $matches[1];
$display_url = $url;
// Extract domain for better display
$parsed_url = wp_parse_url($url);
if ($parsed_url && isset($parsed_url['host'])) {
$domain = $parsed_url['host'];
$path = isset($parsed_url['path']) ? $parsed_url['path'] : '';
// Create a more readable display URL
if (strlen($url) > 50) {
$display_url = $domain . $path;
if (strlen($display_url) > 40) {
$display_url = $domain . (strlen($path) > 20 ? '...' . substr($path, -15) : $path);
}
}
}
// Add external link icon for external URLs
// Determine site host safely for comparison
$site_parsed = wp_parse_url( home_url() );
$site_host = isset( $site_parsed['host'] ) ? $site_parsed['host'] : '';
$is_external = ! isset( $parsed_url['host'] ) || ( $site_host && strtolower( $parsed_url['host'] ) !== strtolower( $site_host ) );
$external_icon = $is_external ? '' : '';
return sprintf(
'%s%s',
esc_url($url),
$is_external ? 'external-link' : 'internal-link',
esc_html($display_url),
$external_icon
);
}, $text);
return $text;
}
/**
* Process and convert email addresses to clickable mailto links
*
* @param string $text The text containing email addresses
* @return string The text with clickable email links
*/
private static function process_emails($text) {
// Email pattern to match email addresses
$email_pattern = '/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/';
// Replace email addresses with clickable mailto links
$text = preg_replace_callback($email_pattern, function($matches) {
$email = $matches[0];
return sprintf(
'%s',
esc_html($email),
esc_html($email)
);
}, $text);
return $text;
}
/**
* Process and format lists (Enhanced)
*
* @param string $text The text containing lists
* @return string The text with formatted lists
*/
private static function process_lists($text) {
// Split text into lines
$lines = explode('
', $text);
$formatted_lines = [];
$in_list = false;
$list_type = '';
$list_items = [];
$list_level = 0;
foreach ($lines as $line) {
$line = trim($line);
// Check for bullet points (enhanced patterns)
if (preg_match('/^[\s]*([•\-\*\+])[\s]+(.+)$/', $line, $matches)) {
$bullet = $matches[1];
$content = trim($matches[2]);
if (!$in_list || $list_type !== 'ul') {
if ($in_list) {
$formatted_lines[] = self::close_list($list_items, $list_type, $list_level);
$list_items = [];
}
$in_list = true;
$list_type = 'ul';
$list_level = 0;
}
$list_items[] = $content;
}
// Check for numbered lists (enhanced patterns)
elseif (preg_match('/^[\s]*(\d+)[\.\)][\s]+(.+)$/', $line, $matches)) {
$number = $matches[1];
$content = trim($matches[2]);
if (!$in_list || $list_type !== 'ol') {
if ($in_list) {
$formatted_lines[] = self::close_list($list_items, $list_type, $list_level);
$list_items = [];
}
$in_list = true;
$list_type = 'ol';
$list_level = 0;
}
$list_items[] = $content;
}
// Check for nested lists (indented content)
elseif ($in_list && preg_match('/^[\s]{2,}(.+)$/', $line, $matches)) {
$content = trim($matches[1]);
// Check if this is a new nested list item
if (preg_match('/^([•\-\*\+])[\s]+(.+)$/', $content, $nested_matches)) {
// Close current list and start nested list
$formatted_lines[] = self::close_list($list_items, $list_type, $list_level);
$list_items = [trim($nested_matches[2])];
$list_type = 'ul';
$list_level++;
} elseif (preg_match('/^(\d+)[\.\)][\s]+(.+)$/', $content, $nested_matches)) {
// Close current list and start nested numbered list
$formatted_lines[] = self::close_list($list_items, $list_type, $list_level);
$list_items = [trim($nested_matches[2])];
$list_type = 'ol';
$list_level++;
} else {
// Continue current list item
if (!empty($list_items)) {
$list_items[count($list_items) - 1] .= ' ' . $content;
}
}
}
// End of list
else {
if ($in_list) {
$formatted_lines[] = self::close_list($list_items, $list_type, $list_level);
$list_items = [];
$in_list = false;
$list_type = '';
$list_level = 0;
}
if (!empty($line)) {
$formatted_lines[] = $line;
}
}
}
// Close any remaining list
if ($in_list) {
$formatted_lines[] = self::close_list($list_items, $list_type, $list_level);
}
return implode('
', $formatted_lines);
}
/**
* Close a list and return formatted HTML (Enhanced)
*
* @param array $items The list items
* @param string $type The list type (ul or ol)
* @param int $level The nesting level
* @return string The formatted list HTML
*/
private static function close_list($items, $type, $level = 0) {
if (empty($items)) {
return '';
}
$indent_class = $level > 0 ? " chat-list-nested chat-list-level-{$level}" : '';
$html = "<{$type} class=\"chat-list{$indent_class}\">";
foreach ($items as $item) {
$html .= "
$1', $text);
// Highlighted text: ==text==
$text = preg_replace('/==(.*?)==/', '$1', $text);
return $text;
}
/**
* Process code blocks (Enhanced)
*
* @param string $text The text containing code blocks
* @return string The text with formatted code blocks
*/
private static function process_code_blocks($text) {
// Code blocks with language specification: ```language code```
$text = preg_replace_callback('/```(\w+)?\s*\n?([^`]+)```/', function($matches) {
$language = !empty($matches[1]) ? $matches[1] : '';
$code = trim($matches[2]);
$language_class = $language ? " language-{$language}" : '';
return '' . esc_html($code) . '';
}, $text);
return $text;
}
/**
* Process quote blocks (Enhanced)
*
* @param string $text The text containing quotes
* @return string The text with formatted quotes
*/
private static function process_quotes($text) {
// Quote blocks: lines starting with >
$lines = explode(''; $html .= ''; return $html; } /** * Process headings * * @param string $text The text containing headings * @return string The text with formatted headings */ private static function process_headings($text) { // Headings: # H1, ## H2, ### H3, etc. $text = preg_replace('/^#\s+(.+)$/m', '' . esc_html(implode(' ', $content)) . '
'; $html .= '
\s*<\/p>/', '', $html);
// Ensure proper spacing around lists, code blocks, and other elements
$html = preg_replace('/
\s*(<[uo]l|