Automatic push 4.1.0

This commit is contained in:
chevereto
2024-04-20 15:54:04 +00:00
parent 8117e52ef3
commit c8b0b3939d
91 changed files with 1743 additions and 1154 deletions

View File

@@ -88,7 +88,11 @@ class Album
}
$return = $album_db;
if (isset($return['album_password']) && hasEncryption()) {
$return['album_password'] = decrypt($return['album_password']);
try {
$return['album_password'] = decrypt($return['album_password']);
} catch (Throwable) {
$return['album_password'] = $return['album_password'];
}
}
return $pretty
@@ -114,7 +118,11 @@ class Album
if (hasEncryption()) {
foreach ($db_rows as &$row) {
if (isset($row['album_password'])) {
$row['album_password'] = decrypt($row['album_password']);
try {
$row['album_password'] = decrypt($row['album_password']);
} catch (Throwable) {
$row['album_password'] = $row['album_password'];
}
}
}
}

View File

@@ -81,13 +81,15 @@ class DB extends GDB
string $clause = 'AND',
array $sort = [],
int $limit = null,
int $fetch_style = PDO::FETCH_ASSOC
int $fetch_style = PDO::FETCH_ASSOC,
array $valuesOperators = []
): mixed {
$prefix = self::getFieldPrefix($table);
$values = self::getPrefixedValues($prefix, $values);
$valuesOperators = self::getPrefixedValues($prefix, $valuesOperators);
$sort = self::getPrefixedSort($prefix, $sort);
return GDB::get($table, $values, $clause, $sort, $limit, $fetch_style);
return GDB::get($table, $values, $clause, $sort, $limit, $fetch_style, $valuesOperators);
}
public static function update(

View File

@@ -0,0 +1,76 @@
<?php
/*
* This file is part of Chevereto.
*
* (c) Rodolfo Berrios <rodolfo@chevereto.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Chevereto\Legacy\Classes;
class Fonts
{
private array $handles = [
0 => 'Helvetica, Arial, sans-serif',
1 => '"Times New Roman", Times, serif',
2 => 'Georgia, serif',
3 => 'Tahoma, Verdana, sans-serif',
4 => '"Trebuchet MS", Helvetica, sans-serif',
5 => 'Geneva, Verdana, sans-serif',
6 => '"Courier New", Courier, monospace',
7 => '"Brush Script MT", cursive',
8 => 'Copperplate, Papyrus, fantasy',
];
private array $names = [
0 => 'Helvetica, Arial, sans-serif',
1 => 'Times New Roman, Times, serif',
2 => 'Georgia, serif',
3 => 'Tahoma, Verdana, sans-serif',
4 => 'Trebuchet MS, Helvetica, sans-serif',
5 => 'Geneva, Verdana, sans-serif',
6 => 'Courier New, Courier, monospace',
7 => 'Brush Script MT, cursive',
8 => 'Copperplate, Papyrus, fantasy',
];
private array $get = [];
private array $handlesToId = [];
public function __construct()
{
$this->handlesToId = array_flip($this->handles);
foreach ($this->handles as $id => $handle) {
$this->get[$id] = [$handle, $this->names[$id]];
}
}
public function handlesToId(): array
{
return $this->handlesToId;
}
public function get(): array
{
return $this->get;
}
public function getHandle(int $id): string
{
return $this->get()[$id][0] ?? '';
}
public function getName(int $id): string
{
return $this->get()[$id][1] ?? '';
}
public function getIdForHandle(string $handle): int
{
return $this->handlesToId[$handle] ?? 0;
}
}

View File

@@ -43,7 +43,7 @@ use function Chevereto\Legacy\G\starts_with;
use function Chevereto\Legacy\G\truncate;
use function Chevereto\Legacy\G\unlinkIfExists;
use function Chevereto\Legacy\G\url_to_relative;
use function Chevereto\Legacy\get_image_fileinfo;
use function Chevereto\Legacy\get_fileinfo;
use function Chevereto\Legacy\getSetting;
use function Chevereto\Legacy\system_notification_email;
use function Chevereto\Legacy\time_elapsed_string;
@@ -52,6 +52,8 @@ use function Chevereto\Vars\session;
use function Chevereto\Vars\sessionVar;
use DateTimeZone;
use Exception;
use FFMpeg\Coordinate\TimeCode;
use FFMpeg\FFMpeg;
use Intervention\Image\ImageManagerStatic;
use PHPExif\Exif;
use function Safe\password_hash;
@@ -82,12 +84,15 @@ class Image
'chain',
'thumb_size',
'medium_size',
'frame_size',
'title',
'expiration_date_gmt',
'likes',
'is_animated',
'is_approved',
'is_360',
'duration',
'type'
];
protected static array $expirations = [
@@ -116,7 +121,19 @@ class Image
['year', 1, 31536000],
];
public static array $chain_sizes = ['original', 'image', 'medium', 'thumb'];
public static array $types = [
1 => 'image',
2 => 'video',
3 => 'audio',
];
public static array $chain_sizes = [
'frame', // 2^4
'original', // 2^3
'image', // 2^2
'medium', // 2^1
'thumb', // 2^0
];
public static function getSingle(
int $id,
@@ -285,36 +302,37 @@ class Image
return $slice;
}
public static function getSrcTargetSingle(array $filearray, bool $prefix = true): array
public static function getSrcTargetSingle(array $fileArray, bool $prefix = true): array
{
$prefix = $prefix ? 'image_' : null;
$folder = CHV_PATH_IMAGES;
$pretty = !isset($filearray['image_id']);
$mode = $filearray[$prefix . 'storage_mode'];
$pretty = !isset($fileArray['image_id']);
$mode = $fileArray[$prefix . 'storage_mode'];
$chain_mask = str_split(
(string) str_pad(
decbin((int) ($filearray[$pretty ? 'chain' : 'image_chain'])),
4,
decbin((int) ($fileArray[$pretty ? 'chain' : 'image_chain'])),
5,
'0',
STR_PAD_LEFT
)
);
$chain_to_sufix = [
$chain_to_suffix = [
'image' => '.',
'frame' => '.fr.',
'medium' => '.md.',
'thumb' => '.th.',
'medium' => '.md.'
];
if ($pretty) {
$type = isset($filearray['storage']['id']) ? 'url' : 'path';
$type = isset($fileArray['storage']['id']) ? 'url' : 'path';
} else {
$type = isset($filearray['storage_id']) ? 'url' : 'path';
$type = isset($fileArray['storage_id']) ? 'url' : 'path';
}
if ($type == 'url') { // URL resource folder
$folder = add_ending_slash($pretty ? $filearray['storage']['url'] : $filearray['storage_url']);
if ($type == 'url') {
$folder = add_ending_slash($pretty ? $fileArray['storage']['url'] : $fileArray['storage_url']);
}
switch ($mode) {
case 'datefolder':
$datetime = $filearray[$prefix . 'date'];
$datetime = $fileArray[$prefix . 'date'];
$datefolder = preg_replace('/(.*)(\s.*)/', '$1', str_replace('-', '/', $datetime));
$folder .= add_ending_slash($datefolder); // Y/m/d/
@@ -327,20 +345,25 @@ class Image
// use direct $folder
break;
case 'path':
$folder = add_ending_slash($filearray['path']);
$folder = add_ending_slash($fileArray['path']);
break;
}
$targets = [
'type' => $type,
'chain' => [
'frame' => null,
'image' => null,
'thumb' => null,
'medium' => null
]
];
foreach (array_keys($targets['chain']) as $k) {
$targets['chain'][$k] = $folder . $filearray[$prefix . 'name'] . $chain_to_sufix[$k] . $filearray[$prefix . 'extension'];
$extension = $fileArray[$prefix . 'extension'];
if ($k !== 'image' && in_array($extension, ['mp4', 'webm'])) {
$extension = 'jpeg';
}
$targets['chain'][$k] = $folder . $fileArray[$prefix . 'name'] . $chain_to_suffix[$k] . $extension;
}
if ($type == 'path') {
foreach ($targets['chain'] as $k => $v) {
@@ -557,7 +580,7 @@ class Image
// Mostly for people uploading two times the same image to test or just bug you
// $mixed => $_FILES or md5 string
public static function isDuplicatedUpload(array|string $source, string $time_frame = 'P1D'): bool
public static function isDuplicatedUpload(array|string $source, string $timePeriod = 'P1D'): bool
{
if (is_array($source) && isset($source['tmp_name'])) {
$filename = $source['tmp_name'];
@@ -576,7 +599,7 @@ class Image
$db->query('SELECT * FROM ' . DB::getTable('images') . ' WHERE (image_md5=:md5 OR image_source_md5=:md5) AND image_uploader_ip=:ip AND image_date_gmt > :date_gmt');
$db->bind(':md5', $md5_file);
$db->bind(':ip', get_client_ip());
$db->bind(':date_gmt', datetime_sub(datetimegmt(), $time_frame));
$db->bind(':date_gmt', datetime_sub(datetimegmt(), $timePeriod));
$db->exec();
return (bool) $db->fetchColumn();
@@ -591,7 +614,7 @@ class Image
): array {
$params['use_file_date'] = $params['use_file_date'] ?? false;
nullify_string($params['album_id']);
$datefolder = '';
$dateFolder = '';
try {
if ($user !== []
@@ -605,8 +628,20 @@ class Image
throw new Exception(_s('Duplicated upload'), 101);
}
$storage_id = null;
$upload_types = [
'image' => 1,
'video' => 2,
// 'audio' => 4,
// 'document' => 8,
// 'other' => 16,
];
$mimetype = strtok($params['mimetype'], '/');
$type_chain = $upload_types[$mimetype] ?? 1;
$get_active_storages = env()['CHEVERETO_ENABLE_EXTERNAL_STORAGE']
? Storage::get(['is_active' => 1])
? Storage::get([
'is_active' => 1,
'type_chain' => $type_chain
])
: [];
if ($get_active_storages !== []) {
if (count($get_active_storages) > 1) {
@@ -659,14 +694,14 @@ class Image
'date' => $stockDate,
'date_gmt' => $stockDateGmt,
];
$datefolder = date('Y/m/d/', strtotime($datefolder_stock['date']));
$upload_path = CHV_PATH_IMAGES . $datefolder;
$dateFolder = date('Y/m/d/', strtotime($datefolder_stock['date']));
$upload_path = CHV_PATH_IMAGES . $dateFolder;
break;
}
$filenaming = getSetting('upload_filenaming');
if ($filenaming !== 'id' && in_array($params['privacy'] ?? '', ['password', 'private', 'private_but_link'])) {
$filenaming = 'random';
$fileNaming = getSetting('upload_filenaming');
if ($fileNaming !== 'id' && in_array($params['privacy'] ?? '', ['password', 'private', 'private_but_link'])) {
$fileNaming = 'random';
}
$upload_options = [
'max_size' => get_bytes(getSetting('upload_max_filesize_mb') . ' MB'),
@@ -674,7 +709,7 @@ class Image
? $user['image_keep_exif']
: getSetting('upload_image_exif'),
];
if ($filenaming == 'id') {
if ($fileNaming == 'id') {
try {
$dummy = [
'name' => '',
@@ -691,27 +726,29 @@ class Image
'chain' => 0,
'thumb_size' => 0,
'medium_size' => 0,
'frame_size' => 0,
'duration' => 0,
];
$dummy_insert = DB::insert('images', $dummy);
DB::delete('images', ['id' => $dummy_insert]);
$target_id = $dummy_insert;
} catch (Throwable $e) {
$filenaming = 'original';
$fileNaming = 'original';
}
}
$upload_options['filenaming'] = $filenaming;
$upload_options['allowed_formats'] = self::getEnabledImageFormats();
$upload_options['filenaming'] = $fileNaming;
$upload_options['allowed_formats'] = self::getEnabledImageExtensions();
$image_upload = self::upload(
$source,
$upload_path,
($filenaming == 'id' && isset($target_id))
($fileNaming == 'id' && isset($target_id))
? encodeID((int) $target_id)
: null,
$upload_options,
$storage_id,
$guestSessionHandle
);
$chain_mask = [0, 1, 0, 1]; // original image medium thumb
$chain_mask = [0, 0, 1, 0, 1]; // frame, original, image, medium, thumb
if ($do_dupe_check && self::isDuplicatedUpload($image_upload['uploaded']['fileinfo']['md5'])) {
throw new Exception(_s('Duplicated upload'), 102);
}
@@ -751,6 +788,14 @@ class Image
if (is_animated_image($image_upload['uploaded']['file'])) {
$must_resize = false;
}
$resizeSourceImage = $image_upload['uploaded']['file'];
$uploadDir = dirname($resizeSourceImage);
if ($image_upload['source']['type'] === 'video') {
$frameImage = $uploadDir . '/' . $image_upload['uploaded']['name'] . '.fr.jpeg';
rename($image_upload['uploaded']['frame'], $frameImage);
$resizeSourceImage = $frameImage;
$chain_mask[0] = 1;
}
if ($must_resize) {
$source_md5 = $image_upload['uploaded']['fileinfo']['md5'];
if ($do_dupe_check && self::isDuplicatedUpload($source_md5)) {
@@ -766,8 +811,8 @@ class Image
$image_resize_options = ['width' => $params['width']];
}
$image_upload['uploaded'] = self::resize(
$image_upload['uploaded']['file'],
dirname($image_upload['uploaded']['file']),
$resizeSourceImage,
dirname($resizeSourceImage),
null,
$image_resize_options
);
@@ -784,8 +829,8 @@ class Image
$medium_fixed_dimension = getSetting('upload_medium_fixed_dimension');
$is_animated_image = is_animated_image($image_upload['uploaded']['file']);
$image_thumb = self::resize(
source: $image_upload['uploaded']['file'],
destination: dirname($image_upload['uploaded']['file']),
source: $resizeSourceImage,
destination: $uploadDir,
filename: $image_upload['uploaded']['name'] . '.th',
options: $image_thumb_options
);
@@ -814,8 +859,8 @@ class Image
$apply_watermark = false;
}
}
if ($apply_watermark && self::watermark($image_upload['uploaded']['file'])) {
$image_upload['uploaded']['fileinfo'] = GGet_image_fileinfo($image_upload['uploaded']['file']); // Remake the fileinfo array, new full array file info (todo: faster!)
if ($apply_watermark && self::watermark($resizeSourceImage)) {
$image_upload['uploaded']['fileinfo'] = GGet_image_fileinfo($resizeSourceImage); // Remake the fileinfo array, new full array file info (todo: faster!)
$image_upload['uploaded']['fileinfo']['md5'] = $original_md5; // Preserve original MD5 for watermarked images
}
if ($image_upload['uploaded']['fileinfo'][$medium_fixed_dimension] > $medium_size || $is_animated_image) {
@@ -826,12 +871,12 @@ class Image
$image_medium_options[$medium_fixed_dimension] = min($image_medium_options[$medium_fixed_dimension], $image_upload['uploaded']['fileinfo'][$medium_fixed_dimension]);
}
$image_medium = self::resize(
$image_upload['uploaded']['file'],
dirname($image_upload['uploaded']['file']),
$resizeSourceImage,
$uploadDir,
$image_upload['uploaded']['name'] . '.md',
$image_medium_options
);
$chain_mask[2] = 1;
$chain_mask[3] = 1;
}
$chain_value = bindec((string) implode('', $chain_mask));
$disk_space_needed = $image_upload['uploaded']['fileinfo']['size'];
@@ -894,8 +939,9 @@ class Image
if (!($image_medium ?? false)) {
unset($chain_props['medium']);
}
$dirChain = dirname($image_upload['uploaded']['file']);
foreach ($chain_props as $k => $v) {
$chain_file = add_ending_slash(dirname($image_upload['uploaded']['file'])) . $image_upload['uploaded']['name'] . '.' . $v['suffix'] . '.' . ${"image_$k"}['fileinfo']['extension'];
$chain_file = add_ending_slash($dirChain) . $image_upload['uploaded']['name'] . '.' . $v['suffix'] . '.' . ${"image_$k"}['fileinfo']['extension'];
try {
$renamed_chain = rename(${"image_$k"}['file'], $chain_file);
@@ -926,9 +972,11 @@ class Image
'chain' => $chain_value,
'thumb_size' => $image_thumb['fileinfo']['size'] ?? 0,
'medium_size' => $image_medium['fileinfo']['size'] ?? 0,
'frame_size' => $image_upload['uploaded']['frameinfo']['size'] ?? 0,
'is_animated' => $is_animated_image,
'source_md5' => $source_md5 ?? null,
'is_360' => $is_360
'is_360' => $is_360,
'duration' => $image_upload['uploaded']['fileinfo']['duration'] ?? 0,
];
if (isset($datefolder_stock)) {
foreach ($datefolder_stock as $k => $v) {
@@ -964,6 +1012,14 @@ class Image
case 'image':
$prop = $image_upload['uploaded'];
break;
case 'frame':
$prop = [
'file' => $frameImage,
'filename' => basename($frameImage),
'fileinfo' => $image_upload['uploaded']['frameinfo']
];
break;
default:
$prop = ${"image_$v"};
@@ -978,7 +1034,7 @@ class Image
}
Storage::uploadFiles($toStorage, $storage, [
'keyprefix' => $storage_mode == 'datefolder'
? $datefolder
? $dateFolder
: null
]);
}
@@ -1009,7 +1065,7 @@ class Image
}
}
$image_insert_values['title'] = $image_title;
if ($filenaming == 'id' && isset($target_id)) { // Insert as a reserved ID
if ($fileNaming == 'id' && isset($target_id)) { // Insert as a reserved ID
$image_insert_values['id'] = $target_id;
}
$image_insert_values['title'] = mb_substr($image_insert_values['title'] ?? '', 0, 100, 'UTF-8');
@@ -1028,7 +1084,7 @@ class Image
DB::insert('images_hash', ['image_id' => $uploaded_id, 'hash' => $deleteHash]);
if (isset($toStorage)) {
foreach ($toStorage as $k => $v) {
unlinkIfExists($v['file']); // Remove the source image
unlinkIfExists($v['file']); // Remove files from local when doing external storage
}
}
$privacyTargets = ['private', 'private_but_link'];
@@ -1085,16 +1141,29 @@ class Image
}
}
public static function getEnabledImageFormats(): array
public static function getEnabledImageExtensions(): array
{
$formats = explode(',', Settings::get('upload_enabled_image_formats'));
if (in_array('jpg', $formats)) {
if (in_array('jpg', $formats) && !in_array('jpeg', $formats)) {
$formats[] = 'jpeg';
}
return $formats;
}
public static function getEnabledImageAcceptAttribute(): string
{
$extensions = self::getEnabledImageExtensions();
$accept = [];
$videos = ['mp4', 'webm'];
foreach ($extensions as $extension) {
$type = in_array($extension, $videos) ? 'video' : 'image';
$accept[] = "$type/$extension";
}
return implode(',', $accept);
}
public static function resize(
string $source,
?string $destination,
@@ -1210,7 +1279,10 @@ class Image
$values['is_approved'] = 1;
}
$insert = DB::insert('images', $values);
$disk_space_used = $values['size'] + $values['thumb_size'] + $values['medium_size'];
$disk_space_used = $values['size']
+ $values['thumb_size']
+ $values['medium_size']
+ $values['frame_size'];
Stat::track([
'action' => 'insert',
'table' => 'images',
@@ -1255,7 +1327,10 @@ class Image
public static function delete(int $id, bool $update_user = true): int
{
$image = self::getSingle(id: $id, pretty: true);
$disk_space_used = $image['size'] + ($image['thumb']['size'] ?? 0) + ($image['medium']['size'] ?? 0);
$disk_space_used = $image['size']
+ $image['thumb_size']
+ $image['medium_size']
+ $image['frame_size'];
if ($image['file_resource']['type'] == 'path') {
foreach ($image['file_resource']['chain'] as $file_delete) {
if (file_exists($file_delete) && !unlinkIfExists($file_delete)) {
@@ -1375,60 +1450,13 @@ class Image
$medium_size = getSetting('upload_medium_size');
$medium_fixed_dimension = getSetting('upload_medium_fixed_dimension');
if ($targets['type'] == 'path') {
if ($image['size'] == 0) {
$get_image_fileinfo = GGet_image_fileinfo($targets['chain']['image']);
$update_missing_values = [
'width' => $get_image_fileinfo['width'],
'height' => $get_image_fileinfo['height'],
'size' => $get_image_fileinfo['size'],
];
foreach (['thumb', 'medium'] as $k) {
if (!array_key_exists($k, $targets['chain'])) {
continue;
}
if ($image[$k . '_size'] == 0) {
$update_missing_values[$k . '_size'] = GGet_image_fileinfo($targets['chain'][$k])['size'];
}
}
self::update($image['id'], $update_missing_values);
$image = array_merge($image, $update_missing_values);
}
$is_animated = isset($targets['chain']['image']) && is_animated_image($targets['chain']['image']);
if (count($targets['chain']) > 0 && !isset($targets['chain']['thumb'])) {
try {
$targets['chain']['thumb'] = self::resize(
$targets['chain']['image'],
pathinfo($targets['chain']['image'], PATHINFO_DIRNAME),
$image['name'] . '.th',
[
'width' => getSetting('upload_thumb_width'),
'height' => getSetting('upload_thumb_height'),
'forced' => $image['extension'] == 'gif' && $is_animated
]
)['file'];
} catch (Exception $e) {
}
}
if ($image[$medium_fixed_dimension] > $medium_size
&& count($targets['chain']) > 0
&& !isset($targets['chain']['medium'])
) {
try {
$targets['chain']['medium'] = self::resize(
$targets['chain']['image'],
pathinfo($targets['chain']['image'], PATHINFO_DIRNAME),
$image['name'] . '.md',
[
$medium_fixed_dimension => $medium_size,
'forced' => $image['extension'] == 'gif' && $is_animated
]
)['file'];
} catch (Throwable $e) {
}
$is_animated = $image['is_animated'];
if (!$is_animated) {
$is_animated = isset($targets['chain']['image']) && is_animated_image($targets['chain']['image']);
}
if (count($targets['chain']) > 0) {
$original_md5 = $image['md5'];
$image = array_merge($image, get_image_fileinfo($targets['chain']['image']));
$image = array_merge($image, get_fileinfo($targets['chain']['image']));
$image['md5'] = $original_md5;
}
if ($is_animated && !$image['is_animated']) {
@@ -1441,8 +1469,10 @@ class Image
'size' => (int) $image['size'],
'size_formatted' => format_bytes($image['size'])
];
$image = array_merge($image, get_image_fileinfo($targets['chain']['image']), $image_fileinfo);
$image = array_merge($image, get_fileinfo($targets['chain']['image']), $image_fileinfo);
}
$image['file_resource'] = $targets;
$image['url_viewer'] = self::getUrlViewer(
$image['id_encoded'],
@@ -1454,14 +1484,17 @@ class Image
$image['url_short'] = self::getUrlViewer($image['id_encoded']);
foreach ($targets['chain'] as $k => $v) {
if ($targets['type'] == 'path') {
$image[$k] = file_exists($v) ? get_image_fileinfo($v) : null;
$image[$k] = file_exists($v) ? get_fileinfo($v) : null;
} else {
$image[$k] = get_image_fileinfo($v);
$image[$k] = get_fileinfo($v);
}
$image[$k]['size'] = $image[($k == 'image' ? '' : $k . '_') . 'size'];
}
$image['url_frame'] = $image['frame']['url'] ?? '';
$image['size_formatted'] = format_bytes($image['size']);
$display_url = $image['url'] ?? '';
$display_url = $image['frame']['url']
?? $image['url']
?? '';
$display_width = $image['width'];
$display_height = $image['height'];
if (!empty($image['medium'])) {
@@ -1479,15 +1512,24 @@ class Image
break;
}
// if (!$image["is_animated"]) {
// // $display_url = $image['url'] ?? '';
// }
} elseif ($image['size'] > get_bytes('200 KB')) {
} elseif (
$image['size'] > get_bytes('200 KB')
&& $image['type'] === 1
) {
$display_url = $image['thumb']['url'] ?? '';
$display_width = getSetting('upload_thumb_width');
$display_height = getSetting('upload_thumb_height');
}
$image['duration'] = (int) ($image['duration'] ?? 0);
$seconds = $image['duration'] ?? 0;
if ($seconds > 0) {
$minutes = floor($seconds / 60);
$duration_time = sprintf('%02d', $minutes) . ':' . sprintf('%02d', $seconds % 60);
} else {
$duration_time = '';
}
$image['duration_time'] = $duration_time;
$image['type'] = self::$types[$image['type']];
$image['display_url'] = $display_url;
$image['display_width'] = $display_width;
$image['display_height'] = $display_height;
@@ -1500,6 +1542,8 @@ class Image
$image['title_truncated'] = truncate($image['title'] ?? '', 28);
$image['title_truncated_html'] = safe_html($image['title_truncated']);
$image['is_use_loader'] = getSetting('image_load_max_filesize_mb') !== '' ? ($image['size'] > get_bytes(getSetting('image_load_max_filesize_mb') . 'MB')) : false;
$image['display_title'] = $image['title']
?? ($image['name'] . '.' . $image['extension']);
}
public static function formatArray(array $dbRow, bool $safe = false): array
@@ -1530,4 +1574,16 @@ class Image
return $output;
}
public static function getVideoFrame(string $file, int $time): string
{
$frameFile = Upload::getTempNam(sys_get_temp_dir());
$ffmpeg = FFMpeg::create();
$video = $ffmpeg->open($file);
$video
->frame(TimeCode::fromSeconds($time))
->save($frameFile);
return $frameFile;
}
}

View File

@@ -328,6 +328,10 @@ class Listing
}
if ($this->type == 'images' && isset($this->params_hidden['is_animated']) && $this->params_hidden['is_animated'] == 1) {
$whereClauses[] = 'image_is_animated = 1';
$whereClauses[] = 'image_type = 1';
}
if ($this->type == 'images' && isset($this->params_hidden['is_video']) && $this->params_hidden['is_video'] == 1) {
$whereClauses[] = 'image_type = 2';
}
if (!empty($whereClauses)) {
$whereClauses = implode(' AND ', $whereClauses);
@@ -646,7 +650,7 @@ class Listing
'sort' => 'date_desc',
],
'trending' => [
'icon' => 'fas fa-poll',
'icon' => 'fas fa-chart-simple',
'label' => _s('Trending'),
'content' => 'all',
'sort' => 'views_desc',
@@ -736,8 +740,8 @@ class Listing
'content' => 'users',
],
'images' => [
'icon' => 'fas fa-image',
'label' => _s('Images'),
'icon' => 'fas fa-photo-film',
'label' => _n('File', 'Files', 20),
'content' => 'images',
],
'albums' => [
@@ -763,11 +767,11 @@ class Listing
$contents = [
'images' => [
'icon' => $listings['images']['icon'],
'label' => _s('Images'),
'label' => $listings['images']['label'],
],
'albums' => [
'icon' => $listings['albums']['icon'],
'label' => _n('Album', 'Albums', 20),
'label' => $listings['albums']['label'],
],
];
if ((bool) env()['CHEVERETO_ENABLE_USERS']) {

View File

@@ -176,7 +176,7 @@ class Settings
'enable_powered_by' => true,
'akismet' => false,
'stopforumspam' => false,
'upload_enabled_image_formats' => 'jpg,png,bmp,gif,webp',
'upload_enabled_image_formats' => 'jpg,png,bmp,gif,webp,mp4,webm',
'hostname' => null,
'theme_show_embed_content_for' => 'all',
'moderatecontent' => false,
@@ -249,7 +249,7 @@ class Settings
'listing_safe_count' => 100,
'image_title_max_length' => 100,
'album_name_max_length' => 100,
'upload_available_image_formats' => 'jpg,jpeg,png,bmp,gif,webp',
'upload_available_image_formats' => 'jpg,jpeg,png,bmp,gif,webp,mp4,webm',
]);
if (!array_key_exists('active_storage', $settings)) {
$settings['active_storage'] = null;

View File

@@ -26,6 +26,7 @@ use function Chevereto\Legacy\G\get_file_extension;
use function Chevereto\Legacy\G\get_filename;
use function Chevereto\Legacy\G\get_image_fileinfo;
use function Chevereto\Legacy\G\get_public_url;
use function Chevereto\Legacy\G\get_video_fileinfo;
use function Chevereto\Legacy\G\is_animated_webp;
use function Chevereto\Legacy\G\is_image_url;
use function Chevereto\Legacy\G\is_url;
@@ -90,6 +91,8 @@ class Upload
'ftp'
];
public string $mediaType = 'image';
public function uploaded(): array
{
return $this->uploaded;
@@ -256,7 +259,9 @@ class Upload
* External storage will be allocated to the temp directory
*/
if (isset($this->storage_id)) {
$this->uploaded_file = forward_slash(dirname($this->downstream)) . '/' . Storage::getStorageValidFilename($this->fixed_filename, $this->storage_id, $this->options['filenaming'], $this->destination);
$this->uploaded_file = forward_slash(dirname($this->downstream))
. '/'
. Storage::getStorageValidFilename($this->fixed_filename, $this->storage_id, $this->options['filenaming'], $this->destination);
} else {
$this->uploaded_file = name_unique_file($this->destination, $this->fixed_filename, $this->options['filenaming']);
}
@@ -265,7 +270,8 @@ class Upload
'filename' => $this->source_filename, // file.ext
'name' => $this->source_name, // file
'image_exif' => $this->source_image_exif,
'fileinfo' => get_image_fileinfo($this->downstream),
'type' => $this->mediaType,
'fileinfo' => $this->source_image_fileinfo,
];
if (stream_resolve_include_path($this->downstream) == false) {
throw new Exception('Concurrency: Downstream gone, aborting operation', 666);
@@ -291,16 +297,28 @@ class Upload
} catch (Throwable $e) {
}
}
$fileInfo = get_image_fileinfo($this->uploaded_file);
$fileInfo = $this->mediaType === 'video'
? get_video_fileinfo($this->uploaded_file)
: get_image_fileinfo($this->uploaded_file);
if ($fileInfo === []) {
throw new Exception("Can't get uploaded info", 610);
}
$fileInfo['is_360'] = $is_360;
$frameFile = null;
if ($this->mediaType === 'video') {
$frameFile = Image::getVideoFrame(
$this->uploaded_file,
(int) ($fileInfo['duration'] / 4)
);
}
$this->uploaded = [
'file' => $this->uploaded_file,
'filename' => get_filename($this->uploaded_file),
'name' => get_basename_without_extension($this->uploaded_file),
'type' => $this->mediaType,
'fileinfo' => $fileInfo,
'frame' => $frameFile,
'frameinfo' => $frameFile ? get_image_fileinfo($frameFile) : [],
];
}
@@ -311,9 +329,16 @@ class Upload
return explode(',', $formats);
}
public static function getEnabledImageFormats(): array
public static function getAvailableTypes(): array
{
return Image::getEnabledImageFormats();
// 0: all
return [
'image', // 2^0
'video', // 2^1
// 'audio', // 2^2
// 'document', // 2^3
// 'other' // 2^4
];
}
/**
@@ -395,7 +420,7 @@ class Upload
throw new Exception(sprintf('Unwanted extension for %s', $filename), 600);
}
$extension = get_file_extension($filename);
if (!in_array($extension, self::getEnabledImageFormats())) {
if (!in_array($extension, Image::getEnabledImageExtensions())) {
throw new Exception(sprintf('Unable to handle upload for %s', $filename), 600);
}
}
@@ -463,7 +488,10 @@ class Upload
if (!file_exists($this->downstream)) {
throw new Exception("Can't fetch target upload source (downstream)", 600);
}
$this->source_image_fileinfo = get_image_fileinfo($this->downstream);
$this->mediaType = str_starts_with($this->source['type'], 'video/') ? 'video' : 'image';
$this->source_image_fileinfo = $this->mediaType === 'video'
? get_video_fileinfo($this->downstream)
: get_image_fileinfo($this->downstream);
if ($this->source_image_fileinfo === []) {
throw new Exception("Can't get target upload source info", 610);
}
@@ -476,8 +504,8 @@ class Upload
if (!in_array($this->source_image_fileinfo['extension'], $this->options['allowed_formats'])) {
throw new Exception(sprintf('Disabled image format (%s)', $this->source_image_fileinfo['extension']), 614);
}
if (!$this->isValidImageMime($this->source_image_fileinfo['mime'])) {
throw new Exception('Invalid image mimetype', 612);
if (!$this->isValidMime($this->source_image_fileinfo['mime'])) {
throw new Exception('Invalid mimetype', 612);
}
if (!$this->options['max_size']) {
$this->options['max_size'] = self::getDefaultOptions()['max_size'];
@@ -497,6 +525,10 @@ class Upload
throw new Exception('Animated WebP is not supported', 400);
}
if ($this->mediaType === 'video') {
return;
}
if (Settings::get('arachnid')) {
$arachnid = new Arachnid(
authorization: Settings::get('arachnid_key'),
@@ -594,11 +626,29 @@ class Upload
return [];
}
protected function isValidMime(string $mime): bool
{
if (str_starts_with($mime, 'video/')) {
return $this->isValidVideoMime($mime);
}
return $this->isValidImageMime($mime);
}
protected function isValidImageMime(string $mime): bool
{
if (str_starts_with($mime, 'video/')) {
return $this->isValidVideoMime($mime);
}
return preg_match("#image\/(gif|pjpeg|jpeg|png|x-png|bmp|x-ms-bmp|x-windows-bmp|webp)$#", $mime) === 1;
}
protected function isValidVideoMime(string $mime): bool
{
return preg_match("#video\/(mp4|webm)$#", $mime) === 1;
}
protected function isValidNamingOption(string $string): bool
{
return in_array($string, ['mixed', 'random', 'original']);

View File

@@ -189,7 +189,7 @@ class User
public static function getStreamName(string $username): string
{
return _s("%t by %s", ['%t' => _s('Images'), '%s' => $username]);
return _s("%t by %s", ['%t' => _s('Media'), '%s' => $username]);
}
public static function getUrl(array|string $handle)

View File

@@ -258,7 +258,8 @@ class DB
string $clause = 'AND',
array $sort = [],
int $limit = null,
int $fetch_style = PDO::FETCH_ASSOC
int $fetch_style = PDO::FETCH_ASSOC,
array $valuesOperators = []
): mixed {
if (!is_array($values) && $values !== 'all') {
throw new Exception('Expecting array values, ' . gettype($values) . ' given');
@@ -279,7 +280,8 @@ class DB
if (is_null($v)) {
$query .= '`' . $k . '` IS :' . $k . ' ' . $clause . ' ';
} else {
$query .= '`' . $k . '`=:' . $k . ' ' . $clause . ' ';
$operator = $valuesOperators[$k] ?? '=';
$query .= '`' . $k . '`' . $operator . ':' . $k . ' ' . $clause . ' ';
}
}
}

View File

@@ -19,6 +19,7 @@ use CurlHandle;
use DateInterval;
use ErrorException;
use Exception;
use FFMpeg\FFProbe;
use GdImage;
use LogicException;
use function Safe\curl_exec;
@@ -1933,6 +1934,8 @@ function mime_to_extension(string $mime): string
'image/x-icon' => 'ico',
'image/vnd.microsoft.icon' => 'ico',
'image/webp' => 'webp',
'video/mp4' => 'mp4',
'video/webm' => 'webm',
][$mime] ?? '';
}
@@ -1947,9 +1950,50 @@ function extension_to_mime(string $ext): string
'tiff' => 'image/tiff',
'ico' => 'image/vnd.microsoft.icon',
'webp' => 'image/webp',
'mp4' => 'video/mp4',
'webm' => 'video/webm',
][$ext] ?? '';
}
function get_video_fileinfo(string $file): array
{
clearstatcache(true, $file);
$ffprobe = FFProbe::create();
if (!$ffprobe->isValid($file)) {
throw new Exception("Invalid video file provided", 610);
}
$all = $ffprobe
->streams($file)
->videos()
->first()
->all();
$codecLong = strtolower($all['codec_long_name'] ?? '');
$extension = str_contains($codecLong, 'mpeg-4') ? 'mp4' : 'webm';
$filesize = filesize($file);
$duration = $all['duration'] ?? null;
if ($duration === null) {
$format = $ffprobe->format($file)->all();
$duration = $format['duration'] ?? null;
}
return [
'filename' => basename($file),
'name' => basename($file, '.' . $extension),
'width' => $all['width'],
'height' => $all['height'],
'ratio' => $all['width'] / $all['height'],
'size' => intval($filesize),
'size_formatted' => format_bytes($filesize),
'mime' => 'video/' . $extension,
'extension' => $extension,
'bits' => $all['bits_per_raw_sample'] ?? 0,
'channels' => '',
'url' => absolute_to_url($file),
'md5' => md5_file($file),
'duration' => (int) $duration,
];
}
function get_image_fileinfo(string $file): array
{
clearstatcache(true, $file);

View File

@@ -93,14 +93,16 @@ function theme_file_exists($var)
function get_html_tags()
{
$palette = Handler::var('theme_palette_handle');
$font = Handler::var('theme_font');
$device = 'device-' . (Handler::cond('mobile_device') ? 'mobile' : 'nonmobile');
$nsfwBlur = 'unsafe-blur-' . (getSetting('theme_nsfw_blur') ? 'on' : 'off');
$classes = strtr(
'%device %palette %nsfwBlur',
'%device %palette %nsfwBlur %font',
[
'%device' => $device,
'%palette' => 'palette-' . $palette,
'%nsfwBlur' => $nsfwBlur,
'%font' => 'font-' . $font,
]
);
if (getSetting('captcha')) {
@@ -168,7 +170,7 @@ function get_captcha_invisible_html()
.then(function(token) {
fetch(recaptchaLocal + "/?action=" + recaptchaAction + "&token="+token).then(function(response) {
response.json().then(function(data) {
console.log(data);
// console.log(data);
});
});
});
@@ -514,14 +516,18 @@ function get_peafowl_item_list($item, $template, $tools, $tpl = 'image', array $
$show_admin_tools = true;
}
}
if (($item['duration_time'] ?? '') === '') {
$template['tpl_list_item/item_duration_time'] = null;
}
$stock_tpl_lower = strtolower($stock_tpl);
if (!$show_item_public_tools) {
$template['tpl_list_item/item_' . strtolower($stock_tpl) . '_public_tools'] = null;
$template['tpl_list_item/item_' . $stock_tpl_lower . '_public_tools'] = null;
}
if (!$show_item_edit_tools) {
$template['tpl_list_item/item_' . strtolower($stock_tpl) . '_edit_tools'] = null;
$template['tpl_list_item/item_' . $stock_tpl_lower . '_edit_tools'] = null;
}
if (!$show_admin_tools) {
$template['tpl_list_item/item_' . strtolower($stock_tpl) . '_admin_tools'] = null;
$template['tpl_list_item/item_' . $stock_tpl_lower . '_admin_tools'] = null;
}
foreach ($conditional_replaces as $k => $v) {
$template[$k] = $v;
@@ -552,7 +558,7 @@ function get_peafowl_item_list($item, $template, $tools, $tpl = 'image', array $
$placeholder = $stock_tpl == 'IMAGE' ? 'IMAGE_FLAG' : 'ALBUM_COVER_FLAG';
$replacements[$placeholder] = $nsfw ? 'unsafe' : 'safe';
}
$object = array_filter_array($item, ['id_encoded', 'image', 'medium', 'thumb', 'name', 'title', 'display_url', 'extension', 'filename', 'height', 'how_long_ago', 'size_formatted', 'url', 'path_viewer', 'url_viewer', 'url_short', 'width', 'is_360']);
$object = array_filter_array($item, ['id_encoded', 'image', 'medium', 'thumb', 'name', 'title', 'display_url', 'display_title', 'extension', 'filename', 'height', 'how_long_ago', 'size_formatted', 'url', 'path_viewer', 'url_viewer', 'url_frame', 'url_short', 'width', 'is_360', 'type']);
if (isset($item['user'])) {
$object['user'] = [];
foreach (['avatar', 'url', 'username', 'name_short_html'] as $k) {
@@ -1034,26 +1040,41 @@ function getThemeLogo(): string
}
}
function badgePaid(bool $isPaid = true): string
function badgePaid(string $edition): string
{
if (!(bool) env()['CHEVERETO_ENABLE_EXPOSE_PAID_FEATURES'] || $isPaid === false) {
if (!(bool) env()['CHEVERETO_ENABLE_EXPOSE_PAID_FEATURES']) {
return '';
}
if (in_array($edition, editionCombo()[env()['CHEVERETO_EDITION']])) {
return '';
}
return sprintf('<span class="badge badge--paid"><i class="fa-solid fa-dollar-sign"></i> %s</span>', _s('Pro'));
return sprintf('<span class="badge badge--paid"><i class="fa-solid fa-dollar-sign"></i> %s</span>', $edition);
}
function echoBadgePaid(bool $isPaid = true): void
function linkPaid(string $edition): ?string
{
echo badgePaid($isPaid);
}
function echoInputDisabledPaid(bool $disabled = true): void
{
if ($disabled === false) {
return;
if (!(bool) env()['CHEVERETO_ENABLE_EXPOSE_PAID_FEATURES']) {
return null;
}
echo ' disabled="disabled" title="' . _s('This is a paid feature') . '"';
if (in_array($edition, editionCombo()[env()['CHEVERETO_EDITION']])) {
return null;
}
return 'https://chevereto.com/pricing';
}
function inputDisabledPaid(string $edition): string
{
if (in_array($edition, editionCombo()[env()['CHEVERETO_EDITION']])) {
return '';
}
return ' disabled="disabled" title="'
. _s('This is a paid feature (%s edition)', $edition)
. '"'
. ' rel="tooltip"'
. ' data-tiptip="right"';
}
function getIpButtonsArray(array $bannedIp, string $ip): array

View File

@@ -650,7 +650,7 @@ function get_users_image_url(string $filename): string
return get_content_url('images/users/' . $filename);
}
function get_image_fileinfo(string $file): array
function get_fileinfo(string $file): array
{
$extension = get_file_extension($file);
$return = [
@@ -1330,3 +1330,13 @@ function getLicenseKey(): string
return $licenseKey;
}
function editionCombo(): array
{
return [
'free' => ['free'],
'lite' => ['free', 'lite'],
'pro' => ['free', 'lite', 'pro'],
'enterprise' => ['free', 'lite', 'pro', 'enterprise'],
];
}