Elgg  Version 5.0
HtmlFormatter.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Views;
4 
11 
16 
17  use Loggable;
18 
37  public const MENTION_REGEX = '/<a[^>]*?>.*?<\/a>|<.*?>|(^|\s|\!|\.|\?|>|\G)+(@([^\s<&]+))/iu';
38 
39  protected ViewsService $views;
40 
42 
43  protected AutoParagraph $autop;
44 
52  public function __construct(
53  ViewsService $views,
54  EventsService $events,
55  AutoParagraph $autop
56  ) {
57  $this->views = $views;
58  $this->events = $events;
59  $this->autop = $autop;
60  }
61 
75  public function formatBlock(string $html, array $options = []): string {
76  $options = array_merge([
77  'parse_urls' => true,
78  'parse_emails' => true,
79  'parse_mentions' => true,
80  'sanitize' => true,
81  'autop' => true,
82  ], $options);
83 
84  $params = [
85  'options' => $options,
86  'html' => $html,
87  ];
88 
89  $params = $this->events->triggerResults('prepare', 'html', [], $params);
90 
91  $html = (string) elgg_extract('html', $params);
92  $options = (array) elgg_extract('options', $params);
93 
94  if (elgg_extract('parse_urls', $options)) {
95  $html = $this->parseUrls($html);
96  }
97 
98  if (elgg_extract('parse_emails', $options)) {
99  $html = $this->parseEmails($html);
100  }
101 
102  if (elgg_extract('parse_mentions', $options)) {
103  $html = $this->parseMentions($html);
104  }
105 
106  if (elgg_extract('sanitize', $options)) {
107  $html = elgg_sanitize_input($html);
108  }
109 
110  if (elgg_extract('autop', $options)) {
111  $html = $this->addParagaraphs($html);
112  }
113 
114  return $html;
115  }
116 
124  public function parseUrls(string $text): string {
125 
126  $linkify = new \Misd\Linkify\Linkify();
127 
128  return $linkify->processUrls($text, ['attr' => ['rel' => 'nofollow']]);
129  }
130 
139  public function parseEmails(string $text): string {
140  $linkify = new \Misd\Linkify\Linkify();
141 
142  return $linkify->processEmails($text, ['attr' => ['rel' => 'nofollow']]);
143  }
144 
153  public function parseMentions(string $text): string {
154  $callback = function (array $matches) {
155  $source = elgg_extract(0, $matches);
156  $preceding_char = elgg_extract(1, $matches);
157  $username = elgg_extract(3, $matches);
158 
159  if (empty($username)) {
160  return $source;
161  }
162 
163  try {
164  _elgg_services()->accounts->assertValidUsername($username);
165  } catch (RegistrationException $e) {
166  return $source;
167  }
168 
170 
171  // Catch the trailing period when used as punctuation and not a username.
172  $period = '';
173  if (!$user && str_ends_with($username, '.')) {
175  $period = '.';
176  }
177 
178  if (!$user) {
179  return $source;
180  }
181 
182  if (elgg_get_config('mentions_display_format') === 'username') {
183  $replacement = elgg_view_url($user->getURL(), "@{$user->username}");
184  } else {
185  $replacement = elgg_view_url($user->getURL(), $user->getDisplayName());
186  }
187 
188  return $preceding_char . $replacement . $period;
189  };
190 
191  return preg_replace_callback(self::MENTION_REGEX, $callback, $text);
192  }
193 
201  public function addParagaraphs(string $string): string {
202  try {
203  $result = $this->autop->process($string);
204  if ($result !== false) {
205  return $result;
206  }
207  } catch (\RuntimeException $e) {
208  $this->getLogger()->warning('AutoParagraph failed to process the string: ' . $e->getMessage());
209  }
210 
211  return $string;
212  }
213 
238  public function formatAttributes(array $attrs = []): string {
239  if (empty($attrs)) {
240  return '';
241  }
242 
243  $attributes = [];
244 
245  foreach ($attrs as $attr => $val) {
246  if (!str_starts_with($attr, 'data-') && str_contains($attr, '_')) {
247  // this is probably a view $vars variable not meant for output
248  continue;
249  }
250 
251  $attr = strtolower($attr);
252 
253  if (!isset($val) || $val === false) {
254  continue;
255  }
256 
257  if ($val === true) {
258  $val = $attr; //e.g. checked => true ==> checked="checked"
259  }
260 
261  if (is_array($val) && empty($val)) {
262  //e.g. ['class' => []]
263  continue;
264  }
265 
266  if (is_scalar($val)) {
267  $val = [$val];
268  }
269 
270  if (!is_array($val)) {
271  continue;
272  }
273 
274  // Check if array contains non-scalar values and bail if so
275  $filtered_val = array_filter($val, function($e) {
276  return is_scalar($e);
277  });
278 
279  if (count($val) != count($filtered_val)) {
280  continue;
281  }
282 
283  $val = implode(' ', $val);
284 
285  $val = htmlspecialchars($val, ENT_QUOTES, 'UTF-8', false);
286  $attributes[] = "$attr=\"$val\"";
287  }
288 
289  return implode(' ', $attributes);
290  }
291 
319  public function formatElement(string $tag_name, array $attributes = [], string $text = '', array $options = []): string {
320  if ($tag_name === '') {
321  throw new InvalidArgumentException('$tag_name is required');
322  }
323 
324  // from http://www.w3.org/TR/html-markup/syntax.html#syntax-elements
325  $is_void = $options['is_void'] ?? in_array(strtolower($tag_name), [
326  'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem',
327  'meta', 'param', 'source', 'track', 'wbr'
328  ]);
329 
330  if (!empty($options['encode_text']) && is_string($text)) {
331  $double_encode = !empty($options['double_encode']);
332  $text = htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8', $double_encode);
333  }
334 
335  $attrs = '';
336  if (!empty($attributes)) {
338  if ($attrs !== '') {
339  $attrs = " $attrs";
340  }
341  }
342 
343  if ($is_void) {
344  return empty($options['is_xml']) ? "<{$tag_name}{$attrs}>" : "<{$tag_name}{$attrs} />";
345  }
346 
347  return "<{$tag_name}{$attrs}>$text</$tag_name>";
348  }
349 
360  public function stripTags(string $string, string $allowable_tags = null): string {
361  $params = [
362  'original_string' => $string,
363  'allowable_tags' => $allowable_tags,
364  ];
365 
366  $string = strip_tags($string, $allowable_tags);
367  return (string) $this->events->triggerResults('format', 'strip_tags', $params, $string);
368  }
369 
397  public function decode(string $string): string {
398  $string = str_replace(
399  ['&gt;', '&lt;', '&amp;', '&quot;', '&#039;'],
400  ['&amp;gt;', '&amp;lt;', '&amp;amp;', '&amp;quot;', '&amp;#039;'],
401  $string
402  );
403  $string = html_entity_decode($string, ENT_NOQUOTES, 'UTF-8');
404  return str_replace(
405  ['&amp;gt;', '&amp;lt;', '&amp;amp;', '&amp;quot;', '&amp;#039;'],
406  ['&gt;', '&lt;', '&amp;', '&quot;', '&#039;'],
407  $string
408  );
409  }
410 
422  public function inlineCss(string $html, string $css, bool $body_only = false): string {
423  if (empty($html) || empty($css)) {
424  return $html;
425  }
426 
427  $inliner = CssInliner::fromHtml($html)->disableStyleBlocksParsing()->inlineCss($css);
428 
429  return $body_only ? $inliner->renderBodyContent() : $inliner->render();
430  }
431 
441  public function normalizeUrls(string $text): string {
442  $pattern = '/\s(?:href|src)=([\'"]\S+[\'"])/i';
443 
444  // find all matches
445  $matches = [];
446  preg_match_all($pattern, $text, $matches);
447 
448  if (empty($matches) || !isset($matches[1])) {
449  return $text;
450  }
451 
452  // go through all the matches
453  $urls = $matches[1];
454  $urls = array_unique($urls);
455 
456  foreach ($urls as $url) {
457  // remove wrapping quotes from the url
458  $real_url = substr($url, 1, -1);
459  // normalize url
460  $new_url = elgg_normalize_url($real_url);
461  // make the correct replacement string
462  $replacement = str_replace($real_url, $new_url, $url);
463 
464  // replace the url in the content
465  $text = str_replace($url, $replacement, $text);
466  }
467 
468  return $text;
469  }
470 }
$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:32
$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.