Your IP : 3.12.111.193
<?php
namespace Bitrix\Main\UrlPreview;
use Bitrix\Main\ArgumentException;
use Bitrix\Main\Config\Option;
use Bitrix\Main\Loader;
use Bitrix\Main\Security\Random;
use Bitrix\Main\Security\Sign\Signer;
use Bitrix\Main\Web\HttpClient;
use Bitrix\Main\Web\Uri;
use Bitrix\Main\Web\IpAddress;
use Bitrix\Main\Web\Http\Response;
use Bitrix\Main\File\Image;
use Bitrix\Main\Web\MimeType;
class UrlPreview
{
const SIGN_SALT = 'url_preview';
const USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 (Bitrix link preview)';
/** @var int Maximum allowed length of the description. */
const MAX_DESCRIPTION = 500;
/** @var int Maximum allowed picture size */
const MAX_FILE_SIZE = 1048576;
/** @var int Range to read picture size */
const FILE_RANGE = 1023;
const IFRAME_MAX_WIDTH = 640;
const IFRAME_MAX_HEIGHT = 340;
protected static $trustedHosts = [
'youtube.com' => 'youtube.com',
'youtu.be' => 'youtu.be',
'vimeo.com' => 'vimeo.com',
'rutube.ru' => 'rutube.ru',
'facebook.com' => 'facebook.com',
'fb.watch' => 'fb.watch',
'vk.com' => 'vk.com',
'instagram.com' => 'instagram.com',
];
/**
* Returns associated metadata for the specified URL
*
* @param string $url URL.
* @param bool $addIfNew Should metadata be fetched and saved, if not found in database.
* @param bool $reuseExistingMetadata Allow reading of the cached metadata.
* @return array|false Metadata for the URL if found, or false otherwise.
*/
public static function getMetadataByUrl($url, $addIfNew = true, $reuseExistingMetadata = true)
{
if (!static::isEnabled())
{
return false;
}
$url = static::normalizeUrl($url);
if ($url == '')
{
return false;
}
if ($reuseExistingMetadata)
{
if ($metadata = UrlMetadataTable::getByUrl($url))
{
if ($metadata['TYPE'] === UrlMetadataTable::TYPE_TEMPORARY && $addIfNew)
{
$metadata = static::resolveTemporaryMetadata($metadata['ID']);
return $metadata;
}
if ($metadata['TYPE'] !== UrlMetadataTable::TYPE_STATIC
|| !isset($metadata['DATE_EXPIRE'])
|| $metadata['DATE_EXPIRE']->getTimestamp() > time()
)
{
return $metadata;
}
if (static::refreshMetadata($metadata))
{
return $metadata;
}
}
}
if (!$addIfNew)
{
return false;
}
$metadataId = static::reserveIdForUrl($url);
$metadata = static::fetchUrlMetadata($url);
if (is_array($metadata) && !empty($metadata))
{
$result = UrlMetadataTable::update($metadataId, $metadata);
$metadata['ID'] = $result->getId();
return $metadata;
}
return false;
}
/**
* Returns html code for url preview
*
* @param array $userField Userfield's value.
* @param array $userFieldParams Userfield's parameters.
* @param string $cacheTag Cache tag for returned preview (out param).
* @param bool $edit Show method build preview for editing the userfield.
* @return string HTML code for the preview.
*/
public static function showView($userField, $userFieldParams, &$cacheTag, $edit = false)
{
global $APPLICATION;
$edit = !!$edit;
$cacheTag = '';
if (!static::isEnabled())
{
return null;
}
$metadataId = (int)$userField['VALUE'][0];
$metadata = false;
if ($metadataId > 0)
{
$metadata = UrlMetadataTable::getById($metadataId)->fetch();
if (isset($metadata['TYPE']) && $metadata['TYPE'] == UrlMetadataTable::TYPE_TEMPORARY)
{
$metadata = static::resolveTemporaryMetadata($metadata['ID']);
}
}
if (is_array($metadata))
{
$fullUrl = static::unfoldShortLink($metadata['URL']);
if ($metadata['TYPE'] == UrlMetadataTable::TYPE_DYNAMIC)
{
$routeRecord = Router::dispatch(new Uri($fullUrl));
if (isset($routeRecord['MODULE']) && Loader::includeModule($routeRecord['MODULE']))
{
$className = $routeRecord['CLASS'];
$routeRecord['PARAMETERS']['URL'] = $metadata['URL'];
$parameters = $routeRecord['PARAMETERS'];
if ($edit && (!method_exists($className, 'checkUserReadAccess') || !$className::checkUserReadAccess($parameters, static::getCurrentUserId())))
{
return null;
}
if (method_exists($className, 'buildPreview'))
{
$metadata['HANDLER'] = $routeRecord;
$metadata['HANDLER']['BUILD_METHOD'] = 'buildPreview';
}
if (method_exists($className, 'getCacheTag'))
{
$cacheTag = $className::getCacheTag();
}
}
elseif (!$edit)
{
return null;
}
}
}
elseif (!$edit)
{
return null;
}
ob_start();
$APPLICATION->IncludeComponent(
'bitrix:main.urlpreview',
'',
array(
'USER_FIELD' => $userField,
'METADATA' => is_array($metadata) ? $metadata : [],
'PARAMS' => $userFieldParams,
'EDIT' => ($edit ? 'Y' : 'N'),
'CHECK_ACCESS' => ($edit ? 'Y' : 'N'),
)
);
return ob_get_clean();
}
/**
* Returns html code for url preview edit form
*
* @param array $userField Userfield's value.
* @param array $userFieldParams Userfield's parameters.
* @return string HTML code for the preview.
*/
public static function showEdit($userField, $userFieldParams)
{
return static::showView($userField, $userFieldParams, $cacheTag, true);
}
/**
* Checks if metadata for the provided url is already fetched and cached.
*
* @param string $url Document's URL.
* @return bool True if metadata for the url is located in database, false otherwise.
*/
public static function isUrlCached($url)
{
$url = static::normalizeUrl($url);
if ($url == '')
{
return false;
}
return (static::isUrlLocal(new Uri($url)) || !!UrlMetadataTable::getByUrl($url));
}
/**
* If url is remote - returns metadata for this url. If url is local - checks current user access to the entity
* behind the url, and returns html preview for this entity.
*
* @param string $url Document's URL.
* @param bool $addIfNew Should method fetch and store metadata for the document, if it is not found in database.
* @params bool $reuseExistingMetadata Allow reading of the cached metadata.
* @return array|false Metadata for the document, or false if metadata could not be fetched/parsed.
*/
public static function getMetadataAndHtmlByUrl($url, $addIfNew = true, $reuseExistingMetadata = true)
{
$metadata = static::getMetadataByUrl($url, $addIfNew, $reuseExistingMetadata);
if ($metadata === false)
{
return false;
}
if ($metadata['TYPE'] == UrlMetadataTable::TYPE_STATIC || $metadata['TYPE'] == UrlMetadataTable::TYPE_FILE)
{
return $metadata;
}
elseif ($metadata['TYPE'] == UrlMetadataTable::TYPE_DYNAMIC)
{
if ($preview = static::getDynamicPreview($url))
{
$metadata['HTML'] = $preview;
return $metadata;
}
}
return false;
}
/**
* Returns stored metadata for array of IDs
*
* @param array $ids Array of record's IDs.
* @param bool $checkAccess Should method check current user's access to the internal entities, or not.
* @params int $userId. ID of the users to check access. If == 0, will check access for current user.
* @return array|false Array with provided IDs as the keys.
*/
public static function getMetadataAndHtmlByIds(array $ids, $checkAccess = true, $userId = 0)
{
if (!static::isEnabled())
{
return false;
}
$result = [];
$queryResult = UrlMetadataTable::getList([
'filter' => [
'ID' => $ids,
'!=TYPE' => UrlMetadataTable::TYPE_TEMPORARY,
]
]);
while ($metadata = $queryResult->fetch())
{
if ($metadata['TYPE'] == UrlMetadataTable::TYPE_DYNAMIC)
{
$metadata['HTML'] = static::getDynamicPreview($metadata['URL'], $checkAccess, $userId);
if ($metadata['HTML'] === false)
{
continue;
}
}
if ($metadata['TYPE'] == UrlMetadataTable::TYPE_STATIC
&& isset($metadata['DATE_EXPIRE'])
&& $metadata['DATE_EXPIRE']->getTimestamp() <= time()
)
{
$refreshResult = static::refreshMetadata($metadata);
if (!$refreshResult)
{
continue;
}
}
$result[$metadata['ID']] = $metadata;
}
return $result;
}
public static function getMetadataByIds(array $ids)
{
if (!static::isEnabled())
{
return false;
}
$result = [];
$queryResult = UrlMetadataTable::getList([
'filter' => [
'ID' => $ids,
'!=TYPE' => UrlMetadataTable::TYPE_TEMPORARY,
]
]);
while ($metadata = $queryResult->fetch())
{
if ($metadata['TYPE'] == UrlMetadataTable::TYPE_STATIC
&& isset($metadata['DATE_EXPIRE'])
&& $metadata['DATE_EXPIRE']->getTimestamp() <= time()
)
{
$refreshResult = static::refreshMetadata($metadata);
if (!$refreshResult)
{
continue;
}
}
$result[$metadata['ID']] = $metadata;
}
return $result;
}
/**
* Creates temporary record for url
*
* @param string $url URL for which temporary record should be created.
* @return int Temporary record's id.
*/
public static function reserveIdForUrl($url)
{
if ($metadata = UrlMetadataTable::getByUrl($url))
{
$id = $metadata['ID'];
}
else
{
$result = UrlMetadataTable::add(array(
'URL' => $url,
'TYPE' => UrlMetadataTable::TYPE_TEMPORARY
));
$id = $result->getId();
}
return $id;
}
/**
* Fetches and stores metadata for temporary record, created by UrlPreview::reserveIdForUrl. If metadata could
* not be fetched, deletes record.
* @param int $id Metadata record's id.
* @param bool $checkAccess Should method check current user's access to the entity, or not.
* @params int $userId. ID of the users to check access. If == 0, will check access for current user.
* @return array|false Metadata if fetched, false otherwise.
*/
public static function resolveTemporaryMetadata($id, $checkAccess = true, $userId = 0)
{
$metadata = UrlMetadataTable::getRowById($id);
if (!is_array($metadata))
{
return false;
}
if ($metadata['TYPE'] == UrlMetadataTable::TYPE_TEMPORARY)
{
$metadata['URL'] = static::normalizeUrl($metadata['URL']);
$metadata = static::fetchUrlMetadata($metadata['URL']);
if ($metadata === false)
{
UrlMetadataTable::delete($id);
return false;
}
UrlMetadataTable::update($id, $metadata);
return $metadata;
}
elseif ($metadata['TYPE'] == UrlMetadataTable::TYPE_STATIC || $metadata['TYPE'] == UrlMetadataTable::TYPE_FILE)
{
return $metadata;
}
elseif ($metadata['TYPE'] == UrlMetadataTable::TYPE_DYNAMIC)
{
if ($preview = static::getDynamicPreview($metadata['URL'], $checkAccess, $userId))
{
$metadata['HTML'] = $preview;
return $metadata;
}
}
return false;
}
protected static function refreshMetadata(array &$metadata): bool
{
if ($metadata['TYPE'] !== UrlMetadataTable::TYPE_STATIC)
{
return false;
}
$url = static::normalizeUrl($metadata['URL']);
$refreshedMetadata = static::fetchUrlMetadata($url);
if (!$refreshedMetadata)
{
return false;
}
if ($metadata['ID'])
{
UrlMetadataTable::update($metadata['ID'], $refreshedMetadata);
$refreshedMetadata['ID'] = $metadata['ID'];
}
$metadata = $refreshedMetadata;
return true;
}
/**
* Returns HTML code for the dynamic (internal url) preview.
* @param string $url URL of the internal document.
* @param bool $checkAccess Should method check current user's access to the entity, or not.
* @params int $userId. ID of the users to check access. If userId == 0, will check access for current user.
* @return string|false HTML code of the preview, or false if case of any errors (including access denied)/
*/
public static function getDynamicPreview($url, $checkAccess = true, $userId = 0)
{
$routeRecord = Router::dispatch(new Uri(static::unfoldShortLink($url)));
if ($routeRecord === false)
{
return false;
}
if (isset($routeRecord['MODULE']) && Loader::includeModule($routeRecord['MODULE']))
{
$className = $routeRecord['CLASS'];
$parameters = $routeRecord['PARAMETERS'];
$parameters['URL'] = $url;
if ($userId == 0)
{
$userId = static::getCurrentUserId();
}
if ($checkAccess && (!method_exists($className, 'checkUserReadAccess') || $userId == 0 || !$className::checkUserReadAccess($parameters, $userId)))
return false;
if (method_exists($className, 'buildPreview'))
{
$preview = $className::buildPreview($parameters);
return ($preview <> '' ? $preview : false);
}
}
return false;
}
/**
* Returns attach for the IM message with the requested internal entity content.
* @param string $url URL of the internal document.
* @param bool $checkAccess Should method check current user's access to the entity, or not.
* @params int $userId. ID of the users to check access. If userId == 0, will check access for current user.
* @return \CIMMessageParamAttach | false
*/
public static function getImAttach($url, $checkAccess = true, $userId = 0)
{
return self::getUrlInfoFromExternal($url, 'getImAttach', $checkAccess, $userId);
}
/**
* @param $url
* @param bool $checkAccess
* @param int $userId
* @return \Bitrix\Im\V2\Entity\Url\RichData | false
*/
public static function getImRich($url, $checkAccess = true, $userId = 0)
{
return self::getUrlInfoFromExternal($url, 'getImRich', $checkAccess, $userId);
}
/**
* Returns true if current user has read access to the content behind internal url.
* @param string $url URL of the internal document.
* @params int $userId. ID of the users to check access. If userId == 0, will check access for current user.
* @return bool True if current user has read access to the main entity of the document, or false otherwise.
*/
public static function checkDynamicPreviewAccess($url, $userId = 0)
{
$routeRecord = Router::dispatch(new Uri(static::unfoldShortLink($url)));
if ($routeRecord === false)
{
return false;
}
if (isset($routeRecord['MODULE']) && Loader::includeModule($routeRecord['MODULE']))
{
$className = $routeRecord['CLASS'];
$parameters = $routeRecord['PARAMETERS'];
if ($userId == 0)
{
$userId = static::getCurrentUserId();
}
return (method_exists($className, 'checkUserReadAccess') && $userId > 0 && $className::checkUserReadAccess($parameters, $userId));
}
return false;
}
/**
* Sets main image url for the metadata with given id.
* @param int $id ID of the metadata to set image url.
* @param string $imageUrl Url of the image.
* @return bool Returns true in case of successful update, or false otherwise.
* @throws ArgumentException
*/
public static function setMetadataImage($id, $imageUrl)
{
if (!is_int($id))
{
throw new ArgumentException("Id of the metadata must be an integer", "id");
}
if (!is_string($imageUrl) && !is_null($imageUrl))
{
throw new ArgumentException("Url of the image must be a string", "imageUrl");
}
$metadata = UrlMetadataTable::getList(array(
'select' => array('IMAGE', 'IMAGE_ID', 'EXTRA'),
'filter' => array('=ID' => $id)
))->fetch();
if (isset($metadata['EXTRA']['IMAGES']))
{
$imageIndex = array_search($imageUrl, $metadata['EXTRA']['IMAGES']);
if ($imageIndex === false)
{
unset($metadata['EXTRA']['SELECTED_IMAGE']);
}
else
{
$metadata['EXTRA']['SELECTED_IMAGE'] = $imageIndex;
}
}
static::fetchImageMetadata($imageUrl, $metadata);
return UrlMetadataTable::update($id, $metadata)->isSuccess();
}
/**
* Checks if UrlPreview is enabled in module option
* @return bool True if UrlPreview is enabled in module options.
*/
public static function isEnabled()
{
static $result = null;
if (is_null($result))
{
$result = Option::get('main', 'url_preview_enable', 'N') === 'Y';
}
return $result;
}
/**
* Signs value using UrlPreview salt
* @param string $id Unsigned value.
* @return string Signed value.
* @throws \Bitrix\Main\ArgumentTypeException
*/
public static function sign($id)
{
$signer = new Signer();
return $signer->sign((string)$id, static::SIGN_SALT);
}
protected static function getUrlInfoFromExternal($url, $method, $checkAccess = true, $userId = 0)
{
//todo: caching
$routeRecord = Router::dispatch(new Uri(static::unfoldShortLink($url)));
if ($routeRecord === false)
{
return false;
}
if ($userId == 0)
{
$userId = static::getCurrentUserId();
}
if (isset($routeRecord['MODULE']) && Loader::includeModule($routeRecord['MODULE']))
{
$className = $routeRecord['CLASS'];
$parameters = $routeRecord['PARAMETERS'];
$parameters['URL'] = $url;
if ($checkAccess && (!method_exists($className, 'checkUserReadAccess') || $userId == 0 || !$className::checkUserReadAccess($parameters, $userId)))
return false;
if (method_exists($className, $method))
{
return $className::$method($parameters);
}
}
return false;
}
/**
* @param string $url URL of the document.
* @return array|false Fetched metadata or false if metadata was not found, or was invalid.
*/
protected static function fetchUrlMetadata($url)
{
$fullUrl = static::unfoldShortLink($url);
$uriParser = new Uri($fullUrl);
if (static::isUrlLocal($uriParser))
{
if (Router::dispatch($uriParser))
{
$metadata = array(
'URL' => $url,
'TYPE' => UrlMetadataTable::TYPE_DYNAMIC,
);
}
}
else
{
$metadataRemote = static::getRemoteUrlMetadata($uriParser);
if (is_array($metadataRemote) && !empty($metadataRemote))
{
$metadata = array(
'URL' => $url,
'TYPE' => $metadataRemote['TYPE'] ?? UrlMetadataTable::TYPE_STATIC,
'TITLE' => $metadataRemote['TITLE'] ?? '',
'DESCRIPTION' => $metadataRemote['DESCRIPTION'] ?? '',
'IMAGE_ID' => $metadataRemote['IMAGE_ID'] ?? null,
'IMAGE' => $metadataRemote['IMAGE'] ?? null,
'EMBED' => $metadataRemote['EMBED'] ?? null,
'EXTRA' => $metadataRemote['EXTRA'] ?? null,
'DATE_EXPIRE' => $metadataRemote['DATE_EXPIRE'] ?? null,
);
}
}
if (isset($metadata['TYPE']))
{
return $metadata;
}
return false;
}
/**
* Returns true if given URL is local
*
* @param Uri $uri Absolute URL to be checked.
* @return bool
*/
protected static function isUrlLocal(Uri $uri)
{
if ($uri->getHost() == '')
{
return true;
}
$host = \Bitrix\Main\Context::getCurrent()->getRequest()->getHttpHost();
return $uri->getHost() === $host;
}
/**
* @param Uri $uri Absolute URL to get metadata for.
* @return array|false
*/
protected static function getRemoteUrlMetadata(Uri $uri)
{
$httpClient = (new HttpClient())
->setPrivateIp(false) //prevents proxy to LAN
->setTimeout(5)
->setStreamTimeout(5)
->setHeader('User-Agent', self::USER_AGENT)
;
$httpClient->shouldFetchBody(function (Response $response) {
$contentType = $response->getHeadersCollection()->getContentType();
return ($contentType === 'text/html' || MimeType::isImage($contentType));
});
try
{
if (!$httpClient->query('GET', $uri->getUri()))
{
return false;
}
}
catch (\ErrorException)
{
return false;
}
if ($httpClient->getStatus() !== 200)
{
return false;
}
$peerIpAddress = $httpClient->getPeerAddress();
if ($httpClient->getHeaders()->getContentType() !== 'text/html')
{
$metadata = static::getFileMetadata($httpClient);
$metadata['EXTRA']['PEER_IP_ADDRESS'] = $peerIpAddress;
$metadata['EXTRA']['PEER_IP_PRIVATE'] = (new IpAddress($peerIpAddress))->isPrivate();
return $metadata;
}
$html = $httpClient->getResult();
$htmlDocument = new HtmlDocument($html, $uri);
$htmlDocument->setEncoding($httpClient->getCharset());
ParserChain::extractMetadata($htmlDocument);
$metadata = $htmlDocument->getMetadata();
if (is_array($metadata) && static::validateRemoteMetadata($metadata))
{
if (isset($metadata['IMAGE']))
{
static::fetchImageMetadata($metadata['IMAGE'], $metadata);
}
if (isset($metadata['DESCRIPTION']) && mb_strlen($metadata['DESCRIPTION']) > static::MAX_DESCRIPTION)
{
$metadata['DESCRIPTION'] = mb_substr($metadata['DESCRIPTION'], 0, static::MAX_DESCRIPTION);
}
if (!isset($metadata['EXTRA']) || !is_array($metadata['EXTRA']))
{
$metadata['EXTRA'] = array();
}
$metadata['EXTRA'] = array_merge($metadata['EXTRA'], array(
'PEER_IP_ADDRESS' => $peerIpAddress,
'PEER_IP_PRIVATE' => (new IpAddress($peerIpAddress))->isPrivate(),
'X_FRAME_OPTIONS' => $httpClient->getHeaders()->get('X-Frame-Options', true),
'EFFECTIVE_URL' => $httpClient->getEffectiveUrl(),
));
return $metadata;
}
return false;
}
protected static function getTempPath(string $fileName): string
{
$tempFileName = Random::getString(32) . '.' . GetFileExtension($fileName);
$tempPath = \CFile::GetTempName('', $tempFileName);
return $tempPath;
}
protected static function downloadFile(string $url, ?string &$fileName = null, ?int $range = null): ?string
{
$httpClient = (new HttpClient())
->setPrivateIp(false)
->setTimeout(5)
->setStreamTimeout(5)
->setBodyLengthMax(self::MAX_FILE_SIZE)
;
if ($range !== null)
{
$httpClient->setHeader('Range', 'bytes=0-' . $range);
}
$urlComponents = parse_url($url);
$fileName = ($urlComponents && $urlComponents["path"] <> '')
? bx_basename($urlComponents["path"])
: bx_basename($url)
;
$tempPath = static::getTempPath($fileName);
try
{
if (!$httpClient->download($url, $tempPath))
{
return null;
}
}
catch (\ErrorException)
{
return null;
}
if (($name = $httpClient->getHeaders()->getFilename()) !== null)
{
$fileName = $name;
}
return $tempPath;
}
/**
* @param string $tempPath
* @param string|null $fileName
* @return integer Saved file identifier
*/
protected static function saveImage(string $tempPath, ?string $fileName)
{
$fileId = false;
$localFile = \CFile::MakeFileArray($tempPath);
if (is_array($localFile))
{
$localFile['MODULE_ID'] = 'main';
if ($fileName <> '')
{
$localFile['name'] = $fileName;
}
if (\CFile::CheckImageFile($localFile, 0, 0, 0, array("IMAGE")) === null)
{
$fileId = \CFile::SaveFile($localFile, 'urlpreview', true);
}
}
return ($fileId === false ? null : $fileId);
}
protected static function fetchImageMetadata(string $imageUrl, array &$metadata): void
{
$saveImage = static::getOptionSaveImages();
$tempPath = static::downloadFile($imageUrl, $fileName, ($saveImage ? null : self::FILE_RANGE));
if ($tempPath !== null)
{
$info = (new Image($tempPath))->getInfo();
if ($info)
{
$metadata['EXTRA']['IMAGE_INFO'] = [
'WIDTH' => $info->getWidth(),
'HEIGHT' => $info->getHeight(),
];
}
if ($saveImage)
{
$metadata['IMAGE_ID'] = static::saveImage($tempPath, $fileName);
$metadata['IMAGE'] = null;
}
else
{
$metadata['IMAGE'] = $imageUrl;
$metadata['IMAGE_ID'] = null;
}
}
}
/**
* If provided url does not contain scheme part, tries to add it
*
* @param string $url URL to be fixed.
* @return string Fixed URL.
*/
protected static function normalizeUrl($url)
{
if (str_starts_with($url, 'https://') || str_starts_with($url, 'http://'))
{
//nop
}
elseif (str_starts_with($url, '//'))
{
$url = 'http:'.$url;
}
elseif (str_starts_with($url, '/'))
{
//nop
}
else
{
$url = 'http://'.$url;
}
$parsedUrl = new Uri($url);
$parsedUrl->setHost(mb_strtolower($parsedUrl->getHost()));
return $parsedUrl->getUri();
}
/**
* Returns value of the option for saving images locally.
* @return bool True if images should be saved locally.
*/
protected static function getOptionSaveImages()
{
static $result = null;
if (is_null($result))
{
$result = Option::get('main', 'url_preview_save_images', 'N') === 'Y';
}
return $result;
}
/**
* Checks if metadata is complete.
* @param array $metadata HTML document metadata.
* @return bool True if metadata is complete, false otherwise.
*/
protected static function validateRemoteMetadata(array $metadata)
{
$result = ((isset($metadata['TITLE']) && isset($metadata['IMAGE'])) || (isset($metadata['TITLE']) && isset($metadata['DESCRIPTION'])) || isset($metadata['EMBED']));
return $result;
}
/**
* Returns id of currently logged user.
* @return int User's id.
*/
public static function getCurrentUserId()
{
return ($GLOBALS['USER'] instanceof \CUser) ? (int)$GLOBALS['USER']->getId() : 0;
}
/**
* Unfolds internal short url. If url is not classified as a short link, returns input $url.
* @param string $shortUrl Short URL.
* @return string Full URL.
*/
protected static function unfoldShortLink($shortUrl)
{
static $cache = [];
if (isset($cache[$shortUrl]))
{
return $cache[$shortUrl];
}
$result = $shortUrl;
if ($shortUri = \CBXShortUri::GetUri($shortUrl))
{
$result = $shortUri['URI'];
}
$cache[$shortUrl] = $result;
return $result;
}
/**
* Returns metadata for downloadable file.
* @param HttpClient $client
* @return array|bool Metadata record if mime type and filename were detected, or false otherwise.
*/
protected static function getFileMetadata(HttpClient $client)
{
$url = $client->getEffectiveUrl();
$httpHeaders = $client->getHeaders();
$mimeType = $httpHeaders->getContentType();
$filename = $httpHeaders->getFilename() ?: bx_basename($url);
$result = false;
if ($mimeType && $filename)
{
$result = array(
'TYPE' => UrlMetadataTable::TYPE_FILE,
'EXTRA' => array(
'ATTACHMENT' => strtolower($httpHeaders->getContentDisposition()) === 'attachment' ? 'Y' : 'N',
'MIME_TYPE' => $mimeType,
'FILENAME' => $filename,
'SIZE' => $httpHeaders->get('Content-Length')
)
);
if (MimeType::isImage($mimeType))
{
// download image to temp file to detect dimensions
$tempPath = static::getTempPath($filename);
$client->saveFile($tempPath);
$info = (new Image($tempPath))->getInfo();
if ($info)
{
$result['EXTRA']['IMAGE_INFO'] = [
'WIDTH' => $info->getWidth(),
'HEIGHT' => $info->getHeight(),
];
}
}
}
return $result;
}
/**
* @deprecated Will be removed.
* @param string $ipAddress
* @return bool
*/
public static function isIpAddressPrivate($ipAddress)
{
return (new IpAddress($ipAddress))->isPrivate();
}
/**
* Returns true if host of $uri is in $trustedHosts list.
*
* @param Uri $uri
* @return bool
*/
public static function isHostTrusted(Uri $uri)
{
$result = false;
$domainNameParts = explode('.', $uri->getHost());
if (is_array($domainNameParts) && ($partsCount = count($domainNameParts)) >= 2)
{
$domainName = $domainNameParts[$partsCount-2] . '.' . $domainNameParts[$partsCount-1];
$result = isset(static::$trustedHosts[$domainName]);
}
return $result;
}
/**
* Returns video metaData for $url if its host is trusted.
*
* @param string $url
* @return array|false
*/
public static function fetchVideoMetaData($url)
{
$url = static::unfoldShortLink($url);
$uri = new Uri($url);
if (static::isHostTrusted($uri) || static::isEnabled())
{
$url = static::normalizeUrl($url);
$metadataId = static::reserveIdForUrl($url);
$metadata = static::fetchUrlMetadata($url);
if (is_array($metadata) && !empty($metadata))
{
$result = UrlMetadataTable::update($metadataId, $metadata);
$metadata['ID'] = $result->getId();
}
else
{
return false;
}
if (!empty($metadata['EMBED']) && !str_contains($metadata['EMBED'], '<iframe'))
{
$url = static::getInnerFrameUrl($metadata['ID'], $metadata['EXTRA']['PROVIDER_NAME']);
if (intval($metadata['EXTRA']['VIDEO_WIDTH']) <= 0)
{
$metadata['EXTRA']['VIDEO_WIDTH'] = self::IFRAME_MAX_WIDTH;
}
if (intval($metadata['EXTRA']['VIDEO_HEIGHT']) <= 0)
{
$metadata['EXTRA']['VIDEO_HEIGHT'] = self::IFRAME_MAX_HEIGHT;
}
$metadata['EMBED'] = '<iframe src="'.$url.'" allowfullscreen="" width="'.$metadata['EXTRA']['VIDEO_WIDTH'].'" height="'.$metadata['EXTRA']['VIDEO_HEIGHT'].'" frameborder="0"></iframe>';
}
if ($metadata['EMBED'] || !empty($metadata['EXTRA']['VIDEO']))
{
return $metadata;
}
}
return false;
}
/**
* Returns inner frame url to embed third parties html video players.
*
* @param int $id
* @param string $provider
* @return bool|string
*/
public static function getInnerFrameUrl($id, $provider = '')
{
$result = false;
$componentPath = \CComponentEngine::makeComponentPath('bitrix:main.urlpreview');
if (!empty($componentPath))
{
$componentPath = getLocalPath('components'.$componentPath.'/frame.php');
$uri = new Uri($componentPath);
$uri->addParams(array('id' => $id, 'provider' => $provider));
$result = static::normalizeUrl($uri->getLocator());
}
return $result;
}
}