Elgg  Version 6.2
HtmlFormatter.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Views;
4 
13 
18 
19  use Loggable;
20 
39  public const MENTION_REGEX = '/<a[^>]*?>.*?<\/a>|<.*?>|(^|\s|\!|\.|\?|>|\G)+(@([^\s<&]+))/iu';
40 
48  public function __construct(
49  protected ViewsService $views,
50  protected EventsService $events,
51  protected AutoParagraph $autop
52  ) {
53  }
54 
68  public function formatBlock(string $html, array $options = []): string {
69  $options = array_merge([
70  'parse_urls' => true,
71  'parse_emails' => true,
72  'parse_mentions' => true,
73  'sanitize' => true,
74  'autop' => true,
75  ], $options);
76 
77  $params = [
78  'options' => $options,
79  'html' => $html,
80  ];
81 
82  $params = $this->events->triggerResults('prepare', 'html', [], $params);
83 
84  $html = (string) elgg_extract('html', $params);
85  $options = (array) elgg_extract('options', $params);
86 
87  if (elgg_extract('parse_urls', $options)) {
88  $html = $this->parseUrls($html);
89  }
90 
91  if (elgg_extract('parse_emails', $options)) {
92  $html = $this->parseEmails($html);
93  }
94 
95  if (elgg_extract('parse_mentions', $options)) {
96  $html = $this->parseMentions($html);
97  }
98 
99  if (elgg_extract('sanitize', $options)) {
100  $html = elgg_sanitize_input($html);
101  }
102 
103  if (elgg_extract('autop', $options)) {
104  $html = $this->addParagaraphs($html);
105  }
106 
107  return $html;
108  }
109 
117  public function parseUrls(string $text): string {
118 
119  $linkify = new \Misd\Linkify\Linkify();
120 
121  return $linkify->processUrls($text, ['attr' => ['rel' => 'nofollow']]);
122  }
123 
132  public function parseEmails(string $text): string {
133  $linkify = new \Misd\Linkify\Linkify();
134 
135  return $linkify->processEmails($text, ['attr' => ['rel' => 'nofollow']]);
136  }
137 
146  public function parseMentions(string $text): string {
147  $callback = function (array $matches) {
148  $source = elgg_extract(0, $matches);
149  $preceding_char = elgg_extract(1, $matches);
150  $username = elgg_extract(3, $matches);
151 
152  if (empty($username)) {
153  return $source;
154  }
155 
156  try {
157  _elgg_services()->accounts->assertValidUsername($username);
158  } catch (RegistrationException $e) {
159  return $source;
160  }
161 
163 
164  // Catch the trailing period when used as punctuation and not a username.
165  $period = '';
166  if (!$user && str_ends_with($username, '.')) {
168  $period = '.';
169  }
170 
171  if (!$user) {
172  return $source;
173  }
174 
175  if (elgg_get_config('mentions_display_format') === 'username') {
176  $replacement = elgg_view_url($user->getURL(), "@{$user->username}");
177  } else {
178  $replacement = elgg_view_url($user->getURL(), $user->getDisplayName());
179  }
180 
181  return $preceding_char . $replacement . $period;
182  };
183 
184  return preg_replace_callback(self::MENTION_REGEX, $callback, $text) ?? $text;
185  }
186 
194  public function addParagaraphs(string $string): string {
195  try {
196  $result = $this->autop->process($string);
197  if ($result !== false) {
198  return $result;
199  }
200  } catch (\RuntimeException $e) {
201  $this->getLogger()->warning('AutoParagraph failed to process the string: ' . $e->getMessage());
202  }
203 
204  return $string;
205  }
206 
231  public function formatAttributes(array $attrs = []): string {
232  if (empty($attrs)) {
233  return '';
234  }
235 
236  $attributes = [];
237 
238  foreach ($attrs as $attr => $val) {
239  if (!str_starts_with($attr, 'data-') && str_contains($attr, '_')) {
240  // this is probably a view $vars variable not meant for output
241  continue;
242  }
243 
244  $attr = strtolower($attr);
245 
246  if (!isset($val) || $val === false) {
247  continue;
248  }
249 
250  if ($val === true) {
251  $val = $attr; //e.g. checked => true ==> checked="checked"
252  }
253 
254  if (is_array($val) && empty($val)) {
255  //e.g. ['class' => []]
256  continue;
257  }
258 
259  if (is_scalar($val)) {
260  $val = [$val];
261  }
262 
263  if (!is_array($val)) {
264  continue;
265  }
266 
267  // Check if array contains non-scalar values and bail if so
268  $filtered_val = array_filter($val, function($e) {
269  return is_scalar($e);
270  });
271 
272  if (count($val) != count($filtered_val)) {
273  continue;
274  }
275 
276  $val = implode(' ', $val);
277 
278  $val = htmlspecialchars($val, ENT_QUOTES, 'UTF-8', false);
279  $attributes[] = "$attr=\"$val\"";
280  }
281 
282  return implode(' ', $attributes);
283  }
284 
312  public function formatElement(string $tag_name, array $attributes = [], string $text = '', array $options = []): string {
313  if ($tag_name === '') {
314  throw new InvalidArgumentException('$tag_name is required');
315  }
316 
317  // from http://www.w3.org/TR/html-markup/syntax.html#syntax-elements
318  $is_void = $options['is_void'] ?? in_array(strtolower($tag_name), [
319  'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem',
320  'meta', 'param', 'source', 'track', 'wbr'
321  ]);
322 
323  if (!empty($options['encode_text']) && is_string($text)) {
324  $double_encode = !empty($options['double_encode']);
325  $text = htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8', $double_encode);
326  }
327 
328  $attrs = '';
329  if (!empty($attributes)) {
331  if ($attrs !== '') {
332  $attrs = " $attrs";
333  }
334  }
335 
336  if ($is_void) {
337  return empty($options['is_xml']) ? "<{$tag_name}{$attrs}>" : "<{$tag_name}{$attrs} />";
338  }
339 
340  return "<{$tag_name}{$attrs}>$text</$tag_name>";
341  }
342 
353  public function stripTags(string $string, ?string $allowable_tags = null): string {
354  $params = [
355  'original_string' => $string,
356  'allowable_tags' => $allowable_tags,
357  ];
358 
359  $string = strip_tags($string, $allowable_tags);
360  return (string) $this->events->triggerResults('format', 'strip_tags', $params, $string);
361  }
362 
390  public function decode(string $string): string {
391  $string = str_replace(
392  ['&gt;', '&lt;', '&amp;', '&quot;', '&#039;'],
393  ['&amp;gt;', '&amp;lt;', '&amp;amp;', '&amp;quot;', '&amp;#039;'],
394  $string
395  );
396  $string = html_entity_decode($string, ENT_NOQUOTES, 'UTF-8');
397  return str_replace(
398  ['&amp;gt;', '&amp;lt;', '&amp;amp;', '&amp;quot;', '&amp;#039;'],
399  ['&gt;', '&lt;', '&amp;', '&quot;', '&#039;'],
400  $string
401  );
402  }
403 
415  public function inlineCss(string $html, string $css, bool $body_only = false): string {
416  if (empty($html) || empty($css)) {
417  return $html;
418  }
419 
420  $html_with_inlined_css = CssInliner::fromHtml($html)->disableStyleBlocksParsing()->inlineCss($css)->render();
421  $html_with_css_variables = CssVariableEvaluator::fromHtml($html_with_inlined_css)->evaluateVariables()->render();
422  $inlined_attribute_converter = CssToAttributeConverter::fromHtml($html_with_css_variables)->convertCssToVisualAttributes();
423 
424  return $body_only ? $inlined_attribute_converter->renderBodyContent() : $inlined_attribute_converter->render();
425  }
426 
436  public function normalizeUrls(string $text): string {
437  $pattern = '/\s(?:href|src)=([\'"]\S+[\'"])/i';
438 
439  // find all matches
440  $matches = [];
441  preg_match_all($pattern, $text, $matches);
442 
443  if (empty($matches) || !isset($matches[1])) {
444  return $text;
445  }
446 
447  // go through all the matches
448  $urls = $matches[1];
449  $urls = array_unique($urls);
450 
451  foreach ($urls as $url) {
452  // remove wrapping quotes from the url
453  $real_url = substr($url, 1, -1);
454  // normalize url
455  $new_url = elgg_normalize_url($real_url);
456  // make the correct replacement string
457  $replacement = str_replace($real_url, $new_url, $url);
458 
459  // replace the url in the content
460  $text = str_replace($url, $replacement, $text);
461  }
462 
463  return $text;
464  }
465 }
$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:31
decode(string $string)
Decode HTML markup into a raw text string.
Events service.
if($alt_image) $tag_name
Definition: image_block.php:45
$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.
elgg_view_url(string $href,?string $text=null, array $options=[])
Helper function for outputting urls.
Definition: views.php:1427
Create wrapper P and BR elements in HTML depending on newlines.
$html
A wrapper to render a section of the page shell.
Definition: section.php:9
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:256
if($who_can_change_language=== 'nobody') elseif($who_can_change_language=== 'admin_only'&&!elgg_is_admin_logged_in()) $options
Definition: language.php:20
trait Loggable
Enables adding a logger.
Definition: Loggable.php:14
formatElement(string $tag_name, array $attributes=[], string $text= '', array $options=[])
Format an HTML element.
stripTags(string $string,?string $allowable_tags=null)
Strip tags and offer plugins the chance.
const MENTION_REGEX
Mentions regex.
__construct(protected ViewsService $views, protected EventsService $events, protected AutoParagraph $autop)
Output constructor.
$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
foreach($plugin_guids as $guid) if(empty($deactivated_plugins)) $url
Definition: deactivate.php:39
_elgg_services()
Get the global service provider.
Definition: elgglib.php:353
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
$views
Definition: item.php:17
parseUrls(string $text)
Takes a string and turns any URLs into formatted links.