<?php
-
/**
* @file src/Content/Text/HTML.php
*/
use DOMDocument;
use DOMXPath;
-use Friendica\Core\Addon;
+use Friendica\Content\Widget\ContactBlock;
+use Friendica\Core\Hook;
+use Friendica\Core\L10n;
+use Friendica\Core\Config;
+use Friendica\Core\Renderer;
+use Friendica\Model\Contact;
use Friendica\Util\Network;
+use Friendica\Util\Proxy as ProxyUtils;
+use Friendica\Util\Strings;
use Friendica\Util\XML;
+use League\HTMLToMarkdown\HtmlConverter;
class HTML
{
/**
* Made by: ike@piratenpartei.de
* Originally made for the syncom project: http://wiki.piratenpartei.de/Syncom
- * https://github.com/annando/Syncom
+ * https://github.com/annando/Syncom
*
* @brief Converter for HTML to BBCode
* @param string $message
* @param string $basepath
* @return string
+ * @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function toBBCode($message, $basepath = '')
{
$message = str_replace(
[
- "<li><p>",
- "</p></li>",
- ], [
- "<li>",
- "</li>",
- ], $message
+ "<li><p>",
+ "</p></li>",
+ ],
+ [
+ "<li>",
+ "</li>",
+ ],
+ $message
);
// remove namespaces
$xpath = new DomXPath($doc);
$list = $xpath->query("//pre");
foreach ($list as $node) {
- $node->nodeValue = str_replace("\n", "\r", $node->nodeValue);
+ // Ensure to escape unescaped & - they will otherwise raise a warning
+ $safe_value = preg_replace('/&(?!\w+;)/', '&', $node->nodeValue);
+ $node->nodeValue = str_replace("\n", "\r", $safe_value);
}
$message = $doc->saveHTML();
self::tagToBBCode($doc, 'p', ['class' => 'MsoNormal', 'style' => 'margin-left:35.4pt'], '[quote]', '[/quote]');
// Outlook-Quote - Variant 2
- self::tagToBBCode($doc, 'div', ['style' => 'border:none;border-left:solid blue 1.5pt;padding:0cm 0cm 0cm 4.0pt'],
- '[quote]', '[/quote]');
+ self::tagToBBCode(
+ $doc,
+ 'div',
+ ['style' => 'border:none;border-left:solid blue 1.5pt;padding:0cm 0cm 0cm 4.0pt'],
+ '[quote]',
+ '[/quote]'
+ );
// MyBB-Stuff
self::tagToBBCode($doc, 'span', ['style' => 'text-decoration: underline;'], '[u]', '[/u]');
self::tagToBBCode($doc, 'a', ['href' => '/mailto:(.+)/'], '[mail=$1]', '[/mail]');
self::tagToBBCode($doc, 'a', ['href' => '/(.+)/'], '[url=$1]', '[/url]');
- self::tagToBBCode($doc, 'img', ['src' => '/(.+)/', 'width' => '/(\d+)/', 'height' => '/(\d+)/'], '[img=$2x$3]$1',
- '[/img]');
+ self::tagToBBCode($doc, 'img', ['src' => '/(.+)/', 'width' => '/(\d+)/', 'height' => '/(\d+)/'], '[img=$2x$3]$1', '[/img]');
self::tagToBBCode($doc, 'img', ['src' => '/(.+)/'], '[img]$1', '[/img]');
$message = preg_replace('=\r *\r=i', "\n", $message);
$message = str_replace("\r", "\n", $message);
- Addon::callHooks('html2bbcode', $message);
+ Hook::callAll('html2bbcode', $message);
$message = strip_tags($message);
"[/",
"[list]",
"[list=1]",
- "[*]"], $message
+ "[*]"],
+ $message
);
} while ($message != $oldmessage);
$message = str_replace(
- ['[b][b]', '[/b][/b]', '[i][i]', '[/i][/i]'], ['[b]', '[/b]', '[i]', '[/i]'], $message
+ ['[b][b]', '[/b][/b]', '[i][i]', '[/i][/i]'],
+ ['[b]', '[/b]', '[i]', '[/i]'],
+ $message
);
// Handling Yahoo style of mails
foreach ($matches as $match) {
$body = preg_replace_callback(
- $match, function ($match) use ($basepath) {
+ $match,
+ function ($match) use ($basepath) {
return self::qualifyURLsSub($match, $basepath);
},
$body
return trim($message);
}
+
+ /**
+ * Converts provided HTML code to Markdown. The hardwrap parameter maximizes
+ * compatibility with Diaspora in spite of the Markdown standards.
+ *
+ * @param string $html
+ * @return string
+ */
+ public static function toMarkdown($html)
+ {
+ $converter = new HtmlConverter(['hard_break' => true]);
+ $markdown = $converter->convert($html);
+
+ return $markdown;
+ }
+
+ /**
+ * @brief Convert video HTML to BBCode tags
+ *
+ * @param string $s
+ * @return string
+ */
+ public static function toBBCodeVideo($s)
+ {
+ $s = preg_replace(
+ '#<object[^>]+>(.*?)https?://www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+)(.*?)</object>#ism',
+ '[youtube]$2[/youtube]',
+ $s
+ );
+
+ $s = preg_replace(
+ '#<iframe[^>](.*?)https?://www.youtube.com/embed/([A-Za-z0-9\-_=]+)(.*?)</iframe>#ism',
+ '[youtube]$2[/youtube]',
+ $s
+ );
+
+ $s = preg_replace(
+ '#<iframe[^>](.*?)https?://player.vimeo.com/video/([0-9]+)(.*?)</iframe>#ism',
+ '[vimeo]$2[/vimeo]',
+ $s
+ );
+
+ return $s;
+ }
+
+ /**
+ * transform link href and img src from relative to absolute
+ *
+ * @param string $text
+ * @param string $base base url
+ * @return string
+ */
+ public static function relToAbs($text, $base)
+ {
+ if (empty($base)) {
+ return $text;
+ }
+
+ $base = rtrim($base, '/');
+
+ $base2 = $base . "/";
+
+ // Replace links
+ $pattern = "/<a([^>]*) href=\"(?!http|https|\/)([^\"]*)\"/";
+ $replace = "<a\${1} href=\"" . $base2 . "\${2}\"";
+ $text = preg_replace($pattern, $replace, $text);
+
+ $pattern = "/<a([^>]*) href=\"(?!http|https)([^\"]*)\"/";
+ $replace = "<a\${1} href=\"" . $base . "\${2}\"";
+ $text = preg_replace($pattern, $replace, $text);
+
+ // Replace images
+ $pattern = "/<img([^>]*) src=\"(?!http|https|\/)([^\"]*)\"/";
+ $replace = "<img\${1} src=\"" . $base2 . "\${2}\"";
+ $text = preg_replace($pattern, $replace, $text);
+
+ $pattern = "/<img([^>]*) src=\"(?!http|https)([^\"]*)\"/";
+ $replace = "<img\${1} src=\"" . $base . "\${2}\"";
+ $text = preg_replace($pattern, $replace, $text);
+
+
+ // Done
+ return $text;
+ }
+
+ /**
+ * return div element with class 'clear'
+ * @return string
+ * @deprecated
+ */
+ public static function clearDiv()
+ {
+ return '<div class="clear"></div>';
+ }
+
+ /**
+ * Loader for infinite scrolling
+ *
+ * @return string html for loader
+ * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+ */
+ public static function scrollLoader()
+ {
+ $tpl = Renderer::getMarkupTemplate("scroll_loader.tpl");
+ return Renderer::replaceMacros($tpl, [
+ 'wait' => L10n::t('Loading more entries...'),
+ 'end' => L10n::t('The end')
+ ]);
+ }
+
+ /**
+ * Get html for contact block.
+ *
+ * @deprecated since version 2019.03
+ * @see ContactBlock::getHTML()
+ * @return string
+ * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+ * @throws \ImagickException
+ */
+ public static function contactBlock()
+ {
+ $a = \get_app();
+
+ return ContactBlock::getHTML($a->profile);
+ }
+
+ /**
+ * @brief Format contacts as picture links or as text links
+ *
+ * @param array $contact Array with contacts which contains an array with
+ * int 'id' => The ID of the contact
+ * int 'uid' => The user ID of the user who owns this data
+ * string 'name' => The name of the contact
+ * string 'url' => The url to the profile page of the contact
+ * string 'addr' => The webbie of the contact (e.g.) username@friendica.com
+ * string 'network' => The network to which the contact belongs to
+ * string 'thumb' => The contact picture
+ * string 'click' => js code which is performed when clicking on the contact
+ * @param boolean $redirect If true try to use the redir url if it's possible
+ * @param string $class CSS class for the
+ * @param boolean $textmode If true display the contacts as text links
+ * if false display the contacts as picture links
+ * @return string Formatted html
+ * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+ * @throws \ImagickException
+ */
+ public static function micropro($contact, $redirect = false, $class = '', $textmode = false)
+ {
+ // Use the contact URL if no address is available
+ if (empty($contact['addr'])) {
+ $contact["addr"] = $contact["url"];
+ }
+
+ $url = $contact['url'];
+ $sparkle = '';
+ $redir = false;
+
+ if ($redirect) {
+ $url = Contact::magicLink($contact['url']);
+ if (strpos($url, 'redir/') === 0) {
+ $sparkle = ' sparkle';
+ }
+ }
+
+ // If there is some js available we don't need the url
+ if (!empty($contact['click'])) {
+ $url = '';
+ }
+
+ return Renderer::replaceMacros(Renderer::getMarkupTemplate(($textmode)?'micropro_txt.tpl':'micropro_img.tpl'), [
+ '$click' => defaults($contact, 'click', ''),
+ '$class' => $class,
+ '$url' => $url,
+ '$photo' => ProxyUtils::proxifyUrl($contact['thumb'], false, ProxyUtils::SIZE_THUMB),
+ '$name' => $contact['name'],
+ 'title' => $contact['name'] . ' [' . $contact['addr'] . ']',
+ '$parkle' => $sparkle,
+ '$redir' => $redir
+ ]);
+ }
+
+ /**
+ * Search box.
+ *
+ * @param string $s Search query.
+ * @param string $id HTML id
+ * @param string $url Search url.
+ * @param bool $aside Display the search widgit aside.
+ *
+ * @return string Formatted HTML.
+ * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+ */
+ public static function search($s, $id = 'search-box', $url = 'search', $aside = true)
+ {
+ $mode = 'text';
+
+ if (strpos($s, '#') === 0) {
+ $mode = 'tag';
+ }
+ $save_label = $mode === 'text' ? L10n::t('Save') : L10n::t('Follow');
+
+ $values = [
+ '$s' => $s,
+ '$id' => $id,
+ '$action_url' => $url,
+ '$search_label' => L10n::t('Search'),
+ '$save_label' => $save_label,
+ '$savedsearch' => 'savedsearch',
+ '$search_hint' => L10n::t('@name, !forum, #tags, content'),
+ '$mode' => $mode
+ ];
+
+ if (!$aside) {
+ $values['$searchoption'] = [
+ L10n::t("Full Text"),
+ L10n::t("Tags"),
+ L10n::t("Contacts")];
+
+ if (Config::get('system', 'poco_local_search')) {
+ $values['$searchoption'][] = L10n::t("Forums");
+ }
+ }
+
+ return Renderer::replaceMacros(Renderer::getMarkupTemplate('searchbox.tpl'), $values);
+ }
+
+ /**
+ * Replace naked text hyperlink with HTML formatted hyperlink
+ *
+ * @param string $s
+ * @return string
+ */
+ public static function toLink($s)
+ {
+ $s = preg_replace("/(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\'\%\$\!\+]*)/", ' <a href="$1" target="_blank">$1</a>', $s);
+ $s = preg_replace("/\<(.*?)(src|href)=(.*?)\&\;(.*?)\>/ism", '<$1$2=$3&$4>', $s);
+ return $s;
+ }
+
+ /**
+ * Given a HTML text and a set of filtering reasons, adds a content hiding header with the provided reasons
+ *
+ * Reasons are expected to have been translated already.
+ *
+ * @param string $html
+ * @param array $reasons
+ * @return string
+ * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+ */
+ public static function applyContentFilter($html, array $reasons)
+ {
+ if (count($reasons)) {
+ $tpl = Renderer::getMarkupTemplate('wall/content_filter.tpl');
+ $html = Renderer::replaceMacros($tpl, [
+ '$reasons' => $reasons,
+ '$rnd' => Strings::getRandomHex(8),
+ '$openclose' => L10n::t('Click to open/close'),
+ '$html' => $html
+ ]);
+ }
+
+ return $html;
+ }
+
+ /**
+ * replace html amp entity with amp char
+ * @param string $s
+ * @return string
+ */
+ public static function unamp($s)
+ {
+ return str_replace('&', '&', $s);
+ }
}