Embed instead of OEmbed

pull/15138/head
Michael 2025-09-07 04:13:30 +00:00
rodzic d5121a70ad
commit dbd56c3e94
14 zmienionych plików z 572 dodań i 264 usunięć

Wyświetl plik

@ -8,6 +8,8 @@
namespace Friendica\Content;
use Friendica\App\BaseURL;
use Friendica\Content\Post\Factory\PostMedia as PostMediaFactory;
use Friendica\Content\Post\Repository\PostMedia as PostMediaRepository;
use Friendica\Content\Text\BBCode;
use Friendica\Content\Text\BBCode\Video;
use Friendica\Content\Text\HTML;
@ -74,21 +76,27 @@ class Item
private EventDispatcherInterface $eventDispatcher;
/** @var LoggerInterface */
protected $logger;
/** @var PostMediaRepository */
protected $postMediaRepository;
/** @var PostMediaFactory */
protected $postMediaFactory;
public function __construct(LoggerInterface $logger, Profiler $profiler, Activity $activity, L10n $l10n, IHandleUserSessions $userSession, Video $bbCodeVideo, ACLFormatter $aclFormatter, IManagePersonalConfigValues $pConfig, IManageConfigValues $config, BaseURL $baseURL, Emailer $emailer, EventDispatcherInterface $eventDispatcher)
public function __construct(LoggerInterface $logger, Profiler $profiler, Activity $activity, L10n $l10n, IHandleUserSessions $userSession, Video $bbCodeVideo, ACLFormatter $aclFormatter, IManagePersonalConfigValues $pConfig, IManageConfigValues $config, BaseURL $baseURL, Emailer $emailer, EventDispatcherInterface $eventDispatcher, PostMediaRepository $postMediaRepository, PostMediaFactory $postMediaFactory)
{
$this->profiler = $profiler;
$this->activity = $activity;
$this->l10n = $l10n;
$this->userSession = $userSession;
$this->bbCodeVideo = $bbCodeVideo;
$this->aclFormatter = $aclFormatter;
$this->baseURL = $baseURL;
$this->pConfig = $pConfig;
$this->config = $config;
$this->emailer = $emailer;
$this->eventDispatcher = $eventDispatcher;
$this->logger = $logger;
$this->profiler = $profiler;
$this->activity = $activity;
$this->l10n = $l10n;
$this->userSession = $userSession;
$this->bbCodeVideo = $bbCodeVideo;
$this->aclFormatter = $aclFormatter;
$this->baseURL = $baseURL;
$this->pConfig = $pConfig;
$this->config = $config;
$this->emailer = $emailer;
$this->eventDispatcher = $eventDispatcher;
$this->logger = $logger;
$this->postMediaRepository = $postMediaRepository;
$this->postMediaFactory = $postMediaFactory;
}
/**
@ -1352,38 +1360,4 @@ class Item
$used_languages = $this->l10n->t("Detected languages in this post:\n%s", $used_languages);
return $used_languages;
}
/**
* Returns the HTML code for an iframe player for embedded content like videos.
* The iframe will automatically adjust its height based on the aspect ratio.
*
* @param string $player_url URL of the embedded player
* @param int|null $width Player width
* @param int|null $height Player height
* @return string
*/
public function getPlayerIframe(string $player_url, ?int $width, ?int $height): string
{
$attributes = ' src="' . $player_url . '"';
$max_height = $this->config->get('system', 'max_video_height') ?: $height;
if ($width != 0 && $height != 0) {
if ($height > $width && $height > $max_height) {
$factor = 100;
$height_attr = $max_height;
} else {
$factor = round($height / $width, 2) * 100;
$height_attr = '100%';
}
$attributes .= ' height="' . $height_attr. '" style="position:absolute;left:0px;top:0px"';
$return = '<div style="position:relative;padding-bottom:' . $factor . '%;margin-bottom:1em">';
} else {
$height = min($max_height, $height);
$attributes .= ' height="' . $height . '"';
$return = '<div style="position:relative">';
}
$return .= '<iframe ' . $attributes . ' width="100%" frameborder="0" allow="fullscreen, picture-in-picture" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe></div>';
return $return;
}
}

Wyświetl plik

@ -38,6 +38,10 @@ use Psr\Http\Message\UriInterface;
* @property-read ?UriInterface $playerUrl
* @property-read ?int $playerWidth
* @property-read ?int $playerHeight
* @property-read ?int $attachId
* @property-read ?string $language
* @property-read ?string $published
* @property-read ?string $modified
*/
class PostMedia extends BaseEntity
{
@ -107,6 +111,14 @@ class PostMedia extends BaseEntity
protected $playerWidth;
/** @var ?int In pixels */
protected $playerHeight;
/** @var ?int */
protected $attachId;
/** @var ?string */
protected $language;
/** @var ?string (Datetime) */
protected $published;
/** @var ?string (Datetime) */
protected $modified;
public function __construct(
int $uriId,
@ -132,7 +144,11 @@ class PostMedia extends BaseEntity
?UriInterface $playerUrl = null,
?int $playerWidth = null,
?int $playerHeight = null,
?int $id = null
?int $id = null,
?int $attachId = null,
?string $language = null,
?string $published = null,
?string $modified = null
) {
$this->uriId = $uriId;
$this->url = $url;
@ -158,6 +174,10 @@ class PostMedia extends BaseEntity
$this->playerWidth = $playerWidth;
$this->playerHeight = $playerHeight;
$this->id = $id;
$this->attachId = $attachId;
$this->language = $language;
$this->published = $published;
$this->modified = $modified;
}
@ -266,6 +286,10 @@ class PostMedia extends BaseEntity
$this->playerWidth,
$this->playerHeight,
$this->id,
$this->attachId,
$this->language,
$this->published,
$this->modified,
);
}
@ -296,6 +320,10 @@ class PostMedia extends BaseEntity
$this->playerWidth,
$this->playerHeight,
$this->id,
$this->attachId,
$this->language,
$this->published,
$this->modified,
);
}

Wyświetl plik

@ -10,8 +10,10 @@ namespace Friendica\Content\Post\Factory;
use Friendica\BaseFactory;
use Friendica\Capabilities\ICanCreateFromTableRow;
use Friendica\Content\Post\Entity\PostMedia as PostMediaEntity;
use Friendica\Model\Post;
use Friendica\Network\Entity\MimeType as MimeTypeEntity;
use Friendica\Network\Factory\MimeType as MimeTypeFactory;
use Friendica\Util\Images;
use Friendica\Util\Network as UtilNetwork;
use GuzzleHttp\Psr7\Uri;
use Psr\Log\LoggerInterface;
@ -58,7 +60,11 @@ class PostMedia extends BaseFactory implements ICanCreateFromTableRow
UtilNetwork::createUriFromString($row['player-url']),
$row['player-width'],
$row['player-height'],
$row['id']
$row['id'],
$row['attach-id'],
$row['language'],
$row['published'],
$row['modified'],
);
}
@ -100,9 +106,84 @@ class PostMedia extends BaseFactory implements ICanCreateFromTableRow
);
}
public function createFromAttachment(int $uriId, array $attachment)
public function createFromAttachment(array $attachment, int $uriId = 0, int $id = 0)
{
$attachment['uri-id'] = $uriId;
return $this->createFromTableRow($attachment);
$row = [
'id' => $id,
'uri-id' => $uriId,
'url' => $attachment['url'],
'type' => Post\Media::HTML,
'mimetype' => null,
'media-uri-id' => null,
'width' => null,
'height' => null,
'size' => null,
'preview' => $attachment['image'] ?? $attachment['preview'] ?? null,
'preview-width' => null,
'preview-height' => null,
'description' => $attachment['description'] ?? null,
'name' => $attachment['title'] ?? null,
'author-url' => $attachment['author_url'] ?? null,
'author-name' => $attachment['author_name'] ?? null,
'author-image' => null,
'publisher-url' => $attachment['provider_url'] ?? null,
'publisher-name' => $attachment['provider_name'] ?? null,
'publisher-image' => null,
'blurhash' => null,
'player-url' => $attachment['player_url'] ?? null,
'player-width' => $attachment['player_width'] ?? null,
'player-height' => $attachment['player_height'] ?? null,
'attach-id' => null,
'language' => null,
'published' => null,
'modified' => null,
];
if (isset($row['preview'])) {
$imagedata = Images::getInfoFromURLCached($row['preview']);
if ($imagedata) {
$row['preview-width'] = $imagedata[0];
$row['preview-height'] = $imagedata[1];
$row['blurhash'] = $imagedata['blurhash'] ?? null;
}
}
return $this->createFromTableRow($row);
}
public function createFromParseUrl(array $data, int $uriId = 0, int $id = 0)
{
$row = [
'id' => $id,
'uri-id' => $uriId,
'url' => $data['url'],
'type' => Post\Media::getType($data['mimetype'] ?? ''),
'mimetype' => $data['mimetype'] ?? null,
'media-uri-id' => null,
'width' => null,
'height' => null,
'size' => $data['size'] ?? null,
'preview' => $data['images'][0]['src'] ?? null,
'preview-width' => $data['images'][0]['width'] ?? null,
'preview-height' => $data['images'][0]['height'] ?? null,
'description' => $data['text'] ?? null,
'name' => $data['title'] ?? null,
'author-url' => $data['author_url'] ?? null,
'author-name' => $data['author_name'] ?? null,
'author-image' => $data['author_img'] ?? null,
'publisher-url' => $data['publisher_url'] ?? null,
'publisher-name' => $data['publisher_name'] ?? null,
'publisher-image' => $data['publisher_img'] ?? null,
'blurhash' => $data['images'][0]['blurhash'] ?? null,
'player-url' => $data['player']['embed'] ?? null,
'player-width' => $data['player']['width'] ?? null,
'player-height' => $data['player']['height'] ?? null,
'attach-id' => null,
'language' => $data['language'] ?? null,
'published' => $data['published'] ?? null,
'modified' => $data['modified'] ?? null,
];
return $this->createFromTableRow($row);
}
}

Wyświetl plik

@ -7,12 +7,23 @@
namespace Friendica\Content\Post\Repository;
use DOMDocument;
use DOMXPath;
use Friendica\App\BaseURL;
use Friendica\BaseRepository;
use Friendica\Content\Item;
use Friendica\Content\Post\Collection\PostMedias as PostMediasCollection;
use Friendica\Content\Post\Entity\PostMedia as PostMediaEntity;
use Friendica\Content\Post\Factory\PostMedia as PostMediaFactory;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
use Friendica\Core\Renderer;
use Friendica\Database\Database;
use Friendica\Database\DBA;
use Friendica\Model\Post;
use Friendica\Network\HTTPException\NotFoundException;
use Friendica\Util\ParseUrl;
use Friendica\Util\Proxy;
use Friendica\Util\Strings;
use Psr\Log\LoggerInterface;
@ -22,10 +33,23 @@ class PostMedia extends BaseRepository
/** @var PostMediaFactory */
protected $factory;
/** @var IManagePersonalConfigValues */
private $pConfig;
/** @var IManageConfigValues */
private $config;
/** @var BaseURL */
private $baseURL;
/** @var Item */
private $item;
public function __construct(Database $database, LoggerInterface $logger, PostMediaFactory $factory)
public function __construct(Database $database, LoggerInterface $logger, PostMediaFactory $factory, IManagePersonalConfigValues $pConfig, IManageConfigValues $config, BaseURL $baseURL, Item $item)
{
parent::__construct($database, $logger, $factory);
$this->baseURL = $baseURL;
$this->pConfig = $pConfig;
$this->config = $config;
$this->item = $item;
}
protected function _select(array $condition, array $params = []): PostMediasCollection
@ -44,19 +68,59 @@ class PostMedia extends BaseRepository
return $Entities;
}
public function selectOneById(int $postMediaId): PostMediaEntity
/**
* Returns a single PostMedia entity, selected by their id.
*
* @param int $postMediaId
* @return PostMediaEntity
* @throws NotFoundException
*/
public function selectById(int $postMediaId): PostMediaEntity
{
$fields = $this->_selectFirstRowAsArray(['id' => $postMediaId]);
return $this->factory->createFromTableRow($fields);
}
public function selectByUriId(int $uriId): PostMediasCollection
/**
* Select PostMedia collection for the given uri-id and the given types
*
* @param integer $uriId
* @param array $types
* @return PostMediasCollection
*/
public function selectByUriId(int $uriId, array $types = []): PostMediasCollection
{
return $this->_select(["`uri-id` = ? AND `type` != ?", $uriId, Post\Media::UNKNOWN]);
$condition = ["`uri-id` = ? AND `type` != ?", $uriId, Post\Media::UNKNOWN];
if (!empty($types)) {
$condition = DBA::mergeConditions($condition, ['type' => $types]);
}
return $this->_select($condition);
}
public function save(PostMediaEntity $PostMedia): PostMediaEntity
/**
* Select PostMedia entity for the given uri-id, the media url and the given types
*
* @param integer $uriId
* @param string $url
* @param array $types
* @return PostMediaEntity
*/
public function selectByURL(int $uriId, string $url, array $types = []): ?PostMediaEntity
{
$condition = ["`uri-id` = ? AND `url` = ? AND `type` != ?", $uriId, $url, Post\Media::UNKNOWN];
if (!empty($types)) {
$condition = DBA::mergeConditions($condition, ['type' => $types]);
}
$medias = $this->_select($condition);
return $medias[0] ?? null;
}
public function getFields(PostMediaEntity $PostMedia, bool $includeId = false): array
{
$fields = [
'uri-id' => $PostMedia->uriId,
@ -82,8 +146,22 @@ class PostMedia extends BaseRepository
'player-url' => $PostMedia->playerUrl,
'player-height' => $PostMedia->playerHeight,
'player-width' => $PostMedia->playerWidth,
'attach-id' => $PostMedia->attachId,
'language' => $PostMedia->language,
'published' => $PostMedia->published,
'modified' => $PostMedia->modified,
];
if ($includeId) {
$fields['id'] = $PostMedia->id;
}
return $fields;
}
public function save(PostMediaEntity $PostMedia): PostMediaEntity
{
$fields = $this->getFields($PostMedia);
if ($PostMedia->id) {
$this->db->update(self::$table_name, $fields, ['id' => $PostMedia->id]);
} else {
@ -91,7 +169,7 @@ class PostMedia extends BaseRepository
$newPostMediaId = $this->db->lastInsertId();
$PostMedia = $this->selectOneById($newPostMediaId);
$PostMedia = $this->selectById($newPostMediaId);
}
return $PostMedia;
@ -123,11 +201,12 @@ class PostMedia extends BaseRepository
return $attachments;
}
$heights = [];
$selected = '';
$previews = [];
$video = [];
$is_hls = false;
$heights = [];
$selected = '';
$previews = [];
$video = [];
$is_hls = false;
$is_torrent = false;
// Check if there is any HLS media
// This is used to determine if we should suppress some of the media types
@ -135,6 +214,9 @@ class PostMedia extends BaseRepository
if ($PostMedia->type == PostMediaEntity::TYPE_HLS) {
$is_hls = true;
}
if ($PostMedia->type == PostMediaEntity::TYPE_TORRENT) {
$is_torrent = true;
}
}
foreach ($PostMedias as $PostMedia) {
@ -170,21 +252,17 @@ class PostMedia extends BaseRepository
//$PostMedia->filetype = $filetype;
//$PostMedia->subtype = $subtype;
if ($PostMedia->type == PostMediaEntity::TYPE_HTML || ($PostMedia->mimetype->type == 'text' && $PostMedia->mimetype->subtype == 'html')) {
if ($PostMedia->type == PostMediaEntity::TYPE_HTML) {
$attachments['link'][] = $PostMedia;
continue;
}
if (
in_array($PostMedia->type, [PostMediaEntity::TYPE_AUDIO, PostMediaEntity::TYPE_IMAGE, PostMediaEntity::TYPE_HLS]) ||
in_array($PostMedia->mimetype->type, ['audio', 'image'])
) {
if (in_array($PostMedia->type, [PostMediaEntity::TYPE_AUDIO, PostMediaEntity::TYPE_IMAGE, PostMediaEntity::TYPE_HLS])) {
$attachments['visual'][] = $PostMedia;
} elseif (($PostMedia->type == PostMediaEntity::TYPE_VIDEO) || ($PostMedia->mimetype->type == 'video')) {
if (!empty($PostMedia->height)) {
// Peertube videos are delivered in many different resolutions. We pick a moderate one.
// Since only Peertube provides a "height" parameter, this wouldn't be executed
// when someone for example on Mastodon was sharing multiple videos in a single post.
} elseif ($PostMedia->type == PostMediaEntity::TYPE_VIDEO) {
if ($is_torrent) {
// We stored older Peertube videos not as HLS, but with many different resolutions.
// We pick a moderate one. We detect Peertube via their also existing Torrent link.
$heights[$PostMedia->height] = (string)$PostMedia->url;
$video[(string) $PostMedia->url] = $PostMedia;
} else {
@ -215,4 +293,180 @@ class PostMedia extends BaseRepository
return $attachments;
}
public function createFromUrl(string $url): PostMediaEntity
{
$data = ParseUrl::getSiteinfoCached($url);
$media = $this->factory->createFromParseUrl($data);
return $this->fetchAdditionalData($media);
}
public function fetchAdditionalData(PostMediaEntity $postMedia): PostMediaEntity
{
$data = $this->getFields($postMedia, true);
$data = Post\Media::fetchAdditionalData($data);
return $this->factory->createFromTableRow($data);
}
/**
* Embed remote media, that had been added with the [embed] element
*
* @param string $html The already rendered HTML output
* @param integer $uid The user the output is rendered for
* @param integer $uri_id The uri-id of the item that is rendered
* @return string
*/
public function addEmbed(string $html, int $uid, int $uri_id): string
{
if ($html == '') {
return $html;
}
$allow_embed = $this->pConfig->get($uid, 'system', 'embed_remote_media', false);
$changed = false;
$tmp = new DOMDocument();
$doc = new DOMDocument();
@$doc->loadHTML(mb_convert_encoding('<span>' . $html . '</span>', 'HTML-ENTITIES', "UTF-8"), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
$xpath = new DOMXPath($doc);
$list = $xpath->query('//a[@class="embed"]');
foreach ($list as $node) {
$href = '';
if ($node->attributes->length) {
foreach ($node->attributes as $attribute) {
if ($attribute->name == 'href') {
$href = $attribute->value;
break;
}
}
}
if (empty($href)) {
continue;
}
if ($uri_id > 0) {
$media = $this->selectByURL($uri_id, $href, [Post\Media::HTML, Post\Media::AUDIO, Post\Media::VIDEO, Post\Media::HLS]);
}
if (!isset($media)) {
$media = $this->createFromUrl($href);
if ($uri_id > 0) {
$fields = $this->getFields($media);
$fields['uri-id'] = $uri_id;
$result = Post\Media::insert($fields);
$this->logger->debug('Media is now assigned to this post', ['uri-id' => $uri_id, 'uid' => $uid, 'result' => $result, 'fields' => $fields]);
}
}
if ($media->type === Post\Media::AUDIO) {
$player = $this->getAudioAttachment($media);
} elseif (in_array($media->type, [Post\Media::VIDEO, Post\Media::HLS])) {
$player = $this->getVideoAttachment($media, $uid);
} elseif ($allow_embed && !empty($media->playerUrl)) {
$player = $this->getPlayerIframe($media);
} else {
$player = $this->getLinkAttachment($media);
}
@$tmp->loadHTML(mb_convert_encoding($player, 'HTML-ENTITIES', "UTF-8"), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
$div = $tmp->documentElement;
$imported = $doc->importNode($div, true);
$node->parentNode->replaceChild($imported, $node);
$changed = true;
}
if (!$changed) {
return $html;
}
$html = trim($doc->saveHTML());
if (substr($html, 0, 6) == '<span>' && substr($html, -7) == '</span>') {
$html = substr($html, 6, -7);
}
return $html;
}
public function getVideoAttachment(PostMediaEntity $postMedia, int $uid): string
{
if ($postMedia->preview || $postMedia->blurhash) {
$preview_url = $this->baseURL . $postMedia->getPreviewPath(Proxy::SIZE_MEDIUM);
} else {
$preview_url = '';
}
if (($postMedia->height ?? 0) > ($postMedia->width ?? 0)) {
$height = min($this->config->get('system', 'max_video_height') ?: '100%', $postMedia->height);
$width = 'auto';
} else {
$height = 'auto';
$width = '100%';
}
if ($this->pConfig->get($uid, 'system', 'embed_media', false) && ($postMedia->playerUrl != '') && ($postMedia->playerHeight > 0)) {
$media = $this->getPlayerIframe($postMedia);
} else {
/// @todo Move the template to /content as well
$media = Renderer::replaceMacros(Renderer::getMarkupTemplate($postMedia->type == Post\Media::HLS ? 'hls_top.tpl' : 'video_top.tpl'), [
'$video' => [
'id' => $postMedia->id,
'src' => (string)$postMedia->url,
'name' => $postMedia->name ?: $postMedia->url,
'preview' => $preview_url,
'mime' => (string)$postMedia->mimetype,
'height' => $height,
'width' => $width,
'description' => $postMedia->description,
],
]);
}
return $media;
}
public function getPlayerIframe(PostMediaEntity $postMedia): string
{
if ($postMedia->playerUrl == '') {
return '';
}
$attributes = ' src="' . $postMedia->playerUrl . '"';
$max_height = $this->config->get('system', 'max_video_height') ?: $postMedia->playerHeight;
if ($postMedia->playerWidth != 0 && $postMedia->playerHeight != 0) {
if ($postMedia->playerHeight > $postMedia->playerWidth && $postMedia->playerHeight > $max_height) {
$factor = 100;
$height_attr = $max_height;
} else {
$factor = round($postMedia->playerHeight / $postMedia->playerWidth, 2) * 100;
$height_attr = '100%';
}
$attributes .= ' height="' . $height_attr. '" style="position:absolute;left:0px;top:0px"';
$return = '<div style="position:relative;padding-bottom:' . $factor . '%;margin-bottom:1em">';
} else {
$attributes .= ' height="' . min($max_height, $postMedia->playerHeight) . '"';
$return = '<div style="position:relative">';
}
$return .= '<iframe ' . $attributes . ' width="100%" frameborder="0" allow="fullscreen, picture-in-picture" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe></div>';
return $return;
}
public function getAudioAttachment(PostMediaEntity $postMedia): string
{
return Renderer::replaceMacros(Renderer::getMarkupTemplate('content/audio.tpl'), [
'$audio' => [
'id' => $postMedia->id,
'src' => (string)$postMedia->url,
'name' => $postMedia->name ?: $postMedia->url,
'mime' => (string)$postMedia->mimetype,
],
]);
}
public function getLinkAttachment(PostMediaEntity $postMedia): string
{
return Renderer::replaceMacros(Renderer::getMarkupTemplate('content/link.tpl'), [
'$url' => $postMedia->url,
'$title' => $postMedia->name,
]);
}
}

Wyświetl plik

@ -9,10 +9,8 @@ namespace Friendica\Content\Text;
use DOMDocument;
use DOMXPath;
use Exception;
use Friendica\Content\ContactSelector;
use Friendica\Content\Item;
use Friendica\Content\OEmbed;
use Friendica\Content\PageInfo;
use Friendica\Content\Smilies;
use Friendica\Core\Protocol;
@ -408,13 +406,13 @@ class BBCode
*
* @param string $text
* @param integer $simplehtml
* @param bool $tryoembed
* @param bool $embed
* @param array $data
* @param int $uriid
* @return string
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function convertAttachment(string $text, int $simplehtml = self::INTERNAL, bool $tryoembed = true, array $data = [], int $uriid = 0, int $preview_mode = self::PREVIEW_LARGE, bool $embed = false): string
public static function convertAttachment(string $text, int $simplehtml = self::INTERNAL, array $data = [], int $uriid = 0, int $preview_mode = self::PREVIEW_LARGE, bool $embed = false): string
{
DI::profiler()->startRecording('rendering');
$data = $data ?: self::getAttachmentData($text);
@ -438,60 +436,54 @@ class BBCode
}
$return = '';
try {
if ($tryoembed && OEmbed::isAllowedURL($data['url'])) {
$return = OEmbed::getHTML($data['url'], $data['title'], $uriid);
$data['title'] = ($data['title'] ?? '') ?: $data['url'];
if ($simplehtml != self::CONNECTORS) {
$return = sprintf('<div class="type-%s">', $data['type']);
}
if ($embed && $data['player_url'] != '' && $data['player_height'] != 0) {
$media = DI::postMediaFactory()->createFromAttachment($data, $uriid);
$return .= DI::postMediaRepository()->getPlayerIframe($media);
$preview_mode = self::PREVIEW_NO_IMAGE;
}
if ($preview_mode == self::PREVIEW_NO_IMAGE) {
unset($data['image']);
unset($data['preview']);
}
if (!empty($data['title']) && !empty($data['url'])) {
$preview_class = $preview_mode == self::PREVIEW_LARGE ? 'attachment-image' : 'attachment-preview';
if (!empty($data['image']) && empty($data['text']) && ($data['type'] == 'photo')) {
$return .= sprintf('<a href="%s" target="_blank" rel="noopener noreferrer"><img src="%s" alt="" title="%s" class="' . $preview_class . '" /></a>', $data['url'], self::proxyUrl($data['image'], $simplehtml, $uriid), $data['title']);
} else {
throw new Exception('OEmbed is disabled for this attachment.');
}
} catch (Exception $e) {
$data['title'] = ($data['title'] ?? '') ?: $data['url'];
if ($simplehtml != self::CONNECTORS) {
$return = sprintf('<div class="type-%s">', $data['type']);
}
if ($embed && $data['player_url'] != '' && $data['player_height'] != 0) {
$return .= DI::contentItem()->getPlayerIframe($data['player_url'], $data['player_width'], $data['player_height']);
$preview_mode = self::PREVIEW_NO_IMAGE;
}
if ($preview_mode == self::PREVIEW_NO_IMAGE) {
unset($data['image']);
unset($data['preview']);
}
if (!empty($data['title']) && !empty($data['url'])) {
$preview_class = $preview_mode == self::PREVIEW_LARGE ? 'attachment-image' : 'attachment-preview';
if (!empty($data['image']) && empty($data['text']) && ($data['type'] == 'photo')) {
$return .= sprintf('<a href="%s" target="_blank" rel="noopener noreferrer"><img src="%s" alt="" title="%s" class="' . $preview_class . '" /></a>', $data['url'], self::proxyUrl($data['image'], $simplehtml, $uriid), $data['title']);
} else {
if (!empty($data['image'])) {
$return .= sprintf('<a href="%s" target="_blank" rel="noopener noreferrer"><img src="%s" alt="" title="%s" class="' . $preview_class . '" /></a><br>', $data['url'], self::proxyUrl($data['image'], $simplehtml, $uriid), $data['title']);
} elseif (!empty($data['preview'])) {
$return .= sprintf('<a href="%s" target="_blank" rel="noopener noreferrer"><img src="%s" alt="" title="%s" class="attachment-preview" /></a><br>', $data['url'], self::proxyUrl($data['preview'], $simplehtml, $uriid), $data['title']);
}
$return .= sprintf('<h4><a href="%s" target="_blank" rel="noopener noreferrer">%s</a></h4>', $data['url'], $data['title']);
if (!empty($data['image'])) {
$return .= sprintf('<a href="%s" target="_blank" rel="noopener noreferrer"><img src="%s" alt="" title="%s" class="' . $preview_class . '" /></a><br>', $data['url'], self::proxyUrl($data['image'], $simplehtml, $uriid), $data['title']);
} elseif (!empty($data['preview'])) {
$return .= sprintf('<a href="%s" target="_blank" rel="noopener noreferrer"><img src="%s" alt="" title="%s" class="attachment-preview" /></a><br>', $data['url'], self::proxyUrl($data['preview'], $simplehtml, $uriid), $data['title']);
}
$return .= sprintf('<h4><a href="%s" target="_blank" rel="noopener noreferrer">%s</a></h4>', $data['url'], $data['title']);
}
}
if (!empty($data['description']) && $data['description'] != $data['title']) {
// Sanitize the HTML
$return .= sprintf('<blockquote>%s</blockquote>', trim(HTML::purify($data['description'])));
}
if (!empty($data['description']) && $data['description'] != $data['title']) {
// Sanitize the HTML
$return .= sprintf('<blockquote>%s</blockquote>', trim(HTML::purify($data['description'])));
}
if (!empty($data['provider_url']) && !empty($data['provider_name'])) {
$data['provider_url'] = Network::sanitizeUrl($data['provider_url']);
if (!empty($data['author_name'])) {
$return .= sprintf('<sup><a href="%s" target="_blank" rel="noopener noreferrer">%s (%s)</a></sup>', $data['provider_url'], $data['author_name'], $data['provider_name']);
} else {
$return .= sprintf('<sup><a href="%s" target="_blank" rel="noopener noreferrer">%s</a></sup>', $data['provider_url'], $data['provider_name']);
}
if (!empty($data['provider_url']) && !empty($data['provider_name'])) {
$data['provider_url'] = Network::sanitizeUrl($data['provider_url']);
if (!empty($data['author_name'])) {
$return .= sprintf('<sup><a href="%s" target="_blank" rel="noopener noreferrer">%s (%s)</a></sup>', $data['provider_url'], $data['author_name'], $data['provider_name']);
} else {
$return .= sprintf('<sup><a href="%s" target="_blank" rel="noopener noreferrer">%s</a></sup>', $data['provider_url'], $data['provider_name']);
}
}
if ($simplehtml != self::CONNECTORS) {
$return .= '</div>';
}
if ($simplehtml != self::CONNECTORS) {
$return .= '</div>';
}
DI::profiler()->stopRecording();
@ -1267,9 +1259,7 @@ class BBCode
*/
public static function convertForUriId(int $uriid = null, string $text = null, int $simple_html = self::INTERNAL): string
{
$try_oembed = ($simple_html == self::INTERNAL);
return self::convert($text ?? '', $try_oembed, $simple_html, false, $uriid ?? 0);
return self::convert($text ?? '', false, $simple_html, false, $uriid ?? 0);
}
/**
@ -1291,14 +1281,14 @@ class BBCode
* - 9: ActivityPub
*
* @param string $text
* @param bool $try_oembed
* @param bool $embed
* @param int $simple_html
* @param bool $for_plaintext
* @param int $uriid
* @return string Converted code or empty string
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function convert(string $text = null, bool $try_oembed = true, int $simple_html = self::INTERNAL, bool $for_plaintext = false, int $uriid = 0): string
public static function convert(string $text = null, bool $embed = true, int $simple_html = self::INTERNAL, bool $for_plaintext = false, int $uriid = 0): string
{
// Accounting for null default column values
if (is_null($text) || $text === '') {
@ -1319,28 +1309,8 @@ class BBCode
$ev = Event::fromBBCode($text);
$text = self::performWithEscapedTags($text, ['code'], function ($text) use ($try_oembed, $simple_html, $for_plaintext, $uriid, $ev) {
$text = self::performWithEscapedTags($text, ['noparse', 'nobb', 'pre'], function ($text) use ($try_oembed, $simple_html, $for_plaintext, $uriid, $ev) {
/*
* preg_match_callback function to replace potential Oembed tags with Oembed content
*
* $match[0] = [tag]$url[/tag] or [tag=$url]$title[/tag]
* $match[1] = $url
* $match[2] = $title or absent
*/
$try_oembed_callback = function (array $match) use ($uriid) {
$url = $match[1];
$title = $match[2] ?? '';
try {
$return = OEmbed::getHTML($url, $title, $uriid);
} catch (Exception $ex) {
$return = $match[0];
}
return $return;
};
$text = self::performWithEscapedTags($text, ['code'], function ($text) use ($simple_html, $for_plaintext, $uriid, $ev) {
$text = self::performWithEscapedTags($text, ['noparse', 'nobb', 'pre'], function ($text) use ($simple_html, $for_plaintext, $uriid, $ev) {
// Extract the private images which use data urls since preg has issues with
// large data sizes. Stash them away while we do bbcode conversion, and then put them back
// in after we've done all the regex matching. We cannot use any preg functions to do this.
@ -1361,7 +1331,7 @@ class BBCode
// We add URL without a surrounding URL at this time, since at a earlier stage it would had been too early,
// since the used regular expression won't touch URL inside of BBCode elements, but with the structural ones it should.
// At a later stage we won't be able to exclude certain parts of the code.
$text = self::performWithEscapedTags($text, ['url', 'img', 'audio', 'video', 'youtube', 'vimeo', 'share', 'attachment', 'iframe', 'bookmark', 'map', 'oembed'], function ($text) use ($simple_html, $for_plaintext) {
$text = self::performWithEscapedTags($text, ['url', 'img', 'audio', 'video', 'youtube', 'vimeo', 'share', 'attachment', 'iframe', 'bookmark', 'map', 'embed'], function ($text) use ($simple_html, $for_plaintext) {
if (!$for_plaintext) {
$text = preg_replace(Strings::autoLinkRegEx(), '[url]$1[/url]', $text) ?? '';
}
@ -1369,11 +1339,10 @@ class BBCode
});
// Now for some more complex BBCode elements (mostly non standard ones)
$text = self::convertAttachmentsToHtml($text, $simple_html, $try_oembed, $uriid);
$text = self::convertAttachmentsToHtml($text, $simple_html, $uriid);
$text = self::convertMapsToHtml($text, $simple_html);
$text = self::convertQuotesToHtml($text);
$text = self::convertVideoPlatformsToHtml($text, $try_oembed);
$text = self::convertOEmbedToHtml($text, $uriid);
$text = self::convertVideoPlatformsToHtml($text);
$text = self::convertEventsToHtml($text, $simple_html, $uriid, $ev);
// Some simpler non standard elements
@ -1381,12 +1350,13 @@ class BBCode
$text = self::convertCryptToHtml($text);
$text = self::convertIFramesToHtml($text);
$text = self::convertMailToHtml($text);
$text = self::convertAudioVideoToHtml($text, $simple_html, $try_oembed, $try_oembed_callback);
$text = self::convertAudioVideoToHtml($text, $simple_html);
$text = self::convertEmbedToHtml($text, $simple_html);
// At last, some standard elements. URL has to go last,
// since some previous conversions use URL elements.
$text = self::convertImagesToHtml($text, $simple_html, $uriid);
$text = self::convertUrlToHtml($text, $simple_html, $for_plaintext, $try_oembed, $try_oembed_callback);
$text = self::convertUrlToHtml($text, $simple_html, $for_plaintext);
// If the post only consists of an emoji, we display it larger than normal.
if (!$for_plaintext && DI::config()->get('system', 'big_emojis') && ($simple_html != self::DIASPORA) && Smilies::isEmojiPost($text)) {
@ -1397,7 +1367,7 @@ class BBCode
$text = self::cleanupHtml($text);
// This needs to be called after the cleanup, since otherwise some links are invalidated
$text = self::convertSharesToHtml($text, $simple_html, $try_oembed, $uriid);
$text = self::convertSharesToHtml($text, $simple_html, $uriid);
// Insert the previously extracted embedded image again.
return self::interpolateSavedImagesIntoItemBody($uriid, $text, $saved_image);
@ -1430,21 +1400,11 @@ class BBCode
$text
);
// Default iframe allowed domains/path
$allowedIframeDomains = DI::config()->get('system', 'no_oembed_rich_content') ? [] : ['www.youtube.com/embed/', 'player.vimeo.com/video/'];
$allowedIframeDomains = array_merge(
$allowedIframeDomains,
DI::config()->get('system', 'allowed_oembed') ?
explode(',', DI::config()->get('system', 'allowed_oembed'))
: []
);
if (strpos($text, '<p>') !== false || strpos($text, '</p>') !== false) {
$text = '<p>' . $text . '</p>';
}
$text = HTML::purify($text, $allowedIframeDomains);
$text = HTML::purify($text);
DI::profiler()->stopRecording();
return trim($text);
@ -1548,7 +1508,7 @@ class BBCode
return $text;
}
private static function convertAttachmentsToHtml(string $text, int $simple_html, bool $try_oembed, int $uriid): string
private static function convertAttachmentsToHtml(string $text, int $simple_html, int $uriid): string
{
/// @todo Have a closer look at the different html modes
// Handle attached links or videos
@ -1559,7 +1519,7 @@ class BBCode
} elseif (!in_array($simple_html, [self::INTERNAL, self::EXTERNAL, self::CONNECTORS])) {
$text = self::replaceAttachment($text, true);
} else {
$text = self::convertAttachment($text, $simple_html, $try_oembed, [], $uriid);
$text = self::convertAttachment($text, $simple_html, [], $uriid);
}
return $text;
@ -1889,7 +1849,7 @@ class BBCode
return $text;
}
private static function convertAudioVideoToHtml(string $text, int $simple_html, bool $try_oembed, \Closure $try_oembed_callback): string
private static function convertAudioVideoToHtml(string $text, int $simple_html): string
{
// Simplify "video" element
$text = preg_replace('(\[video[^\]]*?\ssrc\s?=\s?([^\s\]]+)[^\]]*?\].*?\[/video\])ism', '[video]$1[/video]', $text);
@ -1908,22 +1868,9 @@ class BBCode
'</p><audio src="$1" controls>$1">$1</audio><p>',
$text
);
} elseif ($try_oembed) {
// html5 video and audio
$text = preg_replace(
"/\[video\](.*?\.(ogg|ogv|oga|ogm|webm|mp4).*?)\[\/video\]/ism",
'<video src="$1" controls width="100%" height="auto"><a href="$1">$1</a></video>',
$text
);
$text = preg_replace_callback("/\[video\](.*?)\[\/video\]/ism", $try_oembed_callback, $text);
$text = preg_replace_callback("/\[audio\](.*?)\[\/audio\]/ism", $try_oembed_callback, $text);
$text = preg_replace("/\[video\](.*?)\[\/video\]/ism", '[url]$1[/url]', $text);
$text = preg_replace("/\[audio\](.*?)\[\/audio\]/ism", '<audio src="$1" controls><a href="$1">$1</a></audio>', $text);
} else {
$text = preg_replace("/\[video\](.*?)\[\/video\]/ism", '[url]$1[/url]', $text);
$text = preg_replace("/\[audio\](.*?)\[\/audio\]/ism", '[url]$1[/url]', $text);
$text = preg_replace("/\[video\](.*?)\[\/video\]/ism", '[embed]$1[/embed]', $text);
$text = preg_replace("/\[audio\](.*?)\[\/audio\]/ism", '[embed]$1[/embed]', $text);
}
return $text;
}
@ -1937,40 +1884,39 @@ class BBCode
return $text;
}
private static function convertVideoPlatformsToHtml(string $text, bool $try_oembed): string
private static function convertVideoPlatformsToHtml(string $text): string
{
$appHelper = DI::appHelper();
$text = self::normalizeVideoLinks($text);
// Youtube extensions
if ($try_oembed && OEmbed::isAllowedURL('https://www.youtube.com/embed/')) {
$text = preg_replace("/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism", '<iframe width="' . $appHelper->getThemeInfoValue('videowidth') . '" height="' . $appHelper->getThemeInfoValue('videoheight') . '" src="https://www.youtube.com/embed/$1" frameborder="0" ></iframe>', $text);
} else {
$text = preg_replace("/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism", '[url]https://www.youtube.com/watch?v=$1[/url]', $text);
}
$text = preg_replace("/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism", '[embed]https://www.youtube.com/watch?v=$1[/embed]', $text);
// Vimeo extensions
if ($try_oembed && OEmbed::isAllowedURL('https://player.vimeo.com/video')) {
$text = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism", '<iframe width="' . $appHelper->getThemeInfoValue('videowidth') . '" height="' . $appHelper->getThemeInfoValue('videoheight') . '" src="https://player.vimeo.com/video/$1" frameborder="0" ></iframe>', $text);
} else {
$text = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism", '[url]https://vimeo.com/$1[/url]', $text);
}
$text = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism", '[embed]https://vimeo.com/$1[/embed]', $text);
return $text;
}
private static function convertOEmbedToHtml(string $text, int $uriid): string
private static function convertEmbedToHtml(string $text, int $simple_html): string
{
// oembed tag
$text = OEmbed::BBCode2HTML($text, $uriid);
if ($simple_html == self::INTERNAL) {
$max_length = DI::config()->get('system', 'display_link_length');
} else {
$max_length = 30;
}
// Avoid triple linefeeds through oembed
$text = str_replace("<br style='clear:left'></span><br><br>", "<br style='clear:left'></span><br>", $text);
$text = preg_replace_callback(
"/\[embed\](.*?)\[\/embed\]/ism",
function ($match) use ($max_length) {
return '<a class="embed" href="' . self::escapeUrl(Network::sanitizeUrl($match[1])) . '">' . Strings::getStyledURL(Network::sanitizeUrl($match[1]), $max_length) . "</a>";
},
$text
);
return $text;
}
private static function convertUrlToHtml(string $text, int $simple_html, bool $for_plaintext, bool $try_oembed, \Closure $try_oembed_callback): string
private static function convertUrlToHtml(string $text, int $simple_html, bool $for_plaintext): string
{
$text = preg_replace_callback("/\[(url)\](.*?)\[\/url\]/ism", [self::class, 'sanitizeLinksCallback'], $text);
$text = preg_replace_callback("/\[(url)\=(.*?)\](.*?)\[\/url\]/ism", [self::class, 'sanitizeLinksCallback'], $text);
@ -2048,11 +1994,6 @@ class BBCode
$text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", ' $2 [url]$1[/url]', $text);
}
// Perform URL Search
if ($try_oembed) {
$text = preg_replace_callback("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", $try_oembed_callback, $text);
}
$text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", '[url=$1]$2[/url]', $text);
// Handle Diaspora posts
@ -2191,11 +2132,11 @@ class BBCode
return $text;
}
private static function convertSharesToHtml(string $text, int $simple_html, bool $try_oembed, int $uriid): string
private static function convertSharesToHtml(string $text, int $simple_html, int $uriid): string
{
// Shared content
// when the content is meant exporting to other systems then remove the avatar picture since this doesn't really look good on these systems
if (!$try_oembed) {
if ($simple_html != self::INTERNAL) {
$text = preg_replace("/\[share(.*?)avatar\s?=\s?'.*?'\s?(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "\n[share$1$2]$3[/share]", $text);
}
@ -2361,7 +2302,6 @@ class BBCode
// Converting images with size parameters to simple images. Markdown doesn't know it.
$text = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $text);
// Convert it to HTML - don't try oembed
if ($for_diaspora) {
$text = self::convertForUriId(0, $text, self::DIASPORA);

Wyświetl plik

@ -172,6 +172,7 @@ class HTML
@$doc->loadHTML($message, LIBXML_HTML_NODEFDTD);
XML::deleteNode($doc, 'style');
XML::deleteNode($doc, 'script');
XML::deleteNode($doc, 'head');
XML::deleteNode($doc, 'title');
XML::deleteNode($doc, 'meta');

Wyświetl plik

@ -796,6 +796,11 @@ abstract class DI
return self::$dice->create(Content\Post\Repository\PostMedia::class);
}
public static function postMediaFactory(): Content\Post\Factory\PostMedia
{
return self::$dice->create(Content\Post\Factory\PostMedia::class);
}
/**
* @internal The EventDispatcher should never called outside of the core, like in addons or themes
* @deprecated 2025.07 Use constructor injection instead

Wyświetl plik

@ -3069,6 +3069,8 @@ class Item
$s .= $shared_html;
}
$s = DI::postMediaRepository()->addEmbed($s, $uid, $item['uri-id']);
$s = HTML::applyContentFilter($s, $filter_reasons);
$hook_data = [
@ -3152,6 +3154,23 @@ class Item
return $s;
}
private static function containsEmbed(string $body, string $url): bool
{
if (preg_match_all("/\[audio\]([^\[\]]*)\[\/audio\]/Usi", $body, $embeds)) {
return in_array($url, $embeds[1]);
}
if (preg_match_all("/\[video\]([^\[\]]*)\[\/video\]/Usi", $body, $embeds)) {
return in_array($url, $embeds[1]);
}
if (preg_match_all("/\[embed\]([^\[\]]*)\[\/embed\]/Usi", $body, $embeds)) {
return in_array($url, $embeds[1]);
}
return false;
}
/**
* Check if the body contains a link
*
@ -3259,7 +3278,7 @@ class Item
// @todo In the future we should make a single for the template engine with all media in it. This allows more flexibilty.
foreach ($PostMedias as $PostMedia) {
if (self::containsLink($item['body'], $PostMedia->preview ?? $PostMedia->url, $PostMedia->type)) {
if (self::containsLink($item['body'], $PostMedia->preview ?? $PostMedia->url, $PostMedia->type) || self::containsEmbed($item['body'], $PostMedia->url)) {
continue;
}
@ -3275,53 +3294,21 @@ class Item
continue;
}
if (($PostMedia->mimetype->type == 'video') || ($PostMedia->type == Post\Media::HLS)) {
if (($PostMedia->height ?? 0) > ($PostMedia->width ?? 0)) {
$height = min(DI::config()->get('system', 'max_video_height') ?: '100%', $PostMedia->height);
$width = 'auto';
} else {
$height = 'auto';
$width = '100%';
}
if (DI::pConfig()->get($uid, 'system', 'embed_media', false) && ($PostMedia->playerUrl != '') && ($PostMedia->playerHeight > 0)) {
$media = DI::contentItem()->getPlayerIframe($PostMedia->playerUrl, $PostMedia->playerWidth, $PostMedia->playerHeight);
} else {
/// @todo Move the template to /content as well
$media = Renderer::replaceMacros(Renderer::getMarkupTemplate($PostMedia->type == Post\Media::HLS ? 'hls_top.tpl' : 'video_top.tpl'), [
'$video' => [
'id' => $PostMedia->id,
'src' => (string)$PostMedia->url,
'name' => $PostMedia->name ?: $PostMedia->url,
'preview' => $preview_url,
'mime' => (string)$PostMedia->mimetype,
'height' => $height,
'width' => $width,
'description' => $PostMedia->description,
],
]);
}
if (in_array($PostMedia->type, [Post\Media::VIDEO, Post\Media::HLS])) {
$media = DI::postMediaRepository()->getVideoAttachment($PostMedia, $uid);
if (($item['post-type'] ?? null) == Item::PT_VIDEO) {
$leading .= $media;
} else {
$trailing .= $media;
}
} elseif ($PostMedia->mimetype->type == 'audio') {
$media = Renderer::replaceMacros(Renderer::getMarkupTemplate('content/audio.tpl'), [
'$audio' => [
'id' => $PostMedia->id,
'src' => (string)$PostMedia->url,
'name' => $PostMedia->name ?: $PostMedia->url,
'mime' => (string)$PostMedia->mimetype,
],
]);
} elseif ($PostMedia->type == Post\Media::AUDIO) {
$media = DI::postMediaRepository()->getAudioAttachment($PostMedia);
if (($item['post-type'] ?? null) == Item::PT_AUDIO) {
$leading .= $media;
} else {
$trailing .= $media;
}
} elseif ($PostMedia->mimetype->type == 'image') {
} elseif ($PostMedia->type == Post\Media::IMAGE) {
$src_url = DI::baseUrl() . $PostMedia->getPhotoPath();
if (self::containsLink($item['body'], $src_url)) {
continue;
@ -3416,7 +3403,6 @@ class Item
'player_url' => (string)$attachment->playerUrl,
'player_width' => $attachment->playerWidth,
'player_height' => $attachment->playerHeight,
];
if ($preview && $attachment->preview) {
@ -3468,8 +3454,8 @@ class Item
// @todo Use a template
$preview_mode = DI::pConfig()->get($uid, 'system', 'preview_mode', BBCode::PREVIEW_LARGE);
if ($preview_mode != BBCode::PREVIEW_NONE) {
$rendered = BBCode::convertAttachment('', BBCode::INTERNAL, false, $data, $uriid, $preview_mode, DI::pConfig()->get($uid, 'system', 'embed_remote_media', false));
if ($preview_mode != BBCode::PREVIEW_NONE && !self::containsEmbed($body, $data['url'])) {
$rendered = BBCode::convertAttachment('', BBCode::INTERNAL, $data, $uriid, $preview_mode, DI::pConfig()->get($uid, 'system', 'embed_remote_media', false));
} elseif (!self::containsLink($content, $data['url'], Post\Media::HTML)) {
$rendered = Renderer::replaceMacros(Renderer::getMarkupTemplate('content/link.tpl'), [
'$url' => $data['url'],

Wyświetl plik

@ -189,8 +189,12 @@ class Media
}
}
if (($media['type'] == self::HLS) && empty($media['mimetype'])) {
$media['mimetype'] = 'application/vnd.apple.mpegurl';
}
// Fetch the mimetype or size if missing.
if (Network::isValidHttpUrl($media['url']) && (empty($media['mimetype']) || $media['type'] == self::HTML) && !in_array($media['type'], [self::IMAGE, self::HLS])) {
if (Network::isValidHttpUrl($media['url']) && (empty($media['mimetype']) || $media['type'] == self::HTML) && ($media['type'] != self::IMAGE)) {
$timeout = DI::config()->get('system', 'xrd_timeout');
try {
$curlResult = DI::httpClient()->head($media['url'], [HttpClientOptions::ACCEPT_CONTENT => HttpClientAccept::AS_DEFAULT, HttpClientOptions::TIMEOUT => $timeout, HttpClientOptions::REQUEST => HttpClientRequest::CONTENTTYPE]);
@ -861,6 +865,14 @@ class Media
}
}
if (preg_match_all("/\[embed\]([^\[\]]*)\[\/embed\]$endmatchpattern/ism", $body, $embeds, PREG_SET_ORDER)) {
foreach ($embeds as $embed) {
$body = str_replace($embed[0], '', $body);
$attachments[$embed[1]] = ['uri-id' => $uriid, 'type' => self::UNKNOWN, 'url' => $embed[1]];
}
}
if ($uriid != 0) {
foreach ($attachments as $attachment) {
if (Post\Link::exists($uriid, $attachment['preview'] ?? $attachment['url'])) {

Wyświetl plik

@ -21,6 +21,8 @@ use Friendica\Network\HTTPException;
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
use Friendica\Network\HTTPClient\Client\HttpClientRequest;
use Embera\Embera;
use Friendica\Content\Text\BBCode;
use Friendica\Model\Post;
/**
* Get information about a given URL
@ -220,7 +222,9 @@ class ParseUrl
}
DI::logger()->info('Got content-type', ['content-type' => $type, 'url' => $url]);
if (!empty($type) && in_array($type[0], ['image', 'video', 'audio'])) {
$siteinfo['type'] = $type[0];
$siteinfo['type'] = $type[0];
$siteinfo['mimetype'] = implode('/', $type);
$siteinfo['mediatype'] = Post\Media::getType($siteinfo['mimetype']);
return $siteinfo;
}
@ -240,7 +244,20 @@ class ParseUrl
return $siteinfo;
}
$siteinfo['expires'] = DateTimeFormat::utc(self::DEFAULT_EXPIRATION_SUCCESS);
$siteinfo['mimetype'] = $curlResult->getContentType() ?: $mimetype;
$siteinfo['mediatype'] = Post\Media::getType($siteinfo['mimetype']);
$siteinfo['expires'] = DateTimeFormat::utc(self::DEFAULT_EXPIRATION_SUCCESS);
if (isset($curlResult->getHeader('Last-Modified')[0]) && $curlResult->getHeader('Last-Modified')[0] != '') {
$siteinfo['modified'] = DateTimeFormat::utc($curlResult->getHeader('Last-Modified')[0]);
}
if (isset($curlResult->getHeader('Expires')[0]) && $curlResult->getHeader('Expires')[0] != '') {
$expires = DateTimeFormat::utc($curlResult->getHeader('Expires')[0]);
if (time() < strtotime($expires)) {
$siteinfo['expires'] = $expires;
}
}
if ($cacheControlHeader = $curlResult->getHeader('Cache-Control')[0] ?? '') {
if (preg_match('/max-age=([0-9]+)/i', $cacheControlHeader, $matches)) {
@ -1326,6 +1343,8 @@ class ParseUrl
return $siteinfo;
}
DI::logger()->debug('Got oEmbed data', ['url' => $siteinfo['url'], 'type' => $data['type'], 'data' => $data]);
// Youtube provides only basic information to some IP ranges.
// We can detect this by checking if the host is youtube.com and if there is no player information.
// In this case we remove all tainted information provided by Youtube and use the ones provided by OEmbed.
@ -1340,6 +1359,7 @@ class ParseUrl
$fields = [
'title' => 'title',
'text' => 'description',
'author_name' => 'author_name',
'author_url' => 'author_url',
'publisher_name' => 'provider_name',
@ -1369,6 +1389,14 @@ class ParseUrl
}
}
if ($data['type'] == 'rich' && isset($data['html']) && !isset($siteinfo['text'])) {
$bbcode = HTML::toBBCode($data['html'] ?? '');
$bbcode = preg_replace("(\[url\](.*?)\[\/url\])ism", "", $bbcode);
$siteinfo['text'] = strip_tags(BBCode::convert($bbcode, false));
DI::logger()->debug('Text is fetched from oEmbed HTML', ['url' => $siteinfo['url'], 'text' => $siteinfo['text']]);
}
return $siteinfo;
}

Wyświetl plik

@ -189,7 +189,7 @@ class BBCodeTest extends FixtureTestCase
'simpleHtml' => BBCode::DIASPORA,
],
'bug-7665-audio-tag' => [
'expectedHtml' => '<audio src="http://www.cendrones.fr/colloque2017/jonathanbocquet.mp3" controls><a href="http://www.cendrones.fr/colloque2017/jonathanbocquet.mp3">http://www.cendrones.fr/colloque2017/jonathanbocquet.mp3</a></audio>',
'expectedHtml' => '<a class="embed" href="http://www.cendrones.fr/colloque2017/jonathanbocquet.mp3">cendrones.fr/colloque2017/jona…</a>',
'text' => '[audio]http://www.cendrones.fr/colloque2017/jonathanbocquet.mp3[/audio]',
'try_oembed' => true,
],
@ -756,7 +756,7 @@ Lucas: For the right price, yes.[/share]',
*/
public function testConvertAttachment(string $expected, array $data)
{
$actual = BBCode::convertAttachment('', BBCode::INTERNAL, true, $data, 0, BBCode::PREVIEW_LARGE, true);
$actual = BBCode::convertAttachment('', BBCode::INTERNAL, $data, 0, BBCode::PREVIEW_LARGE, true);
self::assertEquals($expected, $actual);
}

Wyświetl plik

@ -370,7 +370,7 @@ function string2bb(element) {
$.fn.bbco_autocomplete = function(type) {
if (type === 'bbcode') {
var open_close_elements = ['bold', 'italic', 'underline', 'overline', 'strike', 'quote', 'code', 'spoiler', 'map', 'img', 'url', 'audio', 'video', 'embed', 'youtube', 'vimeo', 'list', 'ul', 'ol', 'li', 'table', 'tr', 'th', 'td', 'center', 'color', 'font', 'size', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'nobb', 'noparse', 'pre', 'abstract', 'share', 'attachment'];
var open_close_elements = ['bold', 'italic', 'underline', 'overline', 'strike', 'quote', 'code', 'spoiler', 'map', 'img', 'url', 'audio', 'video', 'embed', 'list', 'ul', 'ol', 'li', 'table', 'tr', 'th', 'td', 'center', 'color', 'font', 'size', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'nobb', 'noparse', 'pre', 'abstract', 'share', 'attachment'];
var open_elements = ['*', 'hr'];
var elements = open_close_elements.concat(open_elements);

Wyświetl plik

@ -6,8 +6,7 @@
*}}
<div class="video-top-wrapper lframe" id="video-top-wrapper-{{$video.id}}">
<script src="view/js/hls/hls.min.js"></script>
<video id="{{$video.id}}" controls poster="{{$video.preview}}" width="{{$video.width}}" height="{{$video.height}}"
title="{{$video.description}}">
<video id="{{$video.id}}" controls {{if $video.preview}}preload="none" poster="{{$video.preview}}" {{else}}preload="metadata" {{/if}} width="{{$video.width}}" height="{{$video.height}}" title="{{$video.description}}" type="{{$video.mime}}">
<a href="{{$video.src}}">{{$video.name}}</a>
</video>
<script>

Wyświetl plik

@ -6,7 +6,7 @@
*}}
<div class="video-top-wrapper lframe" id="video-top-wrapper-{{$video.id}}">
{{* set preloading to none to lessen the load on the server *}}
<video src="{{$video.src}}" controls {{if $video.preview}}preload="none" poster="{{$video.preview}}" {else}preload="metadata" {{/if}}width="{{$video.width}}" height="{{$video.height}}" title="{{$video.description}}">
<video src="{{$video.src}}" controls {{if $video.preview}}preload="none" poster="{{$video.preview}}" {{else}}preload="metadata" {{/if}}width="{{$video.width}}" height="{{$video.height}}" title="{{$video.description}}" type="{{$video.mime}}">
<a href="{{$video.src}}">{{$video.name}}</a>
</video>