diff --git a/.htaccess b/.htaccess index bd7cf45..bc6a177 100644 --- a/.htaccess +++ b/.htaccess @@ -20,12 +20,12 @@ Options -MultiViews #RewriteRule images/.+\.(gif|jpe?g|a?png|bmp|webp) content/images/system/default/404.gif [NC,L] RewriteRule images/.+\.(gif|jpe?g|png|bmp|webp) - [NC,L,R=404] - # PHP front controller + # PHP front controller RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . index.php [L] - # Single PHP-entrypoint - RewriteCond %{THE_REQUEST} ^.+?\ [^?]+\.php[?\ ] [NC] - RewriteRule \.php$ - [NC,L,F,R=404] - \ No newline at end of file + # Single PHP-entrypoint + RewriteCond %{THE_REQUEST} ^.+?\ [^?]+\.php[?\ ] [NC] + RewriteRule \.php$ - [NC,L,F,R=404] + diff --git a/.package/4.1.1.txt b/.package/4.1.1.txt deleted file mode 100644 index aad804f..0000000 --- a/.package/4.1.1.txt +++ /dev/null @@ -1,19 +0,0 @@ -Chevereto 4.1.1 (2024-04-27) - -- 🎥 Added FFmpeg detection on Dashboard -- 🎥 Added support for MOV files (video/quicktime) -- 🎥 Improved detection of FFmpeg required functions proc_open and proc_close -- 🎥 Improved FFprobe handling when processing video -- 🎥 Improved video display for listing viewer -- 💅 Added display title to listing viewer -- 💅 Fixed files remark on Dashboard -- 💅 Fixed file remarks on Bulk importer stats -- 💅 Improved image viewer & natural zoom -- 💅 Improved menu display for mobile -- 💅 Updated homepage cover image -- 🔑 Improved feedback on license key handling -- 🐞 Fixed bug with missing scroll behavior -- 🐞 Fixed bug with not working URL upload -- 🐞 Fixed bug with video upload not working on iOS -- 🐞 Fixed bug with file-upload documentation link -- 🇨🇱 Updated Spanish translation \ No newline at end of file diff --git a/.package/4.1.2.txt b/.package/4.1.2.txt new file mode 100644 index 0000000..d74d295 --- /dev/null +++ b/.package/4.1.2.txt @@ -0,0 +1,20 @@ +Chevereto 4.1.2 (2024-05-06) + +- 🎥 Added support for custom FFmpeg and FFprobe binaries +- 🎥 Improved video display on listings and viewer +- ✅ Improved embed codes display +- ✅ Improved upgrading in systems without passthru +- 🖼️ Improved compatibility with jpg extension +- 🐘 Added Dashboard section for PHP configuration (ini files) +- 💅 Added feedback on CLI update command +- 💅 Added remark on Homepage settings when conflicts with single profile routing +- 💅 Fixed file references on /album +- 💅 Improved style for user settings button +- 💅 Improved style for user top menu +- 🐞 Fixed bug affecting album password when using encryption +- 🐞 Fixed bug affecting full screen video behavior +- 🐞 Fixed bug affecting not working URL upload editing +- 🐞 Fixed bug affecting wrong video image frame permissions +- 🐞 Fixed bug in album embed codes +- 🐞 Fixed bug in password protected album editing on listings +- 🐞 Fixed bug in Search class diff --git a/README.md b/README.md index 9e3c2b4..01acffd 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # Chevereto: Ultimate image and video sharing software -> 🔔 [Subscribe](https://chv.to/newsletter) to don't miss any update regarding Chevereto. -

Chevereto

@@ -16,6 +14,8 @@ [![Legacy stars](https://img.shields.io/github/stars/rodber/chevereto-free?style=flat-square&logo=github&label=Legacy%20stars&color=red)](https://github.com/rodber/chevereto-free) [![Awesome F/OSS](https://img.shields.io/badge/Awesome_F%2FOSS-Certified-black?colorA=&colorB=874efe&style=flat-square)](https://awsmfoss.com/chevereto/) +> 🔔 [Subscribe](https://chv.to/newsletter) to don't miss any update regarding Chevereto. + Chevereto enables to create a media sharing website on your own server. It's your hosting and your rules, say goodbye to closures and restrictions. ⭐️ [Live demo](https://demo.chevereto.com) Chevereto is a turnkey system which main use case is to provide a self-hosted platform for content creators, communities and businesses. It's features are all about media sharing, with a strong focus on user experience, privacy and security. On its pro edition Chevereto excels as a content management system with heavy business related features that you won't get on other systems. @@ -134,108 +134,111 @@ Chevereto is so **feature-rich**, mature and robust that we need three layers of ### Admin features -| Feature | Free | Lite | Pro | -| ----------------------------------------------------- | :---: | :---: | :---: | -| Dashboard (admin UI) | ✅ | ✅ | ✅ | -| System stats & usage | ✅ | ✅ | ✅ | -| Website privacy mode (public, private) | ✅ | ✅ | ✅ | -| Default timezone selection | ✅ | ✅ | ✅ | -| Uploadable file extensions | ✅ | ✅ | ✅ | -| Guest uploads auto delete | ✅ | ✅ | ✅ | -| Upload threads | ✅ | ✅ | ✅ | -| Upload maximum image size | ✅ | ✅ | ✅ | -| Upload Exif removal | ✅ | ✅ | ✅ | -| Upload max file size (users and guest) | ✅ | ✅ | ✅ | -| Upload path | ✅ | ✅ | ✅ | -| Upload file naming | ✅ | ✅ | ✅ | -| Upload thumb size | ✅ | ✅ | ✅ | -| Upload medium size and dimension | ✅ | ✅ | ✅ | -| Semantics | ✅ | ✅ | ✅ | -| Default palette | ✅ | ✅ | ✅ | -| Default font | ✅ | ✅ | ✅ | -| Image load max file size | ✅ | ✅ | ✅ | -| Image first tab | ✅ | ✅ | ✅ | -| Embed codes (content) | ✅ | ✅ | ✅ | -| Custom JS & CSS | ✅ | ✅ | ✅ | -| Universal CDN support | ✅ | ✅ | ✅ | -| Default language | ✅ | ✅ | ✅ | -| Homepage style | – | ✅ | ✅ | -| Homepage cover images | – | ✅ | ✅ | -| Homepage title & paragraph | – | ✅ | ✅ | -| Homepage call to action | – | ✅ | ✅ | -| Pages | – | ✅ | ✅ | -| Lock NSFW editing | – | ✅ | ✅ | -| User min age required | – | ✅ | ✅ | -| User avatar max file size | – | ✅ | ✅ | -| User background max file size | – | ✅ | ✅ | -| Guest API key | – | ✅ | ✅ | -| Available languages (configurable) | – | – | ✅ | -| Hide "Powered by Chevereto" | – | – | ✅ | -| Logo & branding | – | – | ✅ | -| Logo type (vector, image, text) | – | – | ✅ | -| Logo height | – | – | ✅ | -| Logo favicon image | – | – | ✅ | -| Routing (user, image, album) | – | – | ✅ | -| Routing root | – | – | ✅ | -| External services | – | – | ✅ | -| Comments API (Disqus, JS) | – | – | ✅ | -| Analytics code | – | – | ✅ | -| Akismet spam protection | – | – | ✅ | -| StopForumSpam spam protection | – | – | ✅ | -| CAPTCHA (reCAPTCHA, hCaptcha) | – | – | ✅ | -| CAPTCHA threshold | – | – | ✅ | -| Project Arachnid | – | – | ✅ | -| ModerateContent (auto approve, block, flag) | – | – | ✅ | -| OAuth2 login providers (Amazon, Google, Discord, etc) | – | – | ✅ | -| Banners | – | – | ✅ | -| Watermark uploads (guest, user, admin) | – | – | ✅ | -| Watermark file toggles | – | – | ✅ | -| Watermark size requirement | – | – | ✅ | -| Watermark custom image | – | – | ✅ | -| Watermark position | – | – | ✅ | -| Watermark percentage | – | – | ✅ | -| Watermark margin | – | – | ✅ | -| Watermark opacity | – | – | ✅ | +| Feature | Free | Lite | Pro | +| --------------------------------------------------------------------------------------------- | :---: | :---: | :---: | +| Dashboard (admin UI) | ✅ | ✅ | ✅ | +| System stats & usage | ✅ | ✅ | ✅ | +| Website name | ✅ | ✅ | ✅ | +| Website doctitle | ✅ | ✅ | ✅ | +| Website description | ✅ | ✅ | ✅ | +| Website privacy mode (public, private) | ✅ | ✅ | ✅ | +| Default timezone | ✅ | ✅ | ✅ | +| Uploadable file extensions | ✅ | ✅ | ✅ | +| Guest uploads auto delete | ✅ | ✅ | ✅ | +| Upload threads | ✅ | ✅ | ✅ | +| Upload maximum image size | ✅ | ✅ | ✅ | +| Upload Exif removal | ✅ | ✅ | ✅ | +| Upload max file size (users and guest) | ✅ | ✅ | ✅ | +| Upload path | ✅ | ✅ | ✅ | +| Upload file naming | ✅ | ✅ | ✅ | +| Upload thumb size | ✅ | ✅ | ✅ | +| Upload medium size and dimension | ✅ | ✅ | ✅ | +| Semantics | ✅ | ✅ | ✅ | +| Default palette | ✅ | ✅ | ✅ | +| Default font | ✅ | ✅ | ✅ | +| Image load max file size | ✅ | ✅ | ✅ | +| Image first tab | ✅ | ✅ | ✅ | +| Embed codes (content) | ✅ | ✅ | ✅ | +| Custom JS & CSS | ✅ | ✅ | ✅ | +| Universal CDN support | ✅ | ✅ | ✅ | +| [Default language](https://v4-admin.chevereto.com/settings/languages.html#default-language) | ✅ | ✅ | ✅ | +| Homepage style | – | ✅ | ✅ | +| Homepage cover images | – | ✅ | ✅ | +| Homepage title & paragraph | – | ✅ | ✅ | +| Homepage call to action | – | ✅ | ✅ | +| Pages | – | ✅ | ✅ | +| Lock NSFW editing | – | ✅ | ✅ | +| User min age required | – | ✅ | ✅ | +| User avatar max file size | – | ✅ | ✅ | +| User background max file size | – | ✅ | ✅ | +| Guest API key | – | ✅ | ✅ | +| [Enabled languages](https://v4-admin.chevereto.com/settings/languages.html#enabled-languages) | – | – | ✅ | +| Hide "Powered by Chevereto" | – | – | ✅ | +| Logo & branding | – | – | ✅ | +| Logo type (vector, image, text) | – | – | ✅ | +| Logo height | – | – | ✅ | +| Logo favicon image | – | – | ✅ | +| Routing (user, image, album) | – | – | ✅ | +| Routing root | – | – | ✅ | +| External services | – | – | ✅ | +| Comments API (Disqus, JS) | – | – | ✅ | +| Analytics code | – | – | ✅ | +| Akismet spam protection | – | – | ✅ | +| StopForumSpam spam protection | – | – | ✅ | +| CAPTCHA (reCAPTCHA, hCaptcha) | – | – | ✅ | +| CAPTCHA threshold | – | – | ✅ | +| Project Arachnid | – | – | ✅ | +| ModerateContent (auto approve, block, flag) | – | – | ✅ | +| OAuth2 login providers (Amazon, Google, Discord, etc) | – | – | ✅ | +| Banners | – | – | ✅ | +| Watermark uploads (guest, user, admin) | – | – | ✅ | +| Watermark file toggles | – | – | ✅ | +| Watermark size requirement | – | – | ✅ | +| Watermark custom image | – | – | ✅ | +| Watermark position | – | – | ✅ | +| Watermark percentage | – | – | ✅ | +| Watermark margin | – | – | ✅ | +| Watermark opacity | – | – | ✅ | ### Admin toggles -| Feature | Free | Lite | Pro | -| --------------------------- | :---: | :---: | :---: | -| Search (users and guest) | ✅ | ✅ | ✅ | -| Explore (users and guest) | ✅ | ✅ | ✅ | -| Random (users and guest) | ✅ | ✅ | ✅ | -| NSFW listings | ✅ | ✅ | ✅ | -| Blur NSFW content | ✅ | ✅ | ✅ | -| NSFW on random mode | ✅ | ✅ | ✅ | -| Banners on NSFW | ✅ | ✅ | ✅ | -| Uploads (users and guest) | ✅ | ✅ | ✅ | -| Uploads (URL) | ✅ | ✅ | ✅ | -| Upload moderation | ✅ | ✅ | ✅ | -| Upload embed codes | ✅ | ✅ | ✅ | -| Upload redirection | ✅ | ✅ | ✅ | -| Upload duplication | ✅ | ✅ | ✅ | -| Upload expiration | ✅ | ✅ | ✅ | -| Upload NSFW checkbox | ✅ | ✅ | ✅ | -| Download button | ✅ | ✅ | ✅ | -| Right click | ✅ | ✅ | ✅ | -| Show Exif data | ✅ | ✅ | ✅ | -| Social share buttons | ✅ | ✅ | ✅ | -| Automatic updates check | ✅ | ✅ | ✅ | -| Dump update query | ✅ | ✅ | ✅ | -| Debug errors | ✅ | ✅ | ✅ | -| Consent screen (age gate) | – | ✅ | ✅ | -| User sign up | – | ✅ | ✅ | -| User content delete | – | ✅ | ✅ | -| User notify sign up | – | ✅ | ✅ | -| User email confirmation | – | ✅ | ✅ | -| User email for social login | – | ✅ | ✅ | -| Auto language | – | – | ✅ | -| Language chooser | – | – | ✅ | -| SEO URLs (media and album) | – | – | ✅ | -| Cookie law compliance | – | – | ✅ | -| Flood protection | – | – | ✅ | -| Flood protection notify | – | – | ✅ | -| Watermarks | – | – | ✅ | +| Feature | Free | Lite | Pro | +| ------------------------------------------------------------------------------------------- | :---: | :---: | :---: | +| Search (users and guest) | ✅ | ✅ | ✅ | +| Explore (users and guest) | ✅ | ✅ | ✅ | +| Random (users and guest) | ✅ | ✅ | ✅ | +| NSFW listings | ✅ | ✅ | ✅ | +| Blur NSFW content | ✅ | ✅ | ✅ | +| NSFW on random mode | ✅ | ✅ | ✅ | +| Banners on NSFW | ✅ | ✅ | ✅ | +| Uploads (users and guest) | ✅ | ✅ | ✅ | +| Uploads (URL) | ✅ | ✅ | ✅ | +| Upload moderation | ✅ | ✅ | ✅ | +| Upload embed codes | ✅ | ✅ | ✅ | +| Upload redirection | ✅ | ✅ | ✅ | +| Upload duplication | ✅ | ✅ | ✅ | +| Upload expiration | ✅ | ✅ | ✅ | +| Upload NSFW checkbox | ✅ | ✅ | ✅ | +| Download button | ✅ | ✅ | ✅ | +| Right click | ✅ | ✅ | ✅ | +| Show Exif data | ✅ | ✅ | ✅ | +| Social share buttons | ✅ | ✅ | ✅ | +| Automatic updates check | ✅ | ✅ | ✅ | +| Dump update query | ✅ | ✅ | ✅ | +| Debug errors | ✅ | ✅ | ✅ | +| Consent screen (age gate) | – | ✅ | ✅ | +| User sign up | – | ✅ | ✅ | +| User content delete | – | ✅ | ✅ | +| User notify sign up | – | ✅ | ✅ | +| User email confirmation | – | ✅ | ✅ | +| User email for social login | – | ✅ | ✅ | +| [Auto language](https://v4-admin.chevereto.com/settings/languages.html#auto-language) | – | – | ✅ | +| [Language chooser](https://v4-admin.chevereto.com/settings/languages.html#language-chooser) | – | – | ✅ | +| SEO URLs (media and album) | – | – | ✅ | +| Cookie law compliance | – | – | ✅ | +| Flood protection | – | – | ✅ | +| Flood protection notify | – | – | ✅ | +| Watermarks | – | – | ✅ | ### System features diff --git a/app/composer.lock b/app/composer.lock index 15ad497..7dd1995 100644 --- a/app/composer.lock +++ b/app/composer.lock @@ -3277,20 +3277,20 @@ }, { "name": "ramsey/uuid", - "version": "4.7.5", + "version": "4.7.6", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "5f0df49ae5ad6efb7afa69e6bfab4e5b1e080d8e" + "reference": "91039bc1faa45ba123c4328958e620d382ec7088" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/5f0df49ae5ad6efb7afa69e6bfab4e5b1e080d8e", - "reference": "5f0df49ae5ad6efb7afa69e6bfab4e5b1e080d8e", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/91039bc1faa45ba123c4328958e620d382ec7088", + "reference": "91039bc1faa45ba123c4328958e620d382ec7088", "shasum": "" }, "require": { - "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11", + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12", "ext-json": "*", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" @@ -3353,7 +3353,7 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.7.5" + "source": "https://github.com/ramsey/uuid/tree/4.7.6" }, "funding": [ { @@ -3365,7 +3365,7 @@ "type": "tidelift" } ], - "time": "2023-11-08T05:53:05+00:00" + "time": "2024-04-27T21:32:50+00:00" }, { "name": "react/cache", @@ -4083,16 +4083,16 @@ }, { "name": "symfony/cache", - "version": "v5.4.38", + "version": "v5.4.39", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "223c3afac82e003a76931b71d77db408636a0de8" + "reference": "982237e35079fdcc31ab724f06b6131992c4fd24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/223c3afac82e003a76931b71d77db408636a0de8", - "reference": "223c3afac82e003a76931b71d77db408636a0de8", + "url": "https://api.github.com/repos/symfony/cache/zipball/982237e35079fdcc31ab724f06b6131992c4fd24", + "reference": "982237e35079fdcc31ab724f06b6131992c4fd24", "shasum": "" }, "require": { @@ -4160,7 +4160,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v5.4.38" + "source": "https://github.com/symfony/cache/tree/v5.4.39" }, "funding": [ { @@ -4176,7 +4176,7 @@ "type": "tidelift" } ], - "time": "2024-03-19T09:55:32+00:00" + "time": "2024-04-18T08:26:06+00:00" }, { "name": "symfony/cache-contracts", diff --git a/app/env-default.php b/app/env-default.php index 2367770..d1bd041 100644 --- a/app/env-default.php +++ b/app/env-default.php @@ -68,6 +68,8 @@ return [ 'CHEVERETO_ENABLE_UPLOAD_PLUGIN' => '1', 'CHEVERETO_ENABLE_UPLOAD_WATERMARK' => '1', 'CHEVERETO_ENABLE_USERS' => '1', + 'CHEVERETO_ENABLE_SEO_IMAGE_URL' => '1', + 'CHEVERETO_ENABLE_SEO_ALBUM_URL' => '1', 'CHEVERETO_ENCRYPTION_KEY' => '', 'CHEVERETO_ERROR_LOG' => 'php://stderr', 'CHEVERETO_HEADER_CLIENT_IP' => '', @@ -88,4 +90,6 @@ return [ 'CHEVERETO_SESSION_SAVE_HANDLER' => 'files', 'CHEVERETO_SESSION_SAVE_PATH' => '/tmp', 'CHEVERETO_EDITION' => 'pro', + 'CHEVERETO_BINARY_FFMPEG' => 'ffmpeg', + 'CHEVERETO_BINARY_FFPROBE' => 'ffprobe', ]; diff --git a/app/legacy/commands/cron.php b/app/legacy/commands/cron.php index bdede94..c138533 100644 --- a/app/legacy/commands/cron.php +++ b/app/legacy/commands/cron.php @@ -115,7 +115,7 @@ function removeDeleteLog(): void } function checkForNews(): void { - if (!checkoutUpdate('news_check_datetimegmt')) { + if (!checkoutUpdate('news_check_datetimegmt', 'PT4H')) { feedbackAlert('Skipping news check'); return; @@ -133,7 +133,7 @@ function checkForNews(): void } function checkForUpdates(): void { - if (!checkoutUpdate('update_check_datetimegmt')) { + if (!checkoutUpdate('update_check_datetimegmt', 'P1D')) { feedbackAlert('Skipping updates check'); return; @@ -149,10 +149,10 @@ function checkForUpdates(): void } echoLocked($job); } -function checkoutUpdate(string $datetimeSetting): bool +function checkoutUpdate(string $datetimeSetting, string $past): bool { return is_null(Settings::get($datetimeSetting)) - || datetime_add(Settings::get($datetimeSetting), 'P1D') < datetimegmt(); + || datetime_add(Settings::get($datetimeSetting), $past) < datetimegmt(); } function checkHtaccess() { diff --git a/app/legacy/commands/langs.php b/app/legacy/commands/langs.php index 2f3c6c1..86536a5 100644 --- a/app/legacy/commands/langs.php +++ b/app/legacy/commands/langs.php @@ -52,6 +52,9 @@ foreach ($languages as $lang) { ]); } echo "$lang\n"; + if (file_exists($language_override_file)) { + echo "$lang [override]\n"; + } } echo "---\n"; echo L10n::LOCALES_AVAILABLE_FILEPATH . "\n"; diff --git a/app/legacy/install/installer.php b/app/legacy/install/installer.php index 1423cc1..c1e06e5 100644 --- a/app/legacy/install/installer.php +++ b/app/legacy/install/installer.php @@ -604,6 +604,7 @@ $settings_updates = [ '4.1.1' => [ 'upload_enabled_image_formats' => 'jpg,png,bmp,gif,webp,mov,mp4,webm', ], + '4.1.2' => null, ]; $cheveretoFreeMap = [ '1.0.0' => '3.8.3', @@ -2053,6 +2054,7 @@ ALTER TABLE `%table_prefix%users` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8m xr($sql_update); try { + logger("[STATUS] Updating Chevereto database (this may take a while)...\n"); $updated = $db->exec(); if ($updated) { $chevereto_version_installed = DB::get('settings', ['name' => 'chevereto_version_installed'])[0]['setting_value']; diff --git a/app/legacy/load/app.php b/app/legacy/load/app.php index 5f35748..2d1d5a0 100644 --- a/app/legacy/load/app.php +++ b/app/legacy/load/app.php @@ -9,5 +9,5 @@ * file that was distributed with this source code. */ -const APP_VERSION = '4.1.1'; +const APP_VERSION = '4.1.2'; const APP_VERSION_AKA = 'pulento'; diff --git a/app/legacy/routes/dashboard.php b/app/legacy/routes/dashboard.php index fc21e21..88e228d 100644 --- a/app/legacy/routes/dashboard.php +++ b/app/legacy/routes/dashboard.php @@ -39,6 +39,7 @@ use function Chevereto\Legacy\G\get_app_version; use function Chevereto\Legacy\G\get_base_url; use function Chevereto\Legacy\G\get_bytes; use function Chevereto\Legacy\G\get_client_ip; +use function Chevereto\Legacy\G\get_ffmpeg_error; use function Chevereto\Legacy\G\get_ini_bytes; use function Chevereto\Legacy\G\get_regex_match; use Chevereto\Legacy\G\Handler; @@ -69,6 +70,7 @@ use function Chevereto\Vars\server; use function Chevereto\Vars\session; use function Chevereto\Vars\sessionVar; use FFMpeg\FFMpeg; +use FFMpeg\FFProbe; use Intervention\Image\ImageManagerStatic; use PHPMailer\PHPMailer\SMTP; @@ -174,13 +176,13 @@ return function (Handler $handler) { 'languages' => _s('Languages'), 'email' => _s('Email'), 'tools' => _s('Tools'), - 'logo' => _s('Logo'), 'homepage' => _s('Homepage'), 'pages' => _s('Pages'), 'upload-plugin' => _s('Upload plugin'), 'consent-screen' => _s('Consent screen'), 'users' => _n('User', 'Users', 20), 'guest-api' => _s('Guests %s', 'API'), + 'logo' => _s('Logo'), 'external-storage' => _s('External storage'), 'routing' => _s('Routing'), 'external-services' => _s('External services'), @@ -231,7 +233,7 @@ return function (Handler $handler) { 'homepage' => ['lite', 'CHEVERETO_ENABLE_USERS'], 'ip-bans' => ['pro', 'CHEVERETO_ENABLE_IP_BANS'], 'login-providers' => ['pro', 'CHEVERETO_ENABLE_LOGIN_PROVIDERS'], - 'logo' => ['lite', 'CHEVERETO_ENABLE_LOGO'], + 'logo' => ['pro', 'CHEVERETO_ENABLE_LOGO'], 'pages' => ['lite', 'CHEVERETO_ENABLE_PAGES'], 'routing' => ['pro', 'CHEVERETO_ENABLE_ROUTING'], 'upload-plugin' => ['lite', 'CHEVERETO_ENABLE_UPLOAD_PLUGIN'], @@ -417,7 +419,6 @@ return function (Handler $handler) { $errorLogRemark .= '
docker logs ' . (gethostname() ?: 'chv-container') . ' -f 1>/dev/null
'; } } - $ffmpegContent = ' '; try { @@ -434,14 +435,43 @@ return function (Handler $handler) { ) ); } - $ffmpegContent .= FFMpeg::create()->getFFMpegDriver()->getVersion(); + $ffmpegErrors = []; + + try { + $ffmpeg = FFMpeg::create( + [ + 'ffmpeg.binaries' => env()['CHEVERETO_BINARY_FFMPEG'], + 'ffprobe.binaries' => env()['CHEVERETO_BINARY_FFPROBE'], + ] + ); + } catch (Throwable $e) { + $ffmpegErrors[] = get_ffmpeg_error($e); + } + + try { + $ffprobe = FFProbe::create( + [ + 'ffprobe.binaries' => env()['CHEVERETO_BINARY_FFPROBE'], + ] + ); + } catch (Throwable $e) { + $ffmpegErrors[] = get_ffmpeg_error($e); + } + if ($ffmpegErrors !== []) { + throw new Exception(implode(', ', $ffmpegErrors)); + } + + $ffprobe->getFFProbeDriver()->getName(); + $ffmpegContent .= 'FFmpeg bin:' + . env()['CHEVERETO_BINARY_FFMPEG'] + . ' version ' + . $ffmpeg->getFFMpegDriver()->getVersion() + . '
' + . ' FFprobe bin:' + . env()['CHEVERETO_BINARY_FFPROBE']; } catch (Throwable $e) { - $previous = $e->getPrevious() ? - ': ' . $e->getPrevious()->getMessage() : - ''; $ffmpegContent = ' Error: ' - . $e->getMessage() - . $previous + . get_ffmpeg_error($e) . ''; } @@ -534,6 +564,16 @@ return function (Handler $handler) { $mysqlVersion = $db->getAttr(PDO::ATTR_SERVER_VERSION); $db->closeCursor(); $mysqlServerInfo = $db->getAttr(PDO::ATTR_SERVER_INFO); + $phpIniLoaded = php_ini_loaded_file(); + $phpIniFiles = php_ini_scanned_files() ?: 'N/A'; + $phpIniFiles = explode(',', $phpIniFiles); + if ($phpIniLoaded) { + array_unshift($phpIniFiles, $phpIniLoaded); + } + $phpIniFiles = array_map(function ($v) { + return '
' . $v . '
'; + }, $phpIniFiles); + $phpIniFiles = implode('', $phpIniFiles); $system_values_more = [ 'links' => [ 'label' => _s('Links'), @@ -551,10 +591,6 @@ return function (Handler $handler) { 'label' => 'Error log', 'content' => ' ' . Config::system()->errorLog() . '' . $errorLogRemark, ], - 'php_version' => [ - 'label' => _s('PHP version'), - 'content' => ' ' . PHP_VERSION . ' ' . php_ini_loaded_file() - ], 'server' => [ 'label' => _s('Server'), 'content' => ' ' @@ -577,6 +613,12 @@ return function (Handler $handler) { . '
' . $mysqlServerInfo ], + 'php_version' => [ + 'label' => _s('PHP version'), + 'content' => ' ' + . PHP_VERSION + . $phpIniFiles + ], 'file_uploads' => [ 'label' => _s('File uploads'), 'content' => (int) ini_get('file_uploads') == 1 diff --git a/app/legacy/routes/image.php b/app/legacy/routes/image.php index 6eb81db..3489024 100644 --- a/app/legacy/routes/image.php +++ b/app/legacy/routes/image.php @@ -15,6 +15,7 @@ use Chevereto\Legacy\Classes\IpBan; use Chevereto\Legacy\Classes\Login; use Chevereto\Legacy\Classes\User; use function Chevereto\Legacy\encodeID; +use function Chevereto\Legacy\flatten_array; use function Chevereto\Legacy\G\get_current_url; use function Chevereto\Legacy\G\get_global; use Chevereto\Legacy\G\Handler; @@ -275,20 +276,23 @@ return function (Handler $handler) { $handler::setVar('privacy', $image['album']['privacy'] ?? ''); include_theme_file('snippets/embed'); $embed_share_tpl = get_global('embed_share_tpl'); - $sharing = [ - '%URL_VIEWER%' => $image['url_viewer'], - '%URL%' => $image['url'], - '%DISPLAY_URL%' => $image['display_url'], - '%DISPLAY_TITLE%' => $image['display_title'], - '%URL_FRAME%' => $image['url_frame'], - '%THUMB_URL%' => $image['thumb']['url'], - '%MEDIUM_URL%' => $image['medium']['url'] ?? '', - ]; + $sharing = []; + foreach (flatten_array($image) as $imageKey => $imageValue) { + $sharing['%' . strtoupper($imageKey) . '%'] = $imageValue; + } $embed = []; + $hasFrame = $image['url_frame'] !== ''; + $hasMedium = $image['medium']['url'] !== null; foreach ($embed_share_tpl as $code => $group) { $entries = []; $groupLabel = $group['label']; foreach ($group['options'] as $option => $optionValue) { + if (!$hasFrame && str_starts_with($option, 'frame-')) { + continue; + } + if (!$hasMedium && str_starts_with($option, 'medium-')) { + continue; + } $value = $optionValue['template']; if (is_array($value)) { $value = $value[$image['type']]; diff --git a/app/legacy/routes/json.php b/app/legacy/routes/json.php index ce1867c..585c545 100644 --- a/app/legacy/routes/json.php +++ b/app/legacy/routes/json.php @@ -391,7 +391,7 @@ return function (Handler $handler) { $json_array['album'] = array_filter_array($album, ['id', 'creation_ip', 'password', 'user', 'privacy_extra', 'privacy_notes'], 'rest'); $contents = []; foreach ($listing->outputAssoc() as $v) { - $contents[] = array_filter_array($v, ['title', 'id_encoded', 'url', 'url_short', 'path_viewer', 'url_viewer', 'filename', 'medium', 'thumb'], 'exclusion'); + $contents[] = array_filter_array($v, ['title', 'id_encoded', 'url', 'url_short', 'path_viewer', 'url_viewer', 'filename', 'medium', 'thumb', 'type', 'url_frame'], 'exclusion'); } $json_array['is_output_truncated'] = $album['image_count'] > $album_fetch ? 1 : 0; $json_array['contents'] = $contents; diff --git a/app/src/Legacy/Classes/Album.php b/app/src/Legacy/Classes/Album.php index 48afabe..06b4e2b 100644 --- a/app/src/Legacy/Classes/Album.php +++ b/app/src/Legacy/Classes/Album.php @@ -86,18 +86,10 @@ class Album if ($requester !== []) { $album_db['album_liked'] = (bool) $album_db['like_user_id']; } - $return = $album_db; - if (isset($return['album_password']) && hasEncryption()) { - try { - $return['album_password'] = decrypt($return['album_password']); - } catch (Throwable) { - $return['album_password'] = $return['album_password']; - } - } return $pretty - ? self::formatArray($return) - : $return; + ? self::formatArray($album_db) + : self::cipherAwareDbRow($album_db); } public static function getMultiple(array $ids, bool $pretty = false): array @@ -514,8 +506,22 @@ class Album $album['cta'] = $album['cta'] ?? '[]'; } + public static function cipherAwareDbRow(array &$dbrow): array + { + if (isset($dbrow['album_password']) && hasEncryption()) { + try { + $dbrow['album_password'] = decrypt($dbrow['album_password']); + } catch (Throwable) { + $dbrow['album_password'] = $dbrow['album_password']; + } + } + + return $dbrow; + } + public static function formatArray(array $dbrow, bool $safe = false): array { + self::cipherAwareDbRow($dbrow); $output = DB::formatRow($dbrow); if (!isset($output['user'])) { $output['user'] = []; @@ -555,7 +561,7 @@ class Album if (isset($session_password) && hasEncryption()) { $session_password = decrypt($session_password); } - if (!isset($session_password) || !hash_equals($session_password, $album['password'])) { + if (!isset($session_password) || !hash_equals($album['password'], $session_password)) { $removeValue = session()['password'] ?? null; unset($removeValue['album'][$album['id']]); sessionVar()->put('password', $removeValue); diff --git a/app/src/Legacy/Classes/Image.php b/app/src/Legacy/Classes/Image.php index b9d0dea..e615474 100644 --- a/app/src/Legacy/Classes/Image.php +++ b/app/src/Legacy/Classes/Image.php @@ -14,6 +14,8 @@ namespace Chevereto\Legacy\Classes; use function Chevere\Message\message; use function Chevere\String\randomString; use Chevere\Throwable\Exceptions\LogicException; +use function Chevereto\Encryption\decrypt; +use function Chevereto\Encryption\hasEncryption; use function Chevereto\Legacy\assertNotStopWords; use function Chevereto\Legacy\decodeID; use function Chevereto\Legacy\encodeID; @@ -31,6 +33,7 @@ use function Chevereto\Legacy\G\format_bytes; use function Chevereto\Legacy\G\get_basename_without_extension; use function Chevereto\Legacy\G\get_bytes; use function Chevereto\Legacy\G\get_client_ip; +use function Chevereto\Legacy\G\get_ffmpeg_error; use function Chevereto\Legacy\G\get_filename; use function Chevereto\Legacy\G\get_image_fileinfo as GGet_image_fileinfo; use function Chevereto\Legacy\G\get_public_url; @@ -790,9 +793,16 @@ class Image } $resizeSourceImage = $image_upload['uploaded']['file']; $uploadDir = dirname($resizeSourceImage); + $chainExtension = $image_upload['uploaded']['extension']; if ($image_upload['source']['type'] === 'video') { - $frameImage = $uploadDir . '/' . $image_upload['uploaded']['name'] . '.fr.jpeg'; + $chainExtension = 'jpeg'; + $frameImage = $uploadDir + . '/' + . $image_upload['uploaded']['name'] + . '.fr.' + . $chainExtension; rename($image_upload['uploaded']['frame'], $frameImage); + chmod($frameImage, 0644); $resizeSourceImage = $frameImage; $chain_mask[0] = 1; } @@ -810,11 +820,12 @@ class Image } else { $image_resize_options = ['width' => $params['width']]; } + $image_resize_options['extension'] = $image_upload['uploaded']['extension']; $image_upload['uploaded'] = self::resize( - $resizeSourceImage, - dirname($resizeSourceImage), - null, - $image_resize_options + source: $resizeSourceImage, + destination: dirname($resizeSourceImage), + filename: null, + options: $image_resize_options ); $image_upload['uploaded']['fileinfo']['is_360'] = $is_360; } @@ -824,6 +835,7 @@ class Image 'fitted' => true, 'width' => getSetting('upload_thumb_width'), 'height' => getSetting('upload_thumb_height'), + 'extension' => $chainExtension ]; $medium_size = getSetting('upload_medium_size'); $medium_fixed_dimension = getSetting('upload_medium_fixed_dimension'); @@ -870,11 +882,12 @@ class Image $image_medium_options['forced'] = true; $image_medium_options[$medium_fixed_dimension] = min($image_medium_options[$medium_fixed_dimension], $image_upload['uploaded']['fileinfo'][$medium_fixed_dimension]); } + $image_medium_options['extension'] = $chainExtension; $image_medium = self::resize( - $resizeSourceImage, - $uploadDir, - $image_upload['uploaded']['name'] . '.md', - $image_medium_options + source: $resizeSourceImage, + destination: $uploadDir, + filename: $image_upload['uploaded']['name'] . '.md', + options: $image_medium_options ); $chain_mask[3] = 1; } @@ -1246,6 +1259,7 @@ class Image 'original_filename' => $image_upload['source']['filename'], 'original_exifdata' => $original_exifdata, 'is_360' => $is360, + 'extension' => $image_upload['uploaded']['extension'], ]; if (!isset($values['date'])) { $populate_values = array_merge($populate_values, [ @@ -1478,7 +1492,6 @@ class Image $image = array_merge($image, get_fileinfo($targets['chain']['image']), $image_fileinfo); } - $image['file_resource'] = $targets; $image['url_viewer'] = self::getUrlViewer( $image['id_encoded'], @@ -1518,6 +1531,7 @@ class Image break; } + $displaySize = $image['medium']['size']; } elseif ( $image['size'] > get_bytes('200 KB') && $image['type'] === 1 @@ -1525,6 +1539,14 @@ class Image $display_url = $image['thumb']['url'] ?? ''; $display_width = getSetting('upload_thumb_width'); $display_height = getSetting('upload_thumb_height'); + $displaySize = $image['thumb']['size']; + } + if (isset($image['frame']['size'], $displaySize) + && $image['frame']['size'] < $displaySize + ) { + $display_url = $image['frame']['url']; + $display_width = $image['width']; + $display_height = $image['height']; } $image['duration'] = (int) ($image['duration'] ?? 0); $seconds = $image['duration'] ?? 0; @@ -1534,6 +1556,13 @@ class Image } else { $duration_time = ''; } + $image['medium'] = $image['medium'] ?? [ + 'filename' => null, + 'name' => null, + 'mime' => null, + 'extension' => null, + 'url' => null, + ]; $image['duration_time'] = $duration_time; $image['type'] = self::$types[$image['type']]; $image['display_url'] = $display_url; @@ -1562,6 +1591,13 @@ class Image } if (isset($output['album']['id']) || isset($output['user']['id'])) { $output['user'] = $output['user'] ?? []; + if (isset($output['album']['password']) && hasEncryption()) { + try { + $output['album']['password'] = decrypt($output['album']['password']); + } catch (Throwable) { + $output['album']['password'] = $output['album']['password']; + } + } Album::fill($output['album'], $output['user']); } else { unset($output['album']); @@ -1584,7 +1620,18 @@ class Image public static function getVideoFrame(string $file, int $time): string { $frameFile = Upload::getTempNam(sys_get_temp_dir()); - $ffmpeg = FFMpeg::create(); + + try { + $ffmpeg = FFMpeg::create( + [ + 'ffmpeg.binaries' => env()['CHEVERETO_BINARY_FFMPEG'], + 'ffprobe.binaries' => env()['CHEVERETO_BINARY_FFPROBE'], + ] + ); + } catch (Throwable $e) { + throw new Exception("FFprobe error: " . get_ffmpeg_error($e), 600); + } + $video = $ffmpeg->open($file); $video ->frame(TimeCode::fromSeconds($time)) diff --git a/app/src/Legacy/Classes/ImageResize.php b/app/src/Legacy/Classes/ImageResize.php index bf17209..37ce0f4 100644 --- a/app/src/Legacy/Classes/ImageResize.php +++ b/app/src/Legacy/Classes/ImageResize.php @@ -105,7 +105,8 @@ class ImageResize { $this->validateInput(); // Exception 1xx $source_filename = get_basename_without_extension($this->source); - $this->file_extension = $this->source_image_fileinfo['extension']; + $this->file_extension = $this->options['extension'] + ?? $this->source_image_fileinfo['extension']; if (!isset($this->filename)) { $this->filename = $source_filename; } diff --git a/app/src/Legacy/Classes/Search.php b/app/src/Legacy/Classes/Search.php index 06aa773..e68c6b4 100644 --- a/app/src/Legacy/Classes/Search.php +++ b/app/src/Legacy/Classes/Search.php @@ -83,9 +83,6 @@ class Search str_replace($v, '', $q_match) ) ); - if ($q_match === '') { - $q_match = null; - } $op = explode(':', $v); if (!in_array($op[0], ['category', 'ip', 'storage'])) { continue; @@ -134,7 +131,7 @@ class Search break; } } - if (isset($q_match)) { + if ($q_match !== '') { $q_value = $q_match; if ($this->DBEngine == 'InnoDB') { $q_value = trim($q_value, '><'); @@ -146,7 +143,7 @@ class Search $wheres = null; switch ($this->type) { case 'images': - if (isset($q_match)) { + if ($q_match !== '') { $wheres = 'WHERE MATCH(`image_name`,`image_title`,`image_description`,`image_original_filename`) AGAINST (:q IN BOOLEAN MODE)'; } if ($search_op_wheres !== []) { diff --git a/app/src/Legacy/Classes/Settings.php b/app/src/Legacy/Classes/Settings.php index 851e86c..c471dfd 100644 --- a/app/src/Legacy/Classes/Settings.php +++ b/app/src/Legacy/Classes/Settings.php @@ -386,6 +386,16 @@ class Settings 'watermark_target_min_width' => '100', ] ], + 'CHEVERETO_ENABLE_SEO_IMAGE_URL' => ['0', + [ + 'seo_image_urls' => false, + ] + ], + 'CHEVERETO_ENABLE_SEO_ALBUM_URL' => ['0', + [ + 'seo_album_urls' => false, + ] + ], ] as $envKey => $settingValues) { if (env()[$envKey] == $settingValues[0]) { foreach ($settingValues[1] as $k => $v) { diff --git a/app/src/Legacy/Classes/Upload.php b/app/src/Legacy/Classes/Upload.php index bb682e7..304b8bc 100644 --- a/app/src/Legacy/Classes/Upload.php +++ b/app/src/Legacy/Classes/Upload.php @@ -67,6 +67,8 @@ class Upload private array|string $source; + private string $source_extension; + private array $uploaded = []; public bool $detectFlood = true; @@ -145,6 +147,10 @@ class Upload $this->type = (is_image_url($this->source) || is_url($this->source)) ? 'url' : 'file'; + $this->source_extension = $this->type === 'url' + ? pathinfo($this->source, PATHINFO_EXTENSION) + : pathinfo($this->source['name'], PATHINFO_EXTENSION); + $this->source_extension = strtolower($this->source_extension); if ($this->type === 'url') { if (Settings::get('enable_uploads_url') === false) { throw new LogicException( @@ -202,6 +208,9 @@ class Upload } $this->source_name = get_basename_without_extension($this->type == 'url' ? $this->source : $this->source['name']); $this->extension = $this->source_image_fileinfo['extension']; + if ($this->extension === 'jpeg' && $this->source_extension === 'jpg') { + $this->extension = 'jpg'; + } if (!isset($this->name)) { $this->name = $this->source_name; } @@ -211,7 +220,7 @@ class Upload } $this->fixed_filename = preg_replace('/(.*)\.(th|md|original|lg)\.([\w]+)$/', '$1.$3', $this->name . '.' . $this->extension); $is_360 = false; - if ($this->extension == 'jpeg') { + if (in_array($this->extension, ['jpg', 'jpeg'])) { $xmpDataExtractor = new XmpMetadataExtractor(); $xmpData = $xmpDataExtractor->extractFromFile($this->downstream); $reader = \PHPExif\Reader\Reader::factory(\PHPExif\Reader\Reader::TYPE_NATIVE); @@ -319,6 +328,7 @@ class Upload 'fileinfo' => $fileInfo, 'frame' => $frameFile, 'frameinfo' => $frameFile ? get_image_fileinfo($frameFile) : [], + 'extension' => $this->extension, ]; } diff --git a/app/src/Legacy/G/functions.php b/app/src/Legacy/G/functions.php index c2db5f6..0f690bd 100644 --- a/app/src/Legacy/G/functions.php +++ b/app/src/Legacy/G/functions.php @@ -1957,15 +1957,28 @@ function extension_to_mime(string $ext): string ][$ext] ?? ''; } +function get_ffmpeg_error(Throwable $e): string +{ + $previous = $e->getPrevious() ? + (': ' . $e->getPrevious()->getMessage()) : + ''; + + return $e->getMessage() . $previous; +} + function get_video_fileinfo(string $file): array { clearstatcache(true, $file); - $ffprobe = FFProbe::create(); try { + $ffprobe = FFProbe::create( + [ + 'ffprobe.binaries' => env()['CHEVERETO_BINARY_FFPROBE'], + ] + ); $format = $ffprobe->format($file); } catch (Throwable $e) { - throw new Exception("FFprobe error: " . $e->getMessage(), 600); + throw new Exception("FFprobe error: " . get_ffmpeg_error($e), 600); } if (!($format->get('duration') > 0)) { throw new Exception("Invalid video file provided", 100); diff --git a/app/src/Legacy/functions.php b/app/src/Legacy/functions.php index f17b123..ba41a85 100644 --- a/app/src/Legacy/functions.php +++ b/app/src/Legacy/functions.php @@ -1007,7 +1007,7 @@ function loaderHandler( 'CHEVERETO_ENABLE_BANNERS' => '0', 'CHEVERETO_ENABLE_BULK_IMPORTER' => '0', 'CHEVERETO_ENABLE_CAPTCHA' => '0', - 'CHEVERETO_ENABLE_CDN' => '0', + 'CHEVERETO_ENABLE_CDN' => '1', 'CHEVERETO_ENABLE_CONSENT_SCREEN' => '0', 'CHEVERETO_ENABLE_COOKIE_COMPLIANCE' => '0', 'CHEVERETO_ENABLE_EXPOSE_PAID_FEATURES' => '1', @@ -1035,6 +1035,8 @@ function loaderHandler( 'CHEVERETO_ENABLE_UPLOAD_PLUGIN' => '0', 'CHEVERETO_ENABLE_UPLOAD_WATERMARK' => '0', 'CHEVERETO_ENABLE_USERS' => '0', + 'CHEVERETO_ENABLE_SEO_IMAGE_URL' => '0', + 'CHEVERETO_ENABLE_SEO_ALBUM_URL' => '0', 'CHEVERETO_MAX_USERS' => '1', 'CHEVERETO_EDITION' => 'free', )); diff --git a/app/upgrading.php b/app/upgrading.php index 8221898..b125cf3 100644 --- a/app/upgrading.php +++ b/app/upgrading.php @@ -200,13 +200,14 @@ if ($singleStep || $action === 'extract') { logger('Chevereto filesystem upgraded'); unlinkIfExists($lockUpgrading); $safeResult = false; - $command = $rootDir . '/app/bin/legacy -C update'; if (passthruEnabled()) { - logger('Command passthru'); + logger('Update command passthru'); + $command = $rootDir . '/app/bin/legacy -C update'; $safeResult = passthru($command); } if ($safeResult === false) { - logger('Continue with database update'); + logger('Continuing with database update at /update'); + $return = 'update'; } if (PHP_SAPI !== 'cli') { $continueUri = $rootUrl . $return; diff --git a/content/legacy/system/style.css b/content/legacy/system/style.css index e38d325..46a7d49 100644 --- a/content/legacy/system/style.css +++ b/content/legacy/system/style.css @@ -18,7 +18,6 @@ html { color: #000; font: 16px Helvetica, Arial, sans-serif; line-height: 1.3; - } .body--block { diff --git a/content/legacy/themes/Peafowl/header.php b/content/legacy/themes/Peafowl/header.php index edbc8db..f7e0e38 100644 --- a/content/legacy/themes/Peafowl/header.php +++ b/content/legacy/themes/Peafowl/header.php @@ -243,21 +243,18 @@ if (is_route('page') || is_route('plugin')) {