2019-10-18 03:26:15 +02:00
|
|
|
<?php
|
2020-02-09 15:45:36 +01:00
|
|
|
/**
|
2023-01-01 15:36:24 +01:00
|
|
|
* @copyright Copyright (C) 2010-2023, the Friendica project
|
2020-02-09 15:45:36 +01:00
|
|
|
*
|
|
|
|
* @license GNU AGPL version 3 or any later version
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU Affero General Public License as
|
|
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
|
|
* License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU Affero General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
*
|
|
|
|
*/
|
2019-10-18 03:26:15 +02:00
|
|
|
|
|
|
|
namespace Friendica\Util;
|
|
|
|
|
|
|
|
use Friendica\Core\Logger;
|
2019-12-15 22:34:11 +01:00
|
|
|
use Friendica\DI;
|
2021-07-19 08:14:14 +02:00
|
|
|
use Friendica\Model\Photo;
|
2022-04-02 20:26:11 +02:00
|
|
|
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
|
2022-12-04 14:29:21 +01:00
|
|
|
use Friendica\Object\Image;
|
2019-10-18 03:26:15 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Image utilities
|
|
|
|
*/
|
|
|
|
class Images
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* Maps Mime types to Imagick formats
|
|
|
|
*
|
2022-06-21 23:34:14 +02:00
|
|
|
* @return array Format map
|
2019-10-18 03:26:15 +02:00
|
|
|
*/
|
|
|
|
public static function getFormatsMap()
|
|
|
|
{
|
2022-06-21 23:34:14 +02:00
|
|
|
return [
|
2019-10-18 03:26:15 +02:00
|
|
|
'image/jpeg' => 'JPG',
|
2022-06-21 23:34:14 +02:00
|
|
|
'image/jpg' => 'JPG',
|
2019-10-18 03:26:15 +02:00
|
|
|
'image/png' => 'PNG',
|
2022-06-21 23:34:14 +02:00
|
|
|
'image/gif' => 'GIF',
|
2019-10-18 03:26:15 +02:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2022-01-15 22:38:19 +01:00
|
|
|
/**
|
2022-06-21 23:34:14 +02:00
|
|
|
* Return file extension for MIME type
|
|
|
|
*
|
|
|
|
* @param string $mimetype MIME type
|
|
|
|
* @return string File extension for MIME type
|
2022-01-15 22:38:19 +01:00
|
|
|
*/
|
|
|
|
public static function getExtensionByMimeType(string $mimetype): string
|
|
|
|
{
|
|
|
|
switch ($mimetype) {
|
|
|
|
case 'image/png':
|
|
|
|
$imagetype = IMAGETYPE_PNG;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'image/gif':
|
|
|
|
$imagetype = IMAGETYPE_GIF;
|
|
|
|
break;
|
|
|
|
|
2022-06-21 23:34:14 +02:00
|
|
|
case 'image/jpeg':
|
|
|
|
case 'image/jpg':
|
2022-01-15 22:38:19 +01:00
|
|
|
$imagetype = IMAGETYPE_JPEG;
|
|
|
|
break;
|
2022-06-21 23:34:14 +02:00
|
|
|
|
|
|
|
default: // Unknown type must be a blob then
|
|
|
|
return 'blob';
|
|
|
|
break;
|
2022-01-15 22:38:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return image_type_to_extension($imagetype);
|
|
|
|
}
|
|
|
|
|
2019-10-18 03:26:15 +02:00
|
|
|
/**
|
|
|
|
* Returns supported image mimetypes and corresponding file extensions
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
2022-06-21 23:34:14 +02:00
|
|
|
public static function supportedTypes(): array
|
2019-10-18 03:26:15 +02:00
|
|
|
{
|
|
|
|
$types = [
|
2022-06-21 23:34:14 +02:00
|
|
|
'image/jpeg' => 'jpg',
|
|
|
|
'image/jpg' => 'jpg',
|
2019-10-18 03:26:15 +02:00
|
|
|
];
|
2022-06-21 23:34:14 +02:00
|
|
|
|
2019-10-18 03:26:15 +02:00
|
|
|
if (class_exists('Imagick')) {
|
|
|
|
// Imagick::queryFormats won't help us a lot there...
|
|
|
|
// At least, not yet, other parts of friendica uses this array
|
|
|
|
$types += [
|
|
|
|
'image/png' => 'png',
|
|
|
|
'image/gif' => 'gif'
|
|
|
|
];
|
|
|
|
} elseif (imagetypes() & IMG_PNG) {
|
|
|
|
$types += [
|
|
|
|
'image/png' => 'png'
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
return $types;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-04-01 07:42:44 +02:00
|
|
|
* Fetch image mimetype from the image data or guessing from the file name
|
2019-10-18 03:26:15 +02:00
|
|
|
*
|
2020-10-11 23:25:40 +02:00
|
|
|
* @param string $image_data Image data
|
|
|
|
* @param string $filename File name (for guessing the type via the extension)
|
2022-06-21 23:34:14 +02:00
|
|
|
* @param string $default Default MIME type
|
|
|
|
* @return string MIME type
|
2019-10-18 03:26:15 +02:00
|
|
|
* @throws \Exception
|
|
|
|
*/
|
2022-06-21 23:34:14 +02:00
|
|
|
public static function getMimeTypeByData(string $image_data, string $filename = '', string $default = ''): string
|
2019-10-18 03:26:15 +02:00
|
|
|
{
|
2022-06-21 23:34:14 +02:00
|
|
|
if (substr($default, 0, 6) == 'image/') {
|
|
|
|
Logger::info('Using default mime type', ['filename' => $filename, 'mime' => $default]);
|
|
|
|
return $default;
|
2020-04-01 07:42:44 +02:00
|
|
|
}
|
2019-10-18 03:26:15 +02:00
|
|
|
|
2020-04-01 07:42:44 +02:00
|
|
|
$image = @getimagesizefromstring($image_data);
|
|
|
|
if (!empty($image['mime'])) {
|
2022-06-21 23:34:14 +02:00
|
|
|
Logger::info('Mime type detected via data', ['filename' => $filename, 'default' => $default, 'mime' => $image['mime']]);
|
2020-04-01 07:42:44 +02:00
|
|
|
return $image['mime'];
|
|
|
|
}
|
|
|
|
|
|
|
|
return self::guessTypeByExtension($filename);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fetch image mimetype from the image data or guessing from the file name
|
|
|
|
*
|
|
|
|
* @param string $sourcefile Source file of the image
|
|
|
|
* @param string $filename File name (for guessing the type via the extension)
|
2022-06-21 23:34:14 +02:00
|
|
|
* @param string $default default MIME type
|
|
|
|
* @return string MIME type
|
2020-04-01 07:42:44 +02:00
|
|
|
* @throws \Exception
|
|
|
|
*/
|
2022-06-21 23:34:14 +02:00
|
|
|
public static function getMimeTypeBySource(string $sourcefile, string $filename = '', string $default = ''): string
|
2020-04-01 07:42:44 +02:00
|
|
|
{
|
2022-06-22 05:54:25 +02:00
|
|
|
if (substr($default, 0, 6) == 'image/') {
|
|
|
|
Logger::info('Using default mime type', ['filename' => $filename, 'mime' => $default]);
|
|
|
|
return $default;
|
2020-04-01 07:42:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$image = @getimagesize($sourcefile);
|
|
|
|
if (!empty($image['mime'])) {
|
2022-06-22 05:54:25 +02:00
|
|
|
Logger::info('Mime type detected via file', ['filename' => $filename, 'default' => $default, 'image' => $image]);
|
2020-04-01 07:42:44 +02:00
|
|
|
return $image['mime'];
|
2019-10-18 03:26:15 +02:00
|
|
|
}
|
|
|
|
|
2020-04-01 07:42:44 +02:00
|
|
|
return self::guessTypeByExtension($filename);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-06-21 23:34:14 +02:00
|
|
|
* Guess image MIME type from the filename's extension
|
2020-04-01 07:42:44 +02:00
|
|
|
*
|
2022-06-21 23:34:14 +02:00
|
|
|
* @param string $filename Image filename
|
|
|
|
* @return string Guessed MIME type by extension
|
2020-04-01 07:42:44 +02:00
|
|
|
* @throws \Exception
|
|
|
|
*/
|
2022-06-21 23:34:14 +02:00
|
|
|
public static function guessTypeByExtension(string $filename): string
|
2020-04-01 07:42:44 +02:00
|
|
|
{
|
|
|
|
$ext = pathinfo(parse_url($filename, PHP_URL_PATH), PATHINFO_EXTENSION);
|
|
|
|
$types = self::supportedTypes();
|
|
|
|
$type = 'image/jpeg';
|
|
|
|
foreach ($types as $m => $e) {
|
|
|
|
if ($ext == $e) {
|
|
|
|
$type = $m;
|
2019-10-18 03:26:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-01 07:42:44 +02:00
|
|
|
Logger::info('Mime type guessed via extension', ['filename' => $filename, 'type' => $type]);
|
2019-10-18 03:26:15 +02:00
|
|
|
return $type;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-06-21 23:34:14 +02:00
|
|
|
* Gets info array from given URL, cached data has priority
|
|
|
|
*
|
2022-06-23 16:03:55 +02:00
|
|
|
* @param string $url
|
2022-06-21 23:34:14 +02:00
|
|
|
* @return array Info
|
2019-10-18 03:26:15 +02:00
|
|
|
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
|
|
|
*/
|
2022-06-21 23:34:14 +02:00
|
|
|
public static function getInfoFromURLCached(string $url): array
|
2019-10-18 03:26:15 +02:00
|
|
|
{
|
|
|
|
$data = [];
|
|
|
|
|
|
|
|
if (empty($url)) {
|
|
|
|
return $data;
|
|
|
|
}
|
|
|
|
|
2022-02-15 22:10:15 +01:00
|
|
|
$cacheKey = 'getInfoFromURL:' . sha1($url);
|
|
|
|
|
|
|
|
$data = DI::cache()->get($cacheKey);
|
2019-10-18 03:26:15 +02:00
|
|
|
|
|
|
|
if (empty($data) || !is_array($data)) {
|
|
|
|
$data = self::getInfoFromURL($url);
|
|
|
|
|
2022-02-15 22:10:15 +01:00
|
|
|
DI::cache()->set($cacheKey, $data);
|
2019-10-18 03:26:15 +02:00
|
|
|
}
|
|
|
|
|
2022-06-22 06:03:37 +02:00
|
|
|
return $data ?? [];
|
2019-10-18 03:26:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-06-21 23:34:14 +02:00
|
|
|
* Gets info from URL uncached
|
2022-06-22 11:49:54 +02:00
|
|
|
*
|
2022-06-23 16:03:55 +02:00
|
|
|
* @param string $url
|
2022-06-21 23:34:14 +02:00
|
|
|
* @return array Info array
|
2019-10-18 03:26:15 +02:00
|
|
|
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
|
|
|
*/
|
2022-06-21 23:34:14 +02:00
|
|
|
public static function getInfoFromURL(string $url): array
|
2019-10-18 03:26:15 +02:00
|
|
|
{
|
|
|
|
$data = [];
|
|
|
|
|
|
|
|
if (empty($url)) {
|
|
|
|
return $data;
|
|
|
|
}
|
|
|
|
|
2021-07-19 08:14:14 +02:00
|
|
|
if (Network::isLocalLink($url) && ($data = Photo::getResourceData($url))) {
|
2021-07-19 08:19:13 +02:00
|
|
|
$photo = Photo::selectFirst([], ['resource-id' => $data['guid'], 'scale' => $data['scale']]);
|
2021-07-19 08:14:14 +02:00
|
|
|
if (!empty($photo)) {
|
|
|
|
$img_str = Photo::getImageDataForPhoto($photo);
|
|
|
|
}
|
2021-07-19 08:55:23 +02:00
|
|
|
// @todo Possibly add a check for locally stored files
|
2021-07-19 08:14:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (empty($img_str)) {
|
2023-03-18 07:41:35 +01:00
|
|
|
try {
|
|
|
|
$img_str = DI::httpClient()->fetch($url, HttpClientAccept::IMAGE, 4);
|
|
|
|
} catch (\Exception $exception) {
|
|
|
|
Logger::notice('Image is invalid', ['url' => $url, 'exception' => $exception]);
|
|
|
|
return [];
|
|
|
|
}
|
2021-07-19 08:14:14 +02:00
|
|
|
}
|
2019-10-18 03:26:15 +02:00
|
|
|
|
|
|
|
if (!$img_str) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
$filesize = strlen($img_str);
|
|
|
|
|
|
|
|
try {
|
2021-07-05 07:11:35 +02:00
|
|
|
$data = @getimagesizefromstring($img_str);
|
2019-10-18 03:26:15 +02:00
|
|
|
} catch (\Exception $e) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($data) {
|
2022-12-04 14:29:21 +01:00
|
|
|
$image = new Image($img_str);
|
|
|
|
|
2022-12-11 17:48:36 +01:00
|
|
|
if ($image->isValid()) {
|
|
|
|
$data['blurhash'] = $image->getBlurHash();
|
|
|
|
}
|
|
|
|
|
|
|
|
$data['size'] = $filesize;
|
2019-10-18 03:26:15 +02:00
|
|
|
}
|
|
|
|
|
2022-06-22 17:09:20 +02:00
|
|
|
return is_array($data) ? $data : [];
|
2019-10-18 03:26:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-06-21 23:34:14 +02:00
|
|
|
* Returns scaling information
|
|
|
|
*
|
|
|
|
* @param integer $width Width
|
|
|
|
* @param integer $height Height
|
|
|
|
* @param integer $max Max width/height
|
|
|
|
* @return array Scaling dimensions
|
2019-10-18 03:26:15 +02:00
|
|
|
*/
|
2022-06-21 23:34:14 +02:00
|
|
|
public static function getScalingDimensions(int $width, int $height, int $max): array
|
2019-10-18 03:26:15 +02:00
|
|
|
{
|
|
|
|
if ((!$width) || (!$height)) {
|
|
|
|
return ['width' => 0, 'height' => 0];
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($width > $max && $height > $max) {
|
|
|
|
// very tall image (greater than 16:9)
|
|
|
|
// constrain the width - let the height float.
|
|
|
|
|
|
|
|
if ((($height * 9) / 16) > $width) {
|
|
|
|
$dest_width = $max;
|
|
|
|
$dest_height = intval(($height * $max) / $width);
|
|
|
|
} elseif ($width > $height) {
|
|
|
|
// else constrain both dimensions
|
|
|
|
$dest_width = $max;
|
|
|
|
$dest_height = intval(($height * $max) / $width);
|
|
|
|
} else {
|
|
|
|
$dest_width = intval(($width * $max) / $height);
|
|
|
|
$dest_height = $max;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if ($width > $max) {
|
|
|
|
$dest_width = $max;
|
|
|
|
$dest_height = intval(($height * $max) / $width);
|
|
|
|
} else {
|
|
|
|
if ($height > $max) {
|
|
|
|
// very tall image (greater than 16:9)
|
|
|
|
// but width is OK - don't do anything
|
|
|
|
|
|
|
|
if ((($height * 9) / 16) > $width) {
|
|
|
|
$dest_width = $width;
|
|
|
|
$dest_height = $height;
|
|
|
|
} else {
|
|
|
|
$dest_width = intval(($width * $max) / $height);
|
|
|
|
$dest_height = $max;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$dest_width = $width;
|
|
|
|
$dest_height = $height;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ['width' => $dest_width, 'height' => $dest_height];
|
|
|
|
}
|
2023-05-09 22:44:58 +02:00
|
|
|
|
|
|
|
/**
|
2023-05-10 09:54:15 +02:00
|
|
|
* Get a BBCode tag for an local photo page URL with a preview thumbnail and an image description
|
2023-05-09 22:44:58 +02:00
|
|
|
*
|
|
|
|
* @param string $resource_id
|
2023-05-10 09:54:15 +02:00
|
|
|
* @param string $nickname The local user owner of the resource
|
|
|
|
* @param int $preview Preview image size identifier, either 0, 1 or 2 in decreasing order of size
|
|
|
|
* @param string $ext Image file extension
|
2023-05-09 22:44:58 +02:00
|
|
|
* @param string $description
|
|
|
|
* @return string
|
|
|
|
*/
|
2023-05-10 09:54:15 +02:00
|
|
|
public static function getBBCodeByResource(string $resource_id, string $nickname, int $preview, string $ext, string $description = ''): string
|
2023-05-09 22:44:58 +02:00
|
|
|
{
|
2023-05-10 09:54:15 +02:00
|
|
|
return self::getBBCodeByUrl(
|
|
|
|
DI::baseUrl() . '/photos/' . $nickname . '/image/' . $resource_id,
|
|
|
|
DI::baseUrl() . '/photo/' . $resource_id . '-' . $preview. '.' . $ext,
|
|
|
|
$description
|
|
|
|
);
|
2023-05-09 22:44:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-05-10 09:54:15 +02:00
|
|
|
* Get a BBCode tag for an image URL with a preview thumbnail and an image description
|
2023-05-09 22:44:58 +02:00
|
|
|
*
|
2023-05-10 09:54:15 +02:00
|
|
|
* @param string $photo Full image URL
|
|
|
|
* @param string $preview Preview image URL
|
2023-05-09 22:44:58 +02:00
|
|
|
* @param string $description
|
|
|
|
* @return string
|
2023-05-10 10:48:21 +02:00
|
|
|
*/ public static function getBBCodeByUrl(string $photo, string $preview = null, string $description = null): string
|
2023-05-09 22:44:58 +02:00
|
|
|
{
|
2023-05-10 09:54:15 +02:00
|
|
|
if (!empty($preview)) {
|
|
|
|
return '[url=' . $photo . '][img=' . $preview . ']' . $description . '[/img][/url]';
|
|
|
|
}
|
|
|
|
return '[img=' . $photo . ']' . $description . '[/img]';
|
2023-05-09 22:44:58 +02:00
|
|
|
}
|
2019-10-18 03:26:15 +02:00
|
|
|
}
|