20 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)';
32 'youtube.com' =>
'youtube.com',
33 'youtu.be' =>
'youtu.be',
34 'vimeo.com' =>
'vimeo.com',
35 'rutube.ru' =>
'rutube.ru',
36 'facebook.com' =>
'facebook.com',
37 'fb.watch' =>
'fb.watch',
39 'instagram.com' =>
'instagram.com',
50 public static function getMetadataByUrl($url, $addIfNew =
true, $reuseExistingMetadata =
true)
52 if (!static::isEnabled())
57 $url = static::normalizeUrl($url);
63 if ($reuseExistingMetadata)
69 $metadata = static::resolveTemporaryMetadata($metadata[
'ID']);
73 || !isset($metadata[
'DATE_EXPIRE'])
74 || $metadata[
'DATE_EXPIRE']->getTimestamp() > time()
79 if (static::refreshMetadata($metadata))
91 $metadataId = static::reserveIdForUrl($url);
92 $metadata = static::fetchUrlMetadata($url);
93 if (is_array($metadata) && !empty($metadata))
95 $result = UrlMetadataTable::update($metadataId, $metadata);
96 $metadata[
'ID'] = $result->getId();
112 public static function showView($userField, $userFieldParams, &$cacheTag, $edit =
false)
118 if (!static::isEnabled())
123 $metadataId = (int)$userField[
'VALUE'][0];
127 $metadata = UrlMetadataTable::getById($metadataId)->fetch();
130 $metadata = static::resolveTemporaryMetadata($metadata[
'ID']);
134 if (is_array($metadata))
136 $fullUrl = static::unfoldShortLink($metadata[
'URL']);
143 $className = $routeRecord[
'CLASS'];
144 $routeRecord[
'PARAMETERS'][
'URL'] = $metadata[
'URL'];
145 $parameters = $routeRecord[
'PARAMETERS'];
147 if ($edit && (!method_exists($className,
'checkUserReadAccess') || !$className::checkUserReadAccess($parameters, static::getCurrentUserId())))
152 if (method_exists($className,
'buildPreview'))
154 $metadata[
'HANDLER'] = $routeRecord;
155 $metadata[
'HANDLER'][
'BUILD_METHOD'] =
'buildPreview';
158 if (method_exists($className,
'getCacheTag'))
160 $cacheTag = $className::getCacheTag();
175 $APPLICATION->IncludeComponent(
176 'bitrix:main.urlpreview',
179 'USER_FIELD' => $userField,
180 'METADATA' => is_array($metadata) ? $metadata : [],
181 'PARAMS' => $userFieldParams,
182 'EDIT' => ($edit ?
'Y' :
'N'),
183 'CHECK_ACCESS' => ($edit ?
'Y' :
'N'),
186 return ob_get_clean();
196 public static function showEdit($userField, $userFieldParams)
198 return static::showView($userField, $userFieldParams, $cacheTag,
true);
209 $url = static::normalizeUrl($url);
229 $metadata = static::getMetadataByUrl($url, $addIfNew, $reuseExistingMetadata);
230 if ($metadata ===
false)
241 if ($preview = static::getDynamicPreview($url))
243 $metadata[
'HTML'] = $preview;
262 if (!static::isEnabled())
269 $queryResult = UrlMetadataTable::getList([
276 while ($metadata = $queryResult->fetch())
280 $metadata[
'HTML'] = static::getDynamicPreview($metadata[
'URL'], $checkAccess, $userId);
281 if ($metadata[
'HTML'] ===
false)
287 && isset($metadata[
'DATE_EXPIRE'])
288 && $metadata[
'DATE_EXPIRE']->getTimestamp() <= time()
291 $refreshResult = static::refreshMetadata($metadata);
297 $result[$metadata[
'ID']] = $metadata;
305 if (!static::isEnabled())
312 $queryResult = UrlMetadataTable::getList([
319 while ($metadata = $queryResult->fetch())
322 && isset($metadata[
'DATE_EXPIRE'])
323 && $metadata[
'DATE_EXPIRE']->getTimestamp() <= time()
326 $refreshResult = static::refreshMetadata($metadata);
332 $result[$metadata[
'ID']] = $metadata;
348 $id = $metadata[
'ID'];
352 $result = UrlMetadataTable::add(array(
356 $id = $result->getId();
372 $metadata = UrlMetadataTable::getRowById($id);
373 if (!is_array($metadata))
380 $metadata[
'URL'] = static::normalizeUrl($metadata[
'URL']);
381 $metadata = static::fetchUrlMetadata($metadata[
'URL']);
382 if ($metadata ===
false)
384 UrlMetadataTable::delete($id);
388 UrlMetadataTable::update($id, $metadata);
397 if ($preview = static::getDynamicPreview($metadata[
'URL'], $checkAccess, $userId))
399 $metadata[
'HTML'] = $preview;
413 $url = static::normalizeUrl($metadata[
'URL']);
414 $refreshedMetadata = static::fetchUrlMetadata($url);
415 if (!$refreshedMetadata)
421 UrlMetadataTable::update($metadata[
'ID'], $refreshedMetadata);
422 $refreshedMetadata[
'ID'] = $metadata[
'ID'];
424 $metadata = $refreshedMetadata;
439 if ($routeRecord ===
false)
446 $className = $routeRecord[
'CLASS'];
447 $parameters = $routeRecord[
'PARAMETERS'];
448 $parameters[
'URL'] = $url;
452 $userId = static::getCurrentUserId();
455 if ($checkAccess && (!method_exists($className,
'checkUserReadAccess') || $userId == 0 || !$className::checkUserReadAccess($parameters, $userId)))
458 if (method_exists($className,
'buildPreview'))
460 $preview = $className::buildPreview($parameters);
461 return ($preview <>
'' ? $preview :
false);
474 public static function getImAttach($url, $checkAccess =
true, $userId = 0)
476 return self::getUrlInfoFromExternal($url,
'getImAttach', $checkAccess, $userId);
485 public static function getImRich($url, $checkAccess =
true, $userId = 0)
487 return self::getUrlInfoFromExternal($url,
'getImRich', $checkAccess, $userId);
499 if ($routeRecord ===
false)
506 $className = $routeRecord[
'CLASS'];
507 $parameters = $routeRecord[
'PARAMETERS'];
511 $userId = static::getCurrentUserId();
514 return (method_exists($className,
'checkUserReadAccess') && $userId > 0 && $className::checkUserReadAccess($parameters, $userId));
532 if (!is_string($imageUrl) && !is_null($imageUrl))
537 $metadata = UrlMetadataTable::getList(array(
538 'select' => array(
'IMAGE',
'IMAGE_ID',
'EXTRA'),
539 'filter' => array(
'=ID' => $id)
542 if (isset($metadata[
'EXTRA'][
'IMAGES']))
544 $imageIndex = array_search($imageUrl, $metadata[
'EXTRA'][
'IMAGES']);
545 if ($imageIndex ===
false)
547 unset($metadata[
'EXTRA'][
'SELECTED_IMAGE']);
551 $metadata[
'EXTRA'][
'SELECTED_IMAGE'] = $imageIndex;
555 static::fetchImageMetadata($imageUrl, $metadata);
557 return UrlMetadataTable::update($id, $metadata)->isSuccess();
566 static $result =
null;
567 if (is_null($result))
569 $result = Option::get(
'main',
'url_preview_enable',
'N') ===
'Y';
580 public static function sign($id)
583 return $signer->sign((
string)$id, static::SIGN_SALT);
590 if ($routeRecord ===
false)
597 $userId = static::getCurrentUserId();
602 $className = $routeRecord[
'CLASS'];
603 $parameters = $routeRecord[
'PARAMETERS'];
604 $parameters[
'URL'] = $url;
606 if ($checkAccess && (!method_exists($className,
'checkUserReadAccess') || $userId == 0 || !$className::checkUserReadAccess($parameters, $userId)))
609 if (method_exists($className, $method))
611 return $className::$method($parameters);
623 $fullUrl = static::unfoldShortLink($url);
624 $uriParser =
new Uri($fullUrl);
625 if (static::isUrlLocal($uriParser))
637 $metadataRemote = static::getRemoteUrlMetadata($uriParser);
638 if (is_array($metadataRemote) && !empty($metadataRemote))
643 'TITLE' => $metadataRemote[
'TITLE'] ??
'',
644 'DESCRIPTION' => $metadataRemote[
'DESCRIPTION'] ??
'',
645 'IMAGE_ID' => $metadataRemote[
'IMAGE_ID'] ??
null,
646 'IMAGE' => $metadataRemote[
'IMAGE'] ??
null,
647 'EMBED' => $metadataRemote[
'EMBED'] ??
null,
648 'EXTRA' => $metadataRemote[
'EXTRA'] ??
null,
649 'DATE_EXPIRE' => $metadataRemote[
'DATE_EXPIRE'] ??
null,
654 if (isset($metadata[
'TYPE']))
674 $host = \Bitrix\Main\Context::getCurrent()->getRequest()->getHttpHost();
675 return $uri->
getHost() === $host;
685 ->setPrivateIp(
false)
687 ->setStreamTimeout(5)
688 ->setHeader(
'User-Agent', self::USER_AGENT)
691 $httpClient->shouldFetchBody(
function (
Response $response) {
692 $contentType = $response->getHeadersCollection()->getContentType();
698 if (!$httpClient->query(
'GET', $uri->
getUri()))
703 catch (\ErrorException)
708 if ($httpClient->getStatus() !== 200)
713 $peerIpAddress = $httpClient->getPeerAddress();
715 if ($httpClient->getHeaders()->getContentType() !==
'text/html')
717 $metadata = static::getFileMetadata($httpClient);
718 $metadata[
'EXTRA'][
'PEER_IP_ADDRESS'] = $peerIpAddress;
719 $metadata[
'EXTRA'][
'PEER_IP_PRIVATE'] = (
new IpAddress($peerIpAddress))->isPrivate();
724 $html = $httpClient->getResult();
727 $htmlDocument->setEncoding($httpClient->getCharset());
728 ParserChain::extractMetadata($htmlDocument);
729 $metadata = $htmlDocument->getMetadata();
731 if (is_array($metadata) && static::validateRemoteMetadata($metadata))
733 if (isset($metadata[
'IMAGE']))
735 static::fetchImageMetadata($metadata[
'IMAGE'], $metadata);
738 if (isset($metadata[
'DESCRIPTION']) && mb_strlen($metadata[
'DESCRIPTION']) > static::MAX_DESCRIPTION)
740 $metadata[
'DESCRIPTION'] = mb_substr($metadata[
'DESCRIPTION'], 0, static::MAX_DESCRIPTION);
743 if (!isset($metadata[
'EXTRA']) || !is_array($metadata[
'EXTRA']))
745 $metadata[
'EXTRA'] = array();
748 $metadata[
'EXTRA'] = array_merge($metadata[
'EXTRA'], array(
749 'PEER_IP_ADDRESS' => $peerIpAddress,
750 'PEER_IP_PRIVATE' => (
new IpAddress($peerIpAddress))->isPrivate(),
751 'X_FRAME_OPTIONS' => $httpClient->getHeaders()->get(
'X-Frame-Options',
true),
752 'EFFECTIVE_URL' => $httpClient->getEffectiveUrl(),
763 $tempFileName = Random::getString(32) .
'.' . GetFileExtension($fileName);
764 $tempPath = \CFile::GetTempName(
'', $tempFileName);
769 protected static function downloadFile(
string $url, ?
string &$fileName =
null, ?
int $range =
null): ?string
772 ->setPrivateIp(
false)
774 ->setStreamTimeout(5)
775 ->setBodyLengthMax(self::MAX_FILE_SIZE)
780 $httpClient->setHeader(
'Range',
'bytes=0-' . $range);
783 $urlComponents = parse_url($url);
784 $fileName = ($urlComponents && $urlComponents[
"path"] <>
'')
785 ? bx_basename($urlComponents[
"path"])
789 $tempPath = static::getTempPath($fileName);
793 if (!$httpClient->download($url, $tempPath))
798 catch (\ErrorException)
803 if (($name = $httpClient->getHeaders()->getFilename()) !==
null)
816 protected static function saveImage(
string $tempPath, ?
string $fileName)
820 $localFile = \CFile::MakeFileArray($tempPath);
822 if (is_array($localFile))
824 $localFile[
'MODULE_ID'] =
'main';
828 $localFile[
'name'] = $fileName;
830 if (\CFile::CheckImageFile($localFile, 0, 0, 0, array(
"IMAGE")) ===
null)
832 $fileId = \CFile::SaveFile($localFile,
'urlpreview',
true);
836 return ($fileId ===
false ?
null : $fileId);
841 $saveImage = static::getOptionSaveImages();
843 $tempPath = static::downloadFile($imageUrl, $fileName, ($saveImage ?
null : self::FILE_RANGE));
845 if ($tempPath !==
null)
847 $info = (
new Image($tempPath))->getInfo();
850 $metadata[
'EXTRA'][
'IMAGE_INFO'] = [
851 'WIDTH' => $info->getWidth(),
852 'HEIGHT' => $info->getHeight(),
858 $metadata[
'IMAGE_ID'] = static::saveImage($tempPath, $fileName);
859 $metadata[
'IMAGE'] =
null;
863 $metadata[
'IMAGE'] = $imageUrl;
864 $metadata[
'IMAGE_ID'] =
null;
877 if (str_starts_with($url,
'https://') || str_starts_with($url,
'http://'))
881 elseif (str_starts_with($url,
'//'))
885 elseif (str_starts_with($url,
'/'))
891 $url =
'http://'.$url;
894 $parsedUrl =
new Uri($url);
895 $parsedUrl->setHost(mb_strtolower($parsedUrl->getHost()));
897 return $parsedUrl->getUri();
906 static $result =
null;
907 if (is_null($result))
909 $result = Option::get(
'main',
'url_preview_save_images',
'N') ===
'Y';
921 $result = ((isset($metadata[
'TITLE']) && isset($metadata[
'IMAGE'])) || (isset($metadata[
'TITLE']) && isset($metadata[
'DESCRIPTION'])) || isset($metadata[
'EMBED']));
931 return (
$GLOBALS[
'USER'] instanceof \CUser) ? (int)
$GLOBALS[
'USER']->getId() : 0;
942 if (isset($cache[$shortUrl]))
944 return $cache[$shortUrl];
948 if ($shortUri = \CBXShortUri::GetUri($shortUrl))
950 $result = $shortUri[
'URI'];
952 $cache[$shortUrl] = $result;
966 $mimeType = $httpHeaders->getContentType();
967 $filename = $httpHeaders->getFilename() ?: bx_basename($url);
969 if ($mimeType && $filename)
974 'ATTACHMENT' => strtolower($httpHeaders->getContentDisposition()) ===
'attachment' ?
'Y' :
'N',
975 'MIME_TYPE' => $mimeType,
976 'FILENAME' => $filename,
977 'SIZE' => $httpHeaders->get(
'Content-Length')
984 $tempPath = static::getTempPath($filename);
988 $info = (
new Image($tempPath))->getInfo();
991 $result[
'EXTRA'][
'IMAGE_INFO'] = [
992 'WIDTH' => $info->getWidth(),
993 'HEIGHT' => $info->getHeight(),
1008 return (
new IpAddress($ipAddress))->isPrivate();
1020 $domainNameParts = explode(
'.', $uri->
getHost());
1021 if (is_array($domainNameParts) && ($partsCount = count($domainNameParts)) >= 2)
1023 $domainName = $domainNameParts[$partsCount-2] .
'.' . $domainNameParts[$partsCount-1];
1024 $result = isset(static::$trustedHosts[$domainName]);
1037 $url = static::unfoldShortLink($url);
1038 $uri =
new Uri($url);
1039 if (static::isHostTrusted($uri) || static::isEnabled())
1041 $url = static::normalizeUrl($url);
1042 $metadataId = static::reserveIdForUrl($url);
1043 $metadata = static::fetchUrlMetadata($url);
1044 if (is_array($metadata) && !empty($metadata))
1046 $result = UrlMetadataTable::update($metadataId, $metadata);
1047 $metadata[
'ID'] = $result->getId();
1053 if (!empty($metadata[
'EMBED']) && !str_contains($metadata[
'EMBED'],
'<iframe'))
1055 $url = static::getInnerFrameUrl($metadata[
'ID'], $metadata[
'EXTRA'][
'PROVIDER_NAME']);
1056 if (intval($metadata[
'EXTRA'][
'VIDEO_WIDTH']) <= 0)
1058 $metadata[
'EXTRA'][
'VIDEO_WIDTH'] = self::IFRAME_MAX_WIDTH;
1060 if (intval($metadata[
'EXTRA'][
'VIDEO_HEIGHT']) <= 0)
1062 $metadata[
'EXTRA'][
'VIDEO_HEIGHT'] = self::IFRAME_MAX_HEIGHT;
1064 $metadata[
'EMBED'] =
'<iframe src="'.$url.
'" allowfullscreen="" width="'.$metadata[
'EXTRA'][
'VIDEO_WIDTH'].
'" height="'.$metadata[
'EXTRA'][
'VIDEO_HEIGHT'].
'" frameborder="0"></iframe>';
1067 if ($metadata[
'EMBED'] || !empty($metadata[
'EXTRA'][
'VIDEO']))
1087 $componentPath = \CComponentEngine::makeComponentPath(
'bitrix:main.urlpreview');
1088 if (!empty($componentPath))
1090 $componentPath = getLocalPath(
'components'.$componentPath.
'/frame.php');
1091 $uri =
new Uri($componentPath);
1092 $uri->addParams(array(
'id' => $id,
'provider' => $provider));
1093 $result = static::normalizeUrl($uri->getLocator());
static includeModule($moduleName)
static dispatch(Uri $uri)
static getImAttach($url, $checkAccess=true, $userId=0)
static reserveIdForUrl($url)
static getRemoteUrlMetadata(Uri $uri)
static fetchImageMetadata(string $imageUrl, array &$metadata)
static normalizeUrl($url)
static getDynamicPreview($url, $checkAccess=true, $userId=0)
static refreshMetadata(array &$metadata)
static fetchVideoMetaData($url)
static saveImage(string $tempPath, ?string $fileName)
static getOptionSaveImages()
static getUrlInfoFromExternal($url, $method, $checkAccess=true, $userId=0)
static downloadFile(string $url, ?string &$fileName=null, ?int $range=null)
static getMetadataByIds(array $ids)
static getMetadataByUrl($url, $addIfNew=true, $reuseExistingMetadata=true)
static getMetadataAndHtmlByUrl($url, $addIfNew=true, $reuseExistingMetadata=true)
static getImRich($url, $checkAccess=true, $userId=0)
static showView($userField, $userFieldParams, &$cacheTag, $edit=false)
static isHostTrusted(Uri $uri)
static getCurrentUserId()
static checkDynamicPreviewAccess($url, $userId=0)
static getTempPath(string $fileName)
static fetchUrlMetadata($url)
static isIpAddressPrivate($ipAddress)
static getFileMetadata(HttpClient $client)
static getMetadataAndHtmlByIds(array $ids, $checkAccess=true, $userId=0)
static isUrlLocal(Uri $uri)
static showEdit($userField, $userFieldParams)
static resolveTemporaryMetadata($id, $checkAccess=true, $userId=0)
static setMetadataImage($id, $imageUrl)
static getInnerFrameUrl($id, $provider='')
static validateRemoteMetadata(array $metadata)
static unfoldShortLink($shortUrl)
$GLOBALS['____1444769544']