Files
ave-cms/system/App/Frontend/Media/ThumbnailUrl.php
T
2026-07-27 12:58:44 +03:00

168 lines
4.8 KiB
PHP

<?php
/*
|--------------------------------------------------------------------------------------
| AVE.cms
|--------------------------------------------------------------------------------------
| @package AVE.cms
| @file system/App/Frontend/Media/ThumbnailUrl.php
| @author AVE.cms <support@ave-cms.ru>
| @copyright 2007-2026 (c) AVE.cms
| @link https://ave-cms.ru
| @version 3.3
*/
namespace App\Frontend\Media;
defined('BASEPATH') || die('Direct access to this location is not allowed.');
use App\Helpers\Dir;
use App\Helpers\File;
use App\Common\OutboundHttpClient;
/** Builds deterministic URLs for lazily generated thumbnails. */
class ThumbnailUrl
{
protected static $sourceLinks = array();
protected static $urls = array();
public static function make(array $params)
{
if (empty($params['link'])) {
return false;
}
$link = self::sourceLink((string) $params['link']);
if ($link === false) {
return false;
}
$size = isset($params['size']) ? (string) $params['size'] : 't128x128';
if (!ThumbnailPresetPolicy::allowed($size)) {
return false;
}
$cacheKey = $link . '|' . $size;
if (array_key_exists($cacheKey, self::$urls)) {
return self::$urls[$cacheKey];
}
$parts = explode('.', basename($link));
$count = count($parts);
if ($count < 2) {
return false;
}
$parts[$count - 2] .= '-' . $size;
$thumbnail = rtrim(str_replace('\\', '/', dirname($link)), '/') . '/' . self::directoryName() . '/' . implode('.', $parts);
ThumbnailStorage::ensureCurrent($link);
self::discardStaleThumbnail($link, $thumbnail);
self::$urls[$cacheKey] = $thumbnail;
return self::$urls[$cacheKey];
}
public static function fromTag(array $match)
{
if (!isset($match[1])) {
return '';
}
if (!isset($match[2]) || trim((string) $match[2]) === '') {
return self::placeholder($match[1]);
}
$url = self::make(array('size' => $match[1], 'link' => $match[2]));
return $url === false ? self::placeholder($match[1]) : (string) $url;
}
protected static function placeholder($size)
{
$url = self::make(array('size' => (string) $size, 'link' => '/uploads/images/noimage.png'));
return $url === false ? '' : (string) $url;
}
public static function validSize($size)
{
return ThumbnailPresetPolicy::valid($size);
}
public static function directoryName()
{
$directory = defined('THUMBNAIL_DIR') ? trim((string) THUMBNAIL_DIR, '/\\') : 'th';
return $directory === '' ? 'th' : basename($directory);
}
protected static function sourceLink($source)
{
$source = html_entity_decode(trim((string) $source), ENT_QUOTES, 'UTF-8');
if (array_key_exists($source, self::$sourceLinks)) {
return self::$sourceLinks[$source];
}
if ($source === '' || strpos($source, "\0") !== false) {
self::$sourceLinks[$source] = false;
return false;
}
$remote = ltrim($source, '/');
if (preg_match('#^https?://#i', $remote)) {
self::$sourceLinks[$source] = self::remoteSource($remote);
return self::$sourceLinks[$source];
}
$path = parse_url($source, PHP_URL_PATH);
$path = '/' . ltrim(str_replace('\\', '/', rawurldecode((string) $path)), '/');
if (strpos($path, '/../') !== false || substr($path, -3) === '/..') {
self::$sourceLinks[$source] = false;
return false;
}
$uploadRoot = '/' . trim((string) UPLOAD_DIR, '/') . '/';
if (strpos($path, $uploadRoot) !== 0 || !is_file(rtrim(BASEPATH, '/') . $path)) {
self::$sourceLinks[$source] = false;
return false;
}
self::$sourceLinks[$source] = $path;
return self::$sourceLinks[$source];
}
protected static function remoteSource($url)
{
try {
$target = OutboundHttpClient::inspectUrl($url, array(
'allowed_schemes' => array('http', 'https'),
'allowed_ports' => array(80, 443),
));
$url = $target['url'];
} catch (\Throwable $e) {
return false;
}
$hash = md5($url);
$relativeDir = '/' . trim((string) UPLOAD_DIR, '/') . '/ext/' . substr($hash, 0, 4);
$absoluteDir = rtrim(BASEPATH, '/') . $relativeDir;
if (!Dir::create($absoluteDir)) {
return false;
}
$link = $relativeDir . '/' . $hash . '.jpg';
$absolute = rtrim(BASEPATH, '/') . $link;
if (!is_file($absolute) && !is_file($absolute . '.tmp') && !File::putAtomic($absolute . '.tmp', $url)) {
return false;
}
return $link;
}
protected static function discardStaleThumbnail($source, $thumbnail)
{
$sourceFile = rtrim(BASEPATH, '/') . '/' . ltrim((string) $source, '/');
$thumbnailFile = rtrim(BASEPATH, '/') . '/' . ltrim((string) $thumbnail, '/');
if (is_file($sourceFile) && is_file($thumbnailFile)
&& (int) filemtime($sourceFile) > (int) filemtime($thumbnailFile)) {
File::delete($thumbnailFile);
}
}
}