Current Path : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/main/lib/urlpreview/ |
Current File : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/main/lib/urlpreview/urlpreview.php |
<?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; } }