kopia lustrzana https://github.com/friendica/friendica
Embed instead of OEmbed
rodzic
d5121a70ad
commit
dbd56c3e94
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'],
|
||||
|
|
|
@ -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'])) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue