Elgg  Version 5.1
HtmlFormatter.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Views;
4 
12 
17 
18  use Loggable;
19 
38  public const MENTION_REGEX = '/<a[^>]*?>.*?<\/a>|<.*?>|(^|\s|\!|\.|\?|>|\G)+(@([^\s<&]+))/iu';
39 
40  protected ViewsService $views;
41 
43 
44  protected AutoParagraph $autop;
45 
53  public function __construct(
54  ViewsService $views,
55  EventsService $events,
56  AutoParagraph $autop
57  ) {
58  $this->views = $views;
59  $this->events = $events;
60  $this->autop = $autop;
61  }
62 
76  public function formatBlock(string $html, array $options = []): string {
77  $options = array_merge([
78  'parse_urls' => true,
79  'parse_emails' => true,
80  'parse_mentions' => true,
81  'sanitize' => true,
82  'autop' => true,
83  ], $options);
84 
85  $params = [
86  'options' => $options,
87  'html' => $html,
88  ];
89 
90  $params = $this->events->triggerResults('prepare', 'html', [], $params);
91 
92  $html = (string) elgg_extract('html', $params);
93  $options = (array) elgg_extract('options', $params);
94 
95  if (elgg_extract('parse_urls', $options)) {
96  $html = $this->parseUrls($html);
97  }
98 
99  if (elgg_extract('parse_emails', $options)) {
100  $html = $this->parseEmails($html);
101  }
102 
103  if (elgg_extract('parse_mentions', $options)) {
104  $html = $this->parseMentions($html);
105  }
106 
107  if (elgg_extract('sanitize', $options)) {
108  $html = elgg_sanitize_input($html);
109  }
110 
111  if (elgg_extract('autop', $options)) {
112  $html = $this->addParagaraphs($html);
113  }
114 
115  return $html;
116  }
117 
125  public function parseUrls(string $text): string {
126 
127  $linkify = new \Misd\Linkify\Linkify();
128 
129  return $linkify->processUrls($text, ['attr' => ['rel' => 'nofollow']]);
130  }
131 
140  public function parseEmails(string $text): string {
141  $linkify = new \Misd\Linkify\Linkify();
142 
143  return $linkify->processEmails($text, ['attr' => ['rel' => 'nofollow']]);
144  }
145 
154  public function parseMentions(string $text): string {
155  $callback = function (array $matches) {
156  $source = elgg_extract(0, $matches);
157  $preceding_char = elgg_extract(1, $matches);
158  $username = elgg_extract(3, $matches);
159 
160  if (empty($username)) {
161  return $source;
162  }
163 
164  try {
165  _elgg_services()->accounts->assertValidUsername($username);
166  } catch (RegistrationException $e) {
167  return $source;
168  }
169 
171 
172  // Catch the trailing period when used as punctuation and not a username.
173  $period = '';
174  if (!$user && str_ends_with($username, '.')) {
176  $period = '.';
177  }
178 
179  if (!$user) {
180  return $source;
181  }
182 
183  if (elgg_get_config('mentions_display_format') === 'username') {
184  $replacement = elgg_view_url($user->getURL(), "@{$user->username}");
185  } else {
186  $replacement = elgg_view_url($user->getURL(), $user->getDisplayName());
187  }
188 
189  return $preceding_char . $replacement . $period;
190  };
191 
192  return preg_replace_callback(self::MENTION_REGEX, $callback, $text) ?? $text;
193  }
194 
202  public function addParagaraphs(string $string): string {
203  try {
204  $result = $this->autop->process($string);
205  if ($result !== false) {
206  return $result;
207  }
208  } catch (\RuntimeException $e) {
209  $this->getLogger()->warning('AutoParagraph failed to process the string: ' . $e->getMessage());
210  }
211 
212  return $string;
213  }
214 
239  public function formatAttributes(array $attrs = []): string {
240  if (empty($attrs)) {
241  return '';
242  }
243 
244  $attributes = [];
245 
246  foreach ($attrs as $attr => $val) {
247  if (!str_starts_with($attr, 'data-') && str_contains($attr, '_')) {
248  // this is probably a view $vars variable not meant for output
249  continue;
250  }
251 
252  $attr = strtolower($attr);
253 
254  if (!isset($val) || $val === false) {
255  continue;
256  }
257 
258  if ($val === true) {
259  $val = $attr; //e.g. checked => true ==> checked="checked"
260  }
261 
262  if (is_array($val) && empty($val)) {
263  //e.g. ['class' => []]
264  continue;
265  }
266 
267  if (is_scalar($val)) {
268  $val = [$val];
269  }
270 
271  if (!is_array($val)) {
272  continue;
273  }
274 
275  // Check if array contains non-scalar values and bail if so
276  $filtered_val = array_filter($val, function($e) {
277  return is_scalar($e);
278  });
279 
280  if (count($val) != count($filtered_val)) {
281  continue;
282  }
283 
284  $val = implode(' ', $val);
285 
286  $val = htmlspecialchars($val, ENT_QUOTES, 'UTF-8', false);
287  $attributes[] = "$attr=\"$val\"";
288  }
289 
290  return implode(' ', $attributes);
291  }
292 
320  public function formatElement(string $tag_name, array $attributes = [], string $text = '', array $options = []): string {
321  if ($tag_name === '') {
322  throw new InvalidArgumentException('$tag_name is required');
323  }
324 
325  // from http://www.w3.org/TR/html-markup/syntax.html#syntax-elements
326  $is_void = $options['is_void'] ?? in_array(strtolower($tag_name), [
327  'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem',
328  'meta', 'param', 'source', 'track', 'wbr'
329  ]);
330 
331  if (!empty($options['encode_text']) && is_string($text)) {
332  $double_encode = !empty($options['double_encode']);
333  $text = htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8', $double_encode);
334  }
335 
336  $attrs = '';
337  if (!empty($attributes)) {
339  if ($attrs !== '') {
340  $attrs = " $attrs";
341  }
342  }
343 
344  if ($is_void) {
345  return empty($options['is_xml']) ? "<{$tag_name}{$attrs}>" : "<{$tag_name}{$attrs} />";
346  }
347 
348  return "<{$tag_name}{$attrs}>$text</$tag_name>";
349  }
350 
361  public function stripTags(string $string, string $allowable_tags = null): string {
362  $params = [
363  'original_string' => $string,
364  'allowable_tags' => $allowable_tags,
365  ];
366 
367  $string = strip_tags($string, $allowable_tags);
368  return (string) $this->events->triggerResults('format', 'strip_tags', $params, $string);
369  }
370 
398  public function decode(string $string): string {
399  $string = str_replace(
400  ['&gt;', '&lt;', '&amp;', '&quot;', '&#039;'],
401  ['&amp;gt;', '&amp;lt;', '&amp;amp;', '&amp;quot;', '&amp;#039;'],
402  $string
403  );
404  $string = html_entity_decode($string, ENT_NOQUOTES, 'UTF-8');
405  return str_replace(
406  ['&amp;gt;', '&amp;lt;', '&amp;amp;', '&amp;quot;', '&amp;#039;'],
407  ['&gt;', '&lt;', '&amp;', '&quot;', '&#039;'],
408  $string
409  );
410  }
411 
423  public function inlineCss(string $html, string $css, bool $body_only = false): string {
424  if (empty($html) || empty($css)) {
425  return $html;
426  }
427 
428  $html_with_inlined_css = CssInliner::fromHtml($html)->disableStyleBlocksParsing()->inlineCss($css)->render();
429  $inlined_attribute_converter = CssToAttributeConverter::fromHtml($html_with_inlined_css)->convertCssToVisualAttributes();
430 
431  return $body_only ? $inlined_attribute_converter->renderBodyContent() : $inlined_attribute_converter->render();
432  }
433 
443  public function normalizeUrls(string $text): string {
444  $pattern = '/\s(?:href|src)=([\'"]\S+[\'"])/i';
445 
446  // find all matches
447  $matches = [];
448  preg_match_all($pattern, $text, $matches);
449 
450  if (empty($matches) || !isset($matches[1])) {
451  return $text;
452  }
453 
454  // go through all the matches
455  $urls = $matches[1];
456  $urls = array_unique($urls);
457 
458  foreach ($urls as $url) {
459  // remove wrapping quotes from the url
460  $real_url = substr($url, 1, -1);
461  // normalize url
462  $new_url = elgg_normalize_url($real_url);
463  // make the correct replacement string
464  $replacement = str_replace($real_url, $new_url, $url);
465 
466  // replace the url in the content
467  $text = str_replace($url, $replacement, $text);
468  }
469 
470  return $text;
471  }
472 }
$source
Exception thrown if an argument is not of the expected type.
$params
Saves global plugin settings.
Definition: save.php:13
Exception thrown if an error which can only be found on runtime occurs.
elgg_get_config(string $name, $default=null)
Get an Elgg configuration value.
formatAttributes(array $attrs=[])
Converts an associative array into a string of well-formed HTML/XML attributes Returns a concatenated...
Elgg registration action.
parseMentions(string $text)
Takes a string and turns any @ mentions into a formatted link.
elgg_get_user_by_username(string $username, bool $try_email=false)
Get a user by username.
Definition: users.php:39
decode(string $string)
Decode HTML markup into a raw text string.
Events service.
$username
Definition: delete.php:23
normalizeUrls(string $text)
Replaces relative urls in href or src attributes in text.
elgg_sanitize_input($input)
Filter input from a given string based on registered events.
Definition: input.php:77
Could not register a new user for whatever reason.
Create wrapper P and BR elements in HTML depending on newlines.
$options
Elgg admin footer.
Definition: footer.php:6
$html
Definition: section.php:10
elgg_extract($key, $array, $default=null, bool $strict=true)
Checks for $array[$key] and returns its value if it exists, else returns $default.
Definition: elgglib.php:254
trait Loggable
Enables adding a logger.
Definition: Loggable.php:14
stripTags(string $string, string $allowable_tags=null)
Strip tags and offer plugins the chance.
formatElement(string $tag_name, array $attributes=[], string $text= '', array $options=[])
Format an HTML element.
const MENTION_REGEX
Mentions regex.
$user
Definition: ban.php:7
Views service.
Various helper method for formatting and sanitizing output.
$css
Definition: install.css.php:5
parseEmails(string $text)
Takes a string and turns any email addresses into formatted links.
addParagaraphs(string $string)
Create paragraphs from text with line spacing.
formatBlock(string $html, array $options=[])
Prepare HTML output.
getLogger()
Returns logger.
Definition: Loggable.php:37
elgg_view_url(string $href, string $text=null, array $options=[])
Helper function for outputting urls.
Definition: views.php:1481
__construct(ViewsService $views, EventsService $events, AutoParagraph $autop)
Output constructor.
foreach($plugin_guids as $guid) if(empty($deactivated_plugins)) $url
Definition: deactivate.php:39
_elgg_services()
Get the global service provider.
Definition: elgglib.php:346
inlineCss(string $html, string $css, bool $body_only=false)
Adds inline style to html content.
elgg_normalize_url(string $url)
Definition: output.php:163
$text
Definition: button.php:33
$attributes
Elgg AJAX loader.
Definition: ajax_loader.php:10
if(empty($title)&&empty($body)) if(!empty($link)) $attrs
Definition: message.php:28
parseUrls(string $text)
Takes a string and turns any URLs into formatted links.