diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 07edf1c..52fa484 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,6 @@ jobs: env: tools: composer ini-values: default_charset='UTF-8' - key: cache-1633608016315 name: Release on PHP ${{ matrix.php-versions }} ${{ matrix.operating-system }} steps: - name: Checkout @@ -58,20 +57,12 @@ jobs: mkdir importing/{parse-users,parse-albums,no-parse} mv .package .. ls -la ../.package - - name: Archive lite - uses: thedoctor0/zip-release@master - with: - directory: "." - type: "zip" - filename: "${{ github.ref_name}}-lite.zip" - exclusions: "/*app/vendor/*" - name: Archive release uses: thedoctor0/zip-release@master with: directory: "." type: "zip" filename: "${{ github.ref_name}}.zip" - exclusions: "${{ github.ref_name}}-lite.zip" - name: Upload artifacts uses: ncipollo/release-action@v1 with: @@ -80,6 +71,5 @@ jobs: bodyFile: "../.package/${{ github.ref_name}}.txt" artifacts: > ../.package/${{ github.ref_name}}.txt, - ${{ github.ref_name}}.zip, - ${{ github.ref_name}}-lite.zip + ${{ github.ref_name}}.zip token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.package/4.0.12.txt b/.package/4.0.12.txt deleted file mode 100644 index 1108a05..0000000 --- a/.package/4.0.12.txt +++ /dev/null @@ -1,14 +0,0 @@ -Chevereto 4.0.12 (2024-03-12) - -- ✅ Added cipher (encryption) to album password -- ✅ Added hidden (click to show) album password -- 🐞 Fixed bug in `entrypoints/index.php` -- 🐞 Fixed bug in API route -- 🐞 Fixed bug in Dashboard route -- 🐞 Fixed bug in Settings route -- 🐞 Fixed bug in Signup route -- 🐞 Fixed bug in G\Handler -- 🐞 Fixed bug in `app/upgrading.php` -- 🐞 Fixed bug in empty search string -- 💅 Improved license key handling (mobile) at Dashboard -- 🆙 Updated dependencies diff --git a/.package/4.1.0.txt b/.package/4.1.0.txt new file mode 100644 index 0000000..9e4bf0a --- /dev/null +++ b/.package/4.1.0.txt @@ -0,0 +1,30 @@ +Chevereto 4.1.0 (2024-04-20) + +- 🎥 Added support for video files +- 🎥 Added frame image size for video uploads +- 🎥 Added oEmbed support for video +- 🎥 Added video support for anywhere uploader +- 🎥 Added video support for embed codes +- 🎥 Added Video to Discovery (Explore) +- ✅ Added display_title media property +- ✅ Added enabled file type options for External Storage +- ✅ Improved support for PUP.js for SMF 2.1+ +- ✅ Unified embed codes display +- 💅 Added display_title to full screen listing viewer +- 💅 Added edition name at installation details +- 💅 Added one-click theme font configuration +- 💅 Added over effect to display_title on listings +- 💅 Added video duration on listing items +- 💅 Changed "image" references to "file" +- 💅 Changed Trending icon +- 💅 Improved listing icons for like and share +- 💅 Improved password display helper when using Safari +- 💅 Improved uploader size limit display +- 💅 Stop preventing list-item-desc link behavior +- ⏲️ Added button to manually run CRON +- 💬 Added link to Chevereto discord at Dashboard +- 🤚 Added welcome message on new installation +- 🐞 Fixed bug in homepage delete cover action +- 🐞 Fixed bug on upload route +- 🐞 Fixed bug with password protected albums +- 🐞 Fixed unwanted scroll hide top-bar in Safari diff --git a/.package/README.txt b/.package/README.txt index cf50934..65b7aa0 100755 --- a/.package/README.txt +++ b/.package/README.txt @@ -15,6 +15,8 @@ trusting our work. This software exists thanks to your ongoing support. ~ + Rodolfo Berrios A. + https://rodolfoberrios.com/ - GRACIAS + GRACIAS! (Thank you!) diff --git a/README.md b/README.md index 66d774d..147a265 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,8 @@ Chevereto is so **feature-rich**, mature and robust that we need three layers of - [User manual](https://v4-user.chevereto.com/) - [Admin manual](https://v4-admin.chevereto.com/) +## Compare features + **Note:** This is the repository for Chevereto free edition. This software is intended for **personal usage** as it doesn't contain [all the features](https://chevereto.com/features) of commercial editions. This is a short, not exhaustive, list of features available on Chevereto editions. Feel free to request a free demo of the pro edition at [chevereto.com](https://chevereto.com) to see all the features in action. | Feature | Free | Pro | diff --git a/app/composer.json b/app/composer.json index 4444acb..ccccaaf 100644 --- a/app/composer.json +++ b/app/composer.json @@ -44,7 +44,8 @@ "chillerlan/php-qrcode": "^4.3", "firebase/php-jwt": "^6.3", "lychee-org/php-exif": "^0.7.14", - "php-ds/php-ds": "^1.4" + "php-ds/php-ds": "^1.4", + "php-ffmpeg/php-ffmpeg": "^1.2" }, "autoload": { "files": [ diff --git a/app/composer.lock b/app/composer.lock index 235c81c..15ad497 100644 --- a/app/composer.lock +++ b/app/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "64b1e901d23988eeee99248e4f08a229", + "content-hash": "0bbc3dd1b9244ce1f02d783e5b5ff001", "packages": [ { "name": "amphp/amp", - "version": "v2.6.2", + "version": "v2.6.4", "source": { "type": "git", "url": "https://github.com/amphp/amp.git", - "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb" + "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/amp/zipball/9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", - "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", + "url": "https://api.github.com/repos/amphp/amp/zipball/ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", + "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", "shasum": "" }, "require": { @@ -29,8 +29,8 @@ "ext-json": "*", "jetbrains/phpstorm-stubs": "^2019.3", "phpunit/phpunit": "^7 | ^8 | ^9", - "psalm/phar": "^3.11@dev", - "react/promise": "^2" + "react/promise": "^2", + "vimeo/psalm": "^3.12" }, "type": "library", "extra": { @@ -85,7 +85,7 @@ "support": { "irc": "irc://irc.freenode.org/amphp", "issues": "https://github.com/amphp/amp/issues", - "source": "https://github.com/amphp/amp/tree/v2.6.2" + "source": "https://github.com/amphp/amp/tree/v2.6.4" }, "funding": [ { @@ -93,20 +93,20 @@ "type": "github" } ], - "time": "2022-02-20T17:52:18+00:00" + "time": "2024-03-21T18:52:26+00:00" }, { "name": "amphp/byte-stream", - "version": "v1.8.1", + "version": "v1.8.2", "source": { "type": "git", "url": "https://github.com/amphp/byte-stream.git", - "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd" + "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/byte-stream/zipball/acbd8002b3536485c997c4e019206b3f10ca15bd", - "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/4f0e968ba3798a423730f567b1b50d3441c16ddc", + "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc", "shasum": "" }, "require": { @@ -122,11 +122,6 @@ "psalm/phar": "^3.11.4" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, "autoload": { "files": [ "lib/functions.php" @@ -150,7 +145,7 @@ } ], "description": "A stream abstraction to make working with non-blocking I/O simple.", - "homepage": "http://amphp.org/byte-stream", + "homepage": "https://amphp.org/byte-stream", "keywords": [ "amp", "amphp", @@ -160,9 +155,8 @@ "stream" ], "support": { - "irc": "irc://irc.freenode.org/amphp", "issues": "https://github.com/amphp/byte-stream/issues", - "source": "https://github.com/amphp/byte-stream/tree/v1.8.1" + "source": "https://github.com/amphp/byte-stream/tree/v1.8.2" }, "funding": [ { @@ -170,7 +164,7 @@ "type": "github" } ], - "time": "2021-03-30T17:13:30+00:00" + "time": "2024-04-13T18:00:56+00:00" }, { "name": "amphp/parallel", @@ -248,16 +242,16 @@ }, { "name": "amphp/parser", - "version": "v1.1.0", + "version": "v1.1.1", "source": { "type": "git", "url": "https://github.com/amphp/parser.git", - "reference": "ff1de4144726c5dad5fab97f66692ebe8de3e151" + "reference": "3cf1f8b32a0171d4b1bed93d25617637a77cded7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/parser/zipball/ff1de4144726c5dad5fab97f66692ebe8de3e151", - "reference": "ff1de4144726c5dad5fab97f66692ebe8de3e151", + "url": "https://api.github.com/repos/amphp/parser/zipball/3cf1f8b32a0171d4b1bed93d25617637a77cded7", + "reference": "3cf1f8b32a0171d4b1bed93d25617637a77cded7", "shasum": "" }, "require": { @@ -298,7 +292,7 @@ ], "support": { "issues": "https://github.com/amphp/parser/issues", - "source": "https://github.com/amphp/parser/tree/v1.1.0" + "source": "https://github.com/amphp/parser/tree/v1.1.1" }, "funding": [ { @@ -306,26 +300,26 @@ "type": "github" } ], - "time": "2022-12-30T18:08:47+00:00" + "time": "2024-03-21T19:16:53+00:00" }, { "name": "amphp/process", - "version": "v1.1.5", + "version": "v1.1.7", "source": { "type": "git", "url": "https://github.com/amphp/process.git", - "reference": "04b4517bbfe436ab822b853d511165dafbfe115a" + "reference": "1949d85b6d71af2818ff68144304a98495628f19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/process/zipball/04b4517bbfe436ab822b853d511165dafbfe115a", - "reference": "04b4517bbfe436ab822b853d511165dafbfe115a", + "url": "https://api.github.com/repos/amphp/process/zipball/1949d85b6d71af2818ff68144304a98495628f19", + "reference": "1949d85b6d71af2818ff68144304a98495628f19", "shasum": "" }, "require": { "amphp/amp": "^2", "amphp/byte-stream": "^1.4", - "php": ">=7" + "php": ">=7.1" }, "require-dev": { "amphp/php-cs-fixer-config": "dev-master", @@ -363,7 +357,7 @@ "homepage": "https://github.com/amphp/process", "support": { "issues": "https://github.com/amphp/process/issues", - "source": "https://github.com/amphp/process/tree/v1.1.5" + "source": "https://github.com/amphp/process/tree/v1.1.7" }, "funding": [ { @@ -371,7 +365,7 @@ "type": "github" } ], - "time": "2024-02-24T21:06:11+00:00" + "time": "2024-04-19T03:00:28+00:00" }, { "name": "amphp/serialization", @@ -1155,28 +1149,28 @@ }, { "name": "composer/ca-bundle", - "version": "1.4.1", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "3ce240142f6d59b808dd65c1f52f7a1c252e6cfd" + "reference": "0c5ccfcfea312b5c5a190a21ac5cef93f74baf99" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/3ce240142f6d59b808dd65c1f52f7a1c252e6cfd", - "reference": "3ce240142f6d59b808dd65c1f52f7a1c252e6cfd", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/0c5ccfcfea312b5c5a190a21ac5cef93f74baf99", + "reference": "0c5ccfcfea312b5c5a190a21ac5cef93f74baf99", "shasum": "" }, "require": { "ext-openssl": "*", "ext-pcre": "*", - "php": "^5.3.2 || ^7.0 || ^8.0" + "php": "^7.2 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^0.12.55", + "phpstan/phpstan": "^1.10", "psr/log": "^1.0", "symfony/phpunit-bridge": "^4.2 || ^5", - "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0" + "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, "type": "library", "extra": { @@ -1211,7 +1205,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.4.1" + "source": "https://github.com/composer/ca-bundle/tree/1.5.0" }, "funding": [ { @@ -1227,7 +1221,7 @@ "type": "tidelift" } ], - "time": "2024-02-23T10:16:52+00:00" + "time": "2024-03-15T14:00:32+00:00" }, { "name": "evenement/evenement", @@ -3595,16 +3589,16 @@ }, { "name": "react/http", - "version": "v1.9.0", + "version": "v1.10.0", "source": { "type": "git", "url": "https://github.com/reactphp/http.git", - "reference": "bb3154dbaf2dfe3f0467f956a05f614a69d5f1d0" + "reference": "8111281ee57f22b7194f5dba225e609ba7ce4d20" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/http/zipball/bb3154dbaf2dfe3f0467f956a05f614a69d5f1d0", - "reference": "bb3154dbaf2dfe3f0467f956a05f614a69d5f1d0", + "url": "https://api.github.com/repos/reactphp/http/zipball/8111281ee57f22b7194f5dba225e609ba7ce4d20", + "reference": "8111281ee57f22b7194f5dba225e609ba7ce4d20", "shasum": "" }, "require": { @@ -3615,14 +3609,13 @@ "react/event-loop": "^1.2", "react/promise": "^3 || ^2.3 || ^1.2.1", "react/socket": "^1.12", - "react/stream": "^1.2", - "ringcentral/psr7": "^1.2" + "react/stream": "^1.2" }, "require-dev": { "clue/http-proxy-react": "^1.8", "clue/reactphp-ssh-proxy": "^1.4", "clue/socks-react": "^1.4", - "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", "react/async": "^4 || ^3 || ^2", "react/promise-stream": "^1.4", "react/promise-timer": "^1.9" @@ -3675,7 +3668,7 @@ ], "support": { "issues": "https://github.com/reactphp/http/issues", - "source": "https://github.com/reactphp/http/tree/v1.9.0" + "source": "https://github.com/reactphp/http/tree/v1.10.0" }, "funding": [ { @@ -3683,7 +3676,7 @@ "type": "open_collective" } ], - "time": "2023-04-26T10:29:24+00:00" + "time": "2024-03-27T17:20:46+00:00" }, { "name": "react/promise", @@ -3916,67 +3909,6 @@ ], "time": "2023-06-16T10:52:11+00:00" }, - { - "name": "ringcentral/psr7", - "version": "1.3.0", - "source": { - "type": "git", - "url": "https://github.com/ringcentral/psr7.git", - "reference": "360faaec4b563958b673fb52bbe94e37f14bc686" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ringcentral/psr7/zipball/360faaec4b563958b673fb52bbe94e37f14bc686", - "reference": "360faaec4b563958b673fb52bbe94e37f14bc686", - "shasum": "" - }, - "require": { - "php": ">=5.3", - "psr/http-message": "~1.0" - }, - "provide": { - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "files": [ - "src/functions_include.php" - ], - "psr-4": { - "RingCentral\\Psr7\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - } - ], - "description": "PSR-7 message implementation", - "keywords": [ - "http", - "message", - "stream", - "uri" - ], - "support": { - "source": "https://github.com/ringcentral/psr7/tree/master" - }, - "time": "2018-05-29T20:21:04+00:00" - }, { "name": "rodber/php-sse-react", "version": "0.1.0", @@ -4151,16 +4083,16 @@ }, { "name": "symfony/cache", - "version": "v5.4.36", + "version": "v5.4.38", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "a30f316214d908cf5874f700f3f3fb29ceee91ba" + "reference": "223c3afac82e003a76931b71d77db408636a0de8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/a30f316214d908cf5874f700f3f3fb29ceee91ba", - "reference": "a30f316214d908cf5874f700f3f3fb29ceee91ba", + "url": "https://api.github.com/repos/symfony/cache/zipball/223c3afac82e003a76931b71d77db408636a0de8", + "reference": "223c3afac82e003a76931b71d77db408636a0de8", "shasum": "" }, "require": { @@ -4228,7 +4160,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v5.4.36" + "source": "https://github.com/symfony/cache/tree/v5.4.38" }, "funding": [ { @@ -4244,20 +4176,20 @@ "type": "tidelift" } ], - "time": "2024-02-19T13:08:14+00:00" + "time": "2024-03-19T09:55:32+00:00" }, { "name": "symfony/cache-contracts", - "version": "v2.5.2", + "version": "v2.5.3", "source": { "type": "git", "url": "https://github.com/symfony/cache-contracts.git", - "reference": "64be4a7acb83b6f2bf6de9a02cee6dad41277ebc" + "reference": "fee6db04d913094e2fb55ff8e7db5685a8134463" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/64be4a7acb83b6f2bf6de9a02cee6dad41277ebc", - "reference": "64be4a7acb83b6f2bf6de9a02cee6dad41277ebc", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/fee6db04d913094e2fb55ff8e7db5685a8134463", + "reference": "fee6db04d913094e2fb55ff8e7db5685a8134463", "shasum": "" }, "require": { @@ -4307,7 +4239,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/cache-contracts/tree/v2.5.3" }, "funding": [ { @@ -4323,7 +4255,7 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:53:40+00:00" + "time": "2024-01-23T13:51:25+00:00" }, { "name": "symfony/deprecation-contracts", @@ -5211,21 +5143,21 @@ }, { "name": "nikic/php-parser", - "version": "v4.18.0", + "version": "v4.19.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999" + "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999", - "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4e1b88d21c69391150ace211e9eaf05810858d0b", + "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b", "shasum": "" }, "require": { "ext-tokenizer": "*", - "php": ">=7.0" + "php": ">=7.1" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", @@ -5261,9 +5193,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.1" }, - "time": "2023-12-10T21:03:43+00:00" + "time": "2024-03-17T08:10:35+00:00" }, { "name": "phar-io/manifest", @@ -5385,16 +5317,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.60", + "version": "1.10.67", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "95dcea7d6c628a3f2f56d091d8a0219485a86bbe" + "reference": "16ddbe776f10da6a95ebd25de7c1dbed397dc493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/95dcea7d6c628a3f2f56d091d8a0219485a86bbe", - "reference": "95dcea7d6c628a3f2f56d091d8a0219485a86bbe", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/16ddbe776f10da6a95ebd25de7c1dbed397dc493", + "reference": "16ddbe776f10da6a95ebd25de7c1dbed397dc493", "shasum": "" }, "require": { @@ -5437,13 +5369,9 @@ { "url": "https://github.com/phpstan", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" } ], - "time": "2024-03-07T13:30:19+00:00" + "time": "2024-04-16T07:22:02+00:00" }, { "name": "phpunit/php-code-coverage", @@ -5766,16 +5694,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.17", + "version": "9.6.19", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "1a156980d78a6666721b7e8e8502fe210b587fcd" + "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1a156980d78a6666721b7e8e8502fe210b587fcd", - "reference": "1a156980d78a6666721b7e8e8502fe210b587fcd", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1a54a473501ef4cdeaae4e06891674114d79db8", + "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8", "shasum": "" }, "require": { @@ -5849,7 +5777,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.17" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.19" }, "funding": [ { @@ -5865,7 +5793,7 @@ "type": "tidelift" } ], - "time": "2024-02-23T13:14:51+00:00" + "time": "2024-04-05T04:35:58+00:00" }, { "name": "psy/psysh", @@ -6809,16 +6737,16 @@ }, { "name": "sebastian/resource-operations", - "version": "3.0.3", + "version": "3.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", "shasum": "" }, "require": { @@ -6830,7 +6758,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -6851,8 +6779,7 @@ "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" }, "funding": [ { @@ -6860,7 +6787,7 @@ "type": "github" } ], - "time": "2020-09-28T06:45:17+00:00" + "time": "2024-03-14T16:00:52+00:00" }, { "name": "sebastian/type", diff --git a/app/legacy/install/installer.php b/app/legacy/install/installer.php index 5c3fddc..506d58b 100644 --- a/app/legacy/install/installer.php +++ b/app/legacy/install/installer.php @@ -480,7 +480,7 @@ $settings_updates = [ ], '3.13.5' => null, '3.14.0' => [ - 'upload_enabled_image_formats' => 'jpg,png,bmp,gif,webp', + // 'upload_enabled_image_formats' => 'jpg,png,bmp,gif,webp', ], '3.14.1' => null, '3.15.0' => [ @@ -596,6 +596,11 @@ $settings_updates = [ ], '4.0.11' => null, '4.0.12' => null, + '4.1.0' => [ + 'twitter_account' => '', + 'theme_font' => '0', + 'upload_enabled_image_formats' => 'jpg,png,bmp,gif,webp,mp4,webm', + ], ]; $cheveretoFreeMap = [ '1.0.0' => '3.8.3', @@ -1827,6 +1832,42 @@ ALTER TABLE `%table_prefix%users` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8m ], ] ], + '4.1.0' => [ + 'storages' => [ + 'storage_type_chain' => [ + 'op' => 'ADD', + 'type' => 'tinyint(3) UNSIGNED NOT NULL', + 'prop' => 'DEFAULT "1"', + ] + ], + 'images' => [ + 'image_size' => [ + 'op' => 'MODIFY', + 'type' => 'bigint(11)', + 'prop' => "UNSIGNED NOT NULL", + ], + 'image_frame_size' => [ + 'op' => 'ADD', + 'type' => 'int(11)', + 'prop' => "NOT NULL DEFAULT '0'", + ], + 'image_duration' => [ + 'op' => 'ADD', + 'type' => 'int(11)', + 'prop' => "NOT NULL DEFAULT '0'", + ], + 'image_type' => [ + 'op' => 'ADD', + 'type' => 'tinyint(3) UNSIGNED', + 'prop' => "as (case + when `image_extension` in ('pdf', 'doc', 'md') then 4 + when `image_extension` in ('mp3', 'm4a', 'wav') then 3 + when `image_extension` in ('mp4', 'webm') then 2 + when `image_extension` in ('jpg', 'jpeg', 'gif', 'png', 'webp') then 1 + else 0 end) stored" + ], + ] + ], ]; $sql_update = []; if (!$maintenance) { diff --git a/app/legacy/install/template/finished.php b/app/legacy/install/template/finished.php index ec02992..f4a98f8 100644 --- a/app/legacy/install/template/finished.php +++ b/app/legacy/install/template/finished.php @@ -7,9 +7,9 @@ if (!defined('ACCESS') || !ACCESS) { die('This file cannot be directly accessed.'); } ?>

Installation complete

-

admin dashboard and configure your website.', ['%s' => get_chevereto_version(true), '%u' => get_base_url('dashboard')]); ?>

+

admin dashboard and configure your website.', ['%s' => get_chevereto_version(true), '%u' => get_base_url('dashboard/?welcome')]); ?>

- Dashboard + Dashboard Website
diff --git a/app/legacy/install/template/ready.php b/app/legacy/install/template/ready.php index 6cdf326..335eeab 100644 --- a/app/legacy/install/template/ready.php +++ b/app/legacy/install/template/ready.php @@ -25,7 +25,7 @@ if (!defined('ACCESS') || !ACCESS) {
- +
diff --git a/app/legacy/load/app.php b/app/legacy/load/app.php index 165db39..dd7d18d 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.0.12'; -const APP_VERSION_AKA = 'macanudo'; +const APP_VERSION = '4.1.0'; +const APP_VERSION_AKA = 'pulento'; diff --git a/app/legacy/load/web.php b/app/legacy/load/web.php index cc2023a..adc2cc9 100644 --- a/app/legacy/load/web.php +++ b/app/legacy/load/web.php @@ -12,6 +12,7 @@ use Chevereto\Config\Config; use function Chevereto\Legacy\badgePaid; use Chevereto\Legacy\Classes\DB; +use Chevereto\Legacy\Classes\Fonts; use Chevereto\Legacy\Classes\Image; use Chevereto\Legacy\Classes\IpBan; use Chevereto\Legacy\Classes\L10n; @@ -21,6 +22,7 @@ use Chevereto\Legacy\Classes\Palettes; use Chevereto\Legacy\Classes\RequestLog; use Chevereto\Legacy\Classes\Settings; use Chevereto\Legacy\Classes\User; +use function Chevereto\Legacy\editionCombo; use function Chevereto\Legacy\G\get_base_url; use function Chevereto\Legacy\G\get_current_url; use Chevereto\Legacy\G\Handler; @@ -130,10 +132,10 @@ $hook_before = function (Handler $handler) { if ($handler::cond('content_manager')) { $moderateLink = get_base_url('moderate'); $moderateLabel = _s('Moderate'); - if (!(bool) env()['CHEVERETO_ENABLE_MODERATION']) { + if (!in_array('pro', editionCombo()[env()['CHEVERETO_EDITION']])) { if ((bool) env()['CHEVERETO_ENABLE_EXPOSE_PAID_FEATURES']) { $moderateLink = 'https://chevereto.com/pricing'; - $moderateLabel .= ' ' . badgePaid(); + $moderateLabel .= ' ' . badgePaid('pro'); } else { $showContentManager = false; } @@ -160,6 +162,10 @@ $hook_before = function (Handler $handler) { $handler::setVar('canonical', null); $palettes = new Palettes(); $handler::setVar('palettes', $palettes); + $fonts = new Fonts(); + $handler::setVar('fonts', $fonts); + $fontId = intval(getSetting('theme_font') ?? 0); + $handler::setVar('theme_font', $fontId); if (in_array($handler->request_array()[0], ['login', 'signup', 'account'])) { $paletteId = 0; } else { @@ -353,12 +359,16 @@ $hook_before = function (Handler $handler) { ], 'trending' => [ 'label' => _s('Trending'), - 'icon' => 'fas fa-poll', + 'icon' => 'fas fa-chart-simple', ], 'popular' => [ 'label' => _s('Popular'), 'icon' => 'fas fa-heart', ], + 'videos' => [ + 'label' => _s('Videos'), + 'icon' => 'fas fa-video', + ], 'animated' => [ 'label' => _s('Animated'), 'icon' => 'fas fa-play', @@ -370,7 +380,7 @@ $hook_before = function (Handler $handler) { if (!getSetting('enable_likes')) { unset($explore_semantics['popular']); } - if (!in_array('gif', Image::getEnabledImageFormats())) { + if (!in_array('gif', Image::getEnabledImageExtensions())) { unset($explore_semantics['animated']); } foreach ($explore_semantics as $k => &$v) { diff --git a/app/legacy/routes/account.php b/app/legacy/routes/account.php index 1daeaaf..8d38cb8 100644 --- a/app/legacy/routes/account.php +++ b/app/legacy/routes/account.php @@ -111,7 +111,7 @@ return function (Handler $handler) { $keysToCheck = array_keys($keysToCheck); if (in_array($doing, $keysToCheck)) { $request_log = RequestLog::getCounts($request_db_field, 'fail'); - $captcha_needed = getSettings()['captcha'] + $captcha_needed = (getSetting('captcha') ?? false) ? must_use_captcha($request_log['day']) : false; } @@ -264,7 +264,7 @@ return function (Handler $handler) { 'type' => $request_db_field, 'user_id' => $user['id'] ?? null ]); - if (getSettings()['captcha'] + if ((getSetting('captcha') ?? false) && isset($request_log) && must_use_captcha($request_log['day'] + 1)) { $captcha_needed = true; @@ -463,7 +463,7 @@ return function (Handler $handler) { 'user_id' => $user['id'] ?? null ] ); - if (getSettings()['captcha'] + if ((getSetting('captcha') ?? false) && isset($request_log) && must_use_captcha($request_log['day'] + 1)) { $captcha_needed = true; @@ -501,7 +501,7 @@ return function (Handler $handler) { 'result' => 'fail', 'user_id' => $logged_user['id'] ]); - if (getSettings()['captcha'] + if ((getSetting('captcha') ?? false) && isset($request_log) && must_use_captcha($request_log['day'] + 1)) { $captcha_needed = true; diff --git a/app/legacy/routes/album.php b/app/legacy/routes/album.php index 550d2a8..73bec6e 100644 --- a/app/legacy/routes/album.php +++ b/app/legacy/routes/album.php @@ -24,7 +24,6 @@ use function Chevereto\Legacy\G\url_to_relative; use function Chevereto\Legacy\get_share_links; use function Chevereto\Legacy\getIdFromURLComponent; use function Chevereto\Legacy\getSetting; -use function Chevereto\Legacy\getSettings; use function Chevereto\Legacy\is_max_invalid_request; use function Chevereto\Legacy\isShowEmbedContent; use function Chevereto\Legacy\must_use_captcha; @@ -149,7 +148,7 @@ return function (Handler $handler) { $handler::setCond('error', $is_error); $handler::setVar('error', $error_message); if ($is_error) { - if (getSettings()['captcha'] && must_use_captcha($failed_access_requests['day'] + 1)) { + if ((getSetting('captcha') ?? false) && must_use_captcha($failed_access_requests['day'] + 1)) { $captcha_needed = true; } $handler::setCond('captcha_needed', $captcha_needed); diff --git a/app/legacy/routes/api.php b/app/legacy/routes/api.php index 06c02a4..a9b69de 100644 --- a/app/legacy/routes/api.php +++ b/app/legacy/routes/api.php @@ -164,6 +164,7 @@ return function (Handler $handler) { 'title' => $REQUEST['title'] ?? $REQUEST['name'] ?? null, 'width' => $REQUEST['width'] ?? null, 'expiration' => $expiration, + 'mimetype' => $REQUEST['mimetype'] ?? 'image/jpeg', ]; $params = array_filter($params); if (!$handler::cond('content_manager') && getSetting('akismet')) { @@ -188,7 +189,7 @@ return function (Handler $handler) { $json_array['status'] = $json_array['status_code']; $image['id'] = $image['id_encoded']; } - $json_array['success'] = ['message' => 'image uploaded', 'code' => 200]; + $json_array['success'] = ['message' => 'file uploaded', 'code' => 200]; $json_array[$isImgBBSpec ? 'data' : 'image'] = $image; if ($version == 1) { diff --git a/app/legacy/routes/dashboard.php b/app/legacy/routes/dashboard.php index 0f2b9f5..7ddf3f6 100644 --- a/app/legacy/routes/dashboard.php +++ b/app/legacy/routes/dashboard.php @@ -15,6 +15,7 @@ use function Chevereto\Encryption\hasEncryption; use function Chevereto\Legacy\badgePaid; use Chevereto\Legacy\Classes\Akismet; use Chevereto\Legacy\Classes\Arachnid; +use Chevereto\Legacy\Classes\AssetStorage; use Chevereto\Legacy\Classes\DB; use Chevereto\Legacy\Classes\Image; use Chevereto\Legacy\Classes\L10n; @@ -26,7 +27,9 @@ use Chevereto\Legacy\Classes\Settings; use Chevereto\Legacy\Classes\Stat; use Chevereto\Legacy\Classes\Upload; use Chevereto\Legacy\Classes\User; +use function Chevereto\Legacy\editionCombo; use function Chevereto\Legacy\G\abbreviate_number; +use function Chevereto\Legacy\G\absolute_to_relative; use function Chevereto\Legacy\G\absolute_to_url; use function Chevereto\Legacy\G\check_value; use function Chevereto\Legacy\G\datetime_diff; @@ -122,44 +125,47 @@ return function (Handler $handler) { $route_prefix = 'dashboard'; $routes = [ 'stats' => _s('Home'), - 'images' => _s('Images'), + 'images' => _n('File', 'Files', 20), 'albums' => _n('Album', 'Albums', 20), 'users' => _n('User', 'Users', 20), 'bulk-importer' => _s('Bulk importer'), 'settings' => _s('Settings'), + 'run-cron' => _s('Run cron'), ]; $routesLinkLabels = $routes; $paidRoutes = []; $paidRoutesEnv = [ - 'images' => 'CHEVERETO_ENABLE_USERS', - 'albums' => 'CHEVERETO_ENABLE_USERS', - 'users' => 'CHEVERETO_ENABLE_USERS', - 'bulk-importer' => 'CHEVERETO_ENABLE_BULK_IMPORTER', + 'albums' => ['lite', 'CHEVERETO_ENABLE_USERS'], + 'bulk-importer' => ['pro', 'CHEVERETO_ENABLE_BULK_IMPORTER'], + 'images' => ['lite', 'CHEVERETO_ENABLE_USERS'], + 'users' => ['lite', 'CHEVERETO_ENABLE_USERS'], ]; foreach ($paidRoutesEnv as $k => $v) { - if (!(bool) env()['CHEVERETO_ENABLE_EXPOSE_PAID_FEATURES'] && !(bool) env()[$v]) { + $isEnabled = in_array($v[0], editionCombo()[env()['CHEVERETO_EDITION']]); + if (!(bool) env()['CHEVERETO_ENABLE_EXPOSE_PAID_FEATURES'] && !$isEnabled) { unset($routes[$k]); continue; } - if (!(bool) env()[$v]) { + if (!$isEnabled) { array_push($paidRoutes, $k); - $routes[$k] .= ' ' . badgePaid(); + $routes[$k] .= ' ' . badgePaid($v[0]); } } $icons = [ - 'stats' => 'fas fa-home', - 'images' => 'fas fa-image', 'albums' => 'fas fa-images', - 'users' => 'fas fa-users', - 'settings' => 'fas fa-cog', 'bulk-importer' => 'fas fa-layer-group', + 'images' => 'fas fa-photo-film', + 'run-cron' => 'fas fa-bolt', + 'settings' => 'fas fa-cog', + 'stats' => 'fas fa-home', + 'users' => 'fas fa-users', ]; $settings_sections = [ 'website' => _s('Website'), 'content' => _s('Content'), 'listings' => _s('Listings'), - 'image-upload' => _s('Image upload'), + 'file-upload' => _s('File upload'), 'semantics' => _s('Semantics'), 'categories' => _s('Categories'), 'theme' => _s('Theme'), @@ -168,24 +174,23 @@ return function (Handler $handler) { 'email' => _s('Email'), 'tools' => _s('Tools'), 'logo' => _s('Logo'), - 'external-storage' => _s('External storage'), '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'), + 'external-storage' => _s('External storage'), 'routing' => _s('Routing'), 'external-services' => _s('External services'), 'login-providers' => _s('Login providers'), - 'upload-plugin' => _s('Upload plugin'), 'cookie-compliance' => _s('Cookie compliance'), - 'consent-screen' => _s('Consent screen'), 'flood-protection' => _s('Flood protection'), 'banners' => _s('Banners'), 'ip-bans' => _s('IP bans'), - 'users' => _n('User', 'Users', 20), - 'guest-api' => _s('Guests %s', 'API'), 'watermarks' => _s('Watermarks'), ]; $settings_sections_icons = [ - 'upload-plugin' => 'fas fa-plus', 'banners' => 'fas fa-scroll', 'categories' => 'fas fa-columns', 'consent-screen' => 'fas fa-desktop', @@ -194,43 +199,43 @@ return function (Handler $handler) { 'email' => 'fas fa-at', 'external-services' => 'fas fa-concierge-bell', 'external-storage' => 'fas fa-hdd', + 'file-upload' => 'fas fa-cloud-upload-alt', 'flood-protection' => 'fas fa-faucet', 'guest-api' => 'fas fa-project-diagram', 'homepage' => 'fas fa-home', - 'image-upload' => 'fas fa-cloud-upload-alt', - 'semantics' => 'fas fa-sign-hanging', 'ip-bans' => 'fas fa-ban', 'languages' => 'fas fa-language', 'listings' => 'fas fa-th-list', - 'logo' => 'fas fa-gem', 'login-providers' => 'fas fa-right-to-bracket', + 'logo' => 'fas fa-gem', 'pages' => 'fas fa-file', 'routing' => 'fas fa-route', + 'semantics' => 'fas fa-sign-hanging', 'system' => 'fas fa-server', 'theme' => 'fas fa-paint-brush', 'tools' => 'fas fa-tools', - 'users' => 'fas fa-users-cog', - 'website' => 'fas fa-globe', 'upload-plugin' => 'fas fa-plug', + 'users' => 'fas fa-users-cog', 'watermarks' => 'fas fa-tint', + 'website' => 'fas fa-globe', ]; $paidSettingsEnv = [ - 'banners' => 'CHEVERETO_ENABLE_BANNERS', - 'consent-screen' => 'CHEVERETO_ENABLE_CONSENT_SCREEN', - 'cookie-compliance' => 'CHEVERETO_ENABLE_COOKIE_COMPLIANCE', - 'external-services' => 'CHEVERETO_ENABLE_EXTERNAL_SERVICES', - 'external-storage' => 'CHEVERETO_ENABLE_EXTERNAL_STORAGE', - 'flood-protection' => 'CHEVERETO_ENABLE_UPLOAD_FLOOD_PROTECTION', - 'guest-api' => 'CHEVERETO_ENABLE_API_GUEST', - 'homepage' => 'CHEVERETO_ENABLE_USERS', - 'ip-bans' => 'CHEVERETO_ENABLE_IP_BANS', - 'logo' => 'CHEVERETO_ENABLE_LOGO', - 'login-providers' => 'CHEVERETO_ENABLE_LOGIN_PROVIDERS', - 'pages' => 'CHEVERETO_ENABLE_PAGES', - 'routing' => 'CHEVERETO_ENABLE_ROUTING', - 'upload-plugin' => 'CHEVERETO_ENABLE_UPLOAD_PLUGIN', - 'users' => 'CHEVERETO_ENABLE_USERS', - 'watermarks' => 'CHEVERETO_ENABLE_UPLOAD_WATERMARK', + 'banners' => ['pro', 'CHEVERETO_ENABLE_BANNERS'], + 'consent-screen' => ['lite', 'CHEVERETO_ENABLE_CONSENT_SCREEN'], + 'cookie-compliance' => ['pro', 'CHEVERETO_ENABLE_COOKIE_COMPLIANCE'], + 'external-services' => ['pro', 'CHEVERETO_ENABLE_EXTERNAL_SERVICES'], + 'external-storage' => ['pro', 'CHEVERETO_ENABLE_EXTERNAL_STORAGE'], + 'flood-protection' => ['pro', 'CHEVERETO_ENABLE_UPLOAD_FLOOD_PROTECTION'], + 'guest-api' => ['lite', 'CHEVERETO_ENABLE_API_GUEST'], + 'homepage' => ['lite', 'CHEVERETO_ENABLE_USERS'], + 'ip-bans' => ['pro', 'CHEVERETO_ENABLE_IP_BANS'], + 'login-providers' => ['pro', 'CHEVERETO_ENABLE_LOGIN_PROVIDERS'], + 'logo' => ['lite', 'CHEVERETO_ENABLE_LOGO'], + 'pages' => ['lite', 'CHEVERETO_ENABLE_PAGES'], + 'routing' => ['pro', 'CHEVERETO_ENABLE_ROUTING'], + 'upload-plugin' => ['lite', 'CHEVERETO_ENABLE_UPLOAD_PLUGIN'], + 'users' => ['lite', 'CHEVERETO_ENABLE_USERS'], + 'watermarks' => ['pro', 'CHEVERETO_ENABLE_UPLOAD_WATERMARK'], ]; $paidSettings = []; $default_route = 'stats'; @@ -261,6 +266,8 @@ return function (Handler $handler) { $handler::setVar('docsBaseUrl', 'https://v4-docs.chevereto.com/'); $handler::setVar('adminDocsBaseUrl', 'https://v4-admin.chevereto.com/'); $handler::setVar('userDocsBaseUrl', 'https://v4-user.chevereto.com/'); + // hidden routes + unset($route_menu['run-cron'], $routes['run-cron'], $routesLinkLabels['run-cron']); $handler::setVar($route_prefix . '_menu', $route_menu); $handler::setVar('tabs', $route_menu); $is_error = false; @@ -273,7 +280,33 @@ return function (Handler $handler) { if (in_array($doing, $paidRoutes)) { $handler->issueError(404); } + switch ($doing) { + case 'run-cron': + if (!$handler::checkAuthToken(request()['auth_token'] ?? '')) { + $handler->issueError(403); + + return; + } + ini_set('log_errors', true); + ini_set('display_errors', true); + ignore_user_abort(true); + @set_time_limit(0); + ini_set('default_charset', 'utf-8'); + setlocale(LC_ALL, 'en_US.UTF8'); + ini_set('output_buffering', 'off'); + ini_set('zlib.output_compression', false); + echo << + Trigger cron tasks (HTTP API) + -- + + PLAIN; + require_once PATH_APP_LEGACY . 'commands/cron.php'; + echo ''; + die(0); + + break; case 'stats': if (version_compare(getSetting('chevereto_version_installed'), '3.7.0', '<')) { $totals = []; @@ -364,7 +397,12 @@ return function (Handler $handler) { } $install_update_button = ''; $version_check = ''; - $cronRemark = ''; + $cronRemark = '' + . _s('Run cron') + . ' '; $errorLogRemark = ''; $cron_last_ran = Settings::get('cron_last_ran'); if (env()['CHEVERETO_CONTEXT'] !== 'saas') { @@ -387,9 +425,9 @@ return function (Handler $handler) { 'chv_version' => [ 'label' => '
', 'content' => '
' - . '

' + . '

' . $chv_version['files'] - . '' . APP_VERSION_AKA . '

' + . '' . APP_VERSION_AKA . '

' . $install_update_button . '
' . $version_check . $linksButtons . '
' @@ -403,7 +441,7 @@ return function (Handler $handler) { ], 'rebuild_stats' => [ 'label' => _s('Stats'), - 'content' => ' ' . _s('Rebuild stats') . '' + 'content' => '' . _s('Rebuild stats') . '' ], 'connecting_ip' => [ 'label' => _s('Connecting IP'), @@ -413,9 +451,18 @@ return function (Handler $handler) { 'label' => _s('Encryption'), 'content' => ' ' . (hasEncryption() ? _s('Enabled') : _s('Disabled')) ], + 'meta' => [ + 'label' => _s('Meta'), + 'content' => 'CheverexrDebug' + ], ]; $cheveretoLinks = [ + [ + 'label' => _s('Blog'), + 'icon' => 'fas fa-blog', + 'href' => 'https://blog.chevereto.com' + ], [ 'label' => _s('Docs'), 'icon' => 'fas fa-book', @@ -431,6 +478,11 @@ return function (Handler $handler) { 'icon' => 'fas fa-medkit', 'href' => 'https://chevereto.com/support' ], + [ + 'label' => _s('Chat'), + 'icon' => 'fas fa-comments', + 'href' => 'https://chevereto.com/go/discord' + ], [ 'label' => _s('Community'), 'icon' => 'fas fa-users', @@ -474,20 +526,30 @@ return function (Handler $handler) { ], 'server' => [ 'label' => _s('Server'), - 'content' => ' ' . ((server()['SERVER_SOFTWARE'] ?? '🐍') . ' ~ ' . gethostname() . ' ' . PHP_OS . '/' . PHP_SAPI . ((env()['CHEVERETO_SERVICING'] ?? null) === 'docker' ? ' Docker' : '')) + 'content' => ' ' + . ( + (server()['SERVER_SOFTWARE'] ?? '🐍') + . ' ~ ' . gethostname() + . '
' + . PHP_OS + . '/' + . PHP_SAPI + . ((env()['CHEVERETO_SERVICING'] ?? null) === 'docker' + ? ' Docker' + : '') + ) ], - 'mysql_version' => [ - 'label' => _s('MySQL version'), - 'content' => ' ' . $mysqlVersion - ], - 'mysql_server_info' => [ - 'label' => _s('MySQL server info'), - 'content' => ' ' . $mysqlServerInfo + 'mysql' => [ + 'label' => 'MySQL', + 'content' => ' ' + . $mysqlVersion + . '
' + . $mysqlServerInfo ], 'file_uploads' => [ 'label' => _s('File uploads'), 'content' => (int) ini_get('file_uploads') == 1 - ? ' ' . _s('Enabled') + ? ' ' . _s('Enabled') : ' ' . _s('Disabled') ], 'max_execution_time' => [ @@ -538,9 +600,10 @@ return function (Handler $handler) { $settingsSectionsTitles = $settings_sections; if ((bool) env()['CHEVERETO_ENABLE_EXPOSE_PAID_FEATURES']) { foreach ($paidSettingsEnv as $k => $v) { - if (!(bool) env()[$v]) { + $isEnabled = in_array($v[0], editionCombo()[env()['CHEVERETO_EDITION']]); + if (!$isEnabled) { array_push($paidSettings, $k); - $settings_sections[$k] .= ' ' . badgePaid(); + $settings_sections[$k] .= ' ' . badgePaid($v[0]); } } } @@ -576,10 +639,17 @@ return function (Handler $handler) { if (in_array($requestSetting, $paidSettings)) { $requestSetting = ''; $handler->issueError(404); + + return; } switch ($requestSetting) { case 'homepage': if ((get()['action'] ?? '') == 'delete-cover' && isset(get()['cover'])) { + if (!$handler::checkAuthToken(request()['auth_token'] ?? '')) { + $handler->issueError(403); + + return; + } $cover_index = get()['cover'] - 1; $homecovers = getSetting('homepage_cover_images'); $cover_target = $homecovers[$cover_index]; @@ -595,7 +665,8 @@ return function (Handler $handler) { // Try to delete the image (disk) if (!starts_with('default/', $cover_target['basename'])) { $cover_file = PATH_PUBLIC_CONTENT_IMAGES_SYSTEM . $cover_target['basename']; - unlinkIfExists($cover_file); + $storagePath = ltrim(absolute_to_relative($cover_file), '/'); + AssetStorage::deleteFiles(['key' => $storagePath]); } unset($homecovers[$cover_index]); $homecovers = array_values($homecovers); @@ -960,6 +1031,14 @@ return function (Handler $handler) { 'validate' => empty($POST['theme_logo_height']) ? (true) : is_integer($POST['theme_logo_height']), 'error_msg' => _s('Invalid value') ], + 'theme_font' => + [ + 'validate' => isset($POST['theme_font']) && in_array( + $POST['theme_font'], + array_keys($handler::var('fonts')->get()) + ), + 'error_msg' => _s('Invalid %s', _s('font')) + ], 'theme_palette' => [ 'validate' => isset($POST['theme_palette']) && in_array( @@ -1573,9 +1652,10 @@ return function (Handler $handler) { $is_changed = true; $reset_notices = false; $settings_to_vars = [ - 'website_doctitle' => 'doctitle', - 'website_description' => 'meta_description', - ]; + 'website_doctitle' => 'doctitle', + 'website_description' => 'meta_description', + 'theme_font' => 'theme_font' + ]; foreach (array_keys($update_settings) as $k) { if ($k == 'maintenance') { $reset_notices = true; diff --git a/app/legacy/routes/explore.php b/app/legacy/routes/explore.php index 249d18d..d6e67b9 100644 --- a/app/legacy/routes/explore.php +++ b/app/legacy/routes/explore.php @@ -65,6 +65,9 @@ return function (Handler $handler) { if ($doing == 'animated') { $listingParams['params_hidden']['is_animated'] = 1; } + if ($doing == 'videos') { + $listingParams['params_hidden']['is_video'] = 1; + } $getParams = Listing::getParams(request()); $tabs = Listing::getTabs($listingParams, $getParams, true); $currentKey = $tabs['currentKey']; diff --git a/app/legacy/routes/image.php b/app/legacy/routes/image.php index 962bc65..6eb81db 100644 --- a/app/legacy/routes/image.php +++ b/app/legacy/routes/image.php @@ -16,8 +16,9 @@ use Chevereto\Legacy\Classes\Login; use Chevereto\Legacy\Classes\User; use function Chevereto\Legacy\encodeID; use function Chevereto\Legacy\G\get_current_url; +use function Chevereto\Legacy\G\get_global; use Chevereto\Legacy\G\Handler; -use function Chevereto\Legacy\G\html_to_bbcode; +use function Chevereto\Legacy\G\include_theme_file; use function Chevereto\Legacy\G\is_animated_image; use function Chevereto\Legacy\G\redirect; use function Chevereto\Legacy\G\safe_html; @@ -55,7 +56,6 @@ return function (Handler $handler) { if (!isset(session()['image_view_stock'])) { sessionVar()->put('image_view_stock', []); } - $logged_user = Login::getUser(); User::statusRedirect($logged_user['status'] ?? null); $image = Image::getSingle($id, !in_array($id, session()['image_view_stock']), true, $logged_user); @@ -149,7 +149,7 @@ return function (Handler $handler) { : $image_safe_html['name'] . '.' . $image['extension'] . ' hosted at ' . getSetting('website_name'); $tabs = []; $tabs[] = [ - 'icon' => 'fas fa-image', + 'icon' => 'fas fa-list-ul', 'label' => _s('About'), 'id' => 'tab-about', 'current' => true, @@ -273,124 +273,46 @@ return function (Handler $handler) { } $handler::setVar('share_links_array', get_share_links()); $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'] ?? '', + ]; $embed = []; - $embed['direct-links'] = [ - 'label' => _s('Direct links'), - 'entries' => [ - [ - 'label' => _s('Image link'), - 'value' => $image['url_short'], - ], - [ - 'label' => _s('Image URL'), - 'value' => $image['url'], - ], - [ - 'label' => _s('Thumbnail URL'), - 'value' => $image['thumb']['url'] ?? '', - ], - ], - ]; - if (isset($image['medium'])) { - $embed['direct-links']['entries'][] = [ - 'label' => _s('Medium URL'), - 'value' => $image['medium']['url'] ?? '', - ]; - } - $image_full = [ - 'html' => '' . $image['filename'] . '', - 'markdown' => '![' . $image['filename'] . '](' . $image['url'] . ')', - ]; - $image_full['bbcode'] = html_to_bbcode($image_full['html']); - $embed['full-image'] = [ - 'label' => _s('Full image'), - 'entries' => [ - [ - 'label' => 'HTML', - 'value' => htmlentities($image_full['html']), - ], - [ - 'label' => 'BBCode', - 'value' => $image_full['bbcode'], - ], - [ - 'label' => 'Markdown', - 'value' => $image_full['markdown'], - ], - ], - ]; - $embed_full_linked['html'] = '' . $image_full['html'] . ''; - $embed_full_linked['bbcode'] = html_to_bbcode($embed_full_linked['html']); - $embed_full_linked['markdown'] = '[![' . $image['filename'] . '](' . $image['url'] . ')](' . $image['url_short'] . ')'; - $embed['full-linked'] = [ - 'label' => _s('Full image (linked)'), - 'entries' => [ - [ - 'label' => 'HTML', - 'value' => htmlentities($embed_full_linked['html']), - ], - [ - 'label' => 'BBCode', - 'value' => $embed_full_linked['bbcode'], - ], - [ - 'label' => 'Markdown', - 'value' => $embed_full_linked['markdown'], - ], - ], - ]; - if (isset($image['medium'])) { - $embed_medium_linked = [ - 'html' => '' . $image['filename'] . '', - ]; - $embed_medium_linked['bbcode'] = html_to_bbcode($embed_medium_linked['html']); - $embed_medium_linked['markdown'] = '[![' . $image['medium']['filename'] . '](' . $image['medium']['url'] . ')](' . $image['url_short'] . ')'; - $embed['medium-linked'] = [ - 'label' => _s('Medium image (linked)'), - 'entries' => [ - [ - 'label' => 'HTML', - 'value' => htmlentities($embed_medium_linked['html']), - ], - [ - 'label' => 'BBCode', - 'value' => $embed_medium_linked['bbcode'], - ], - [ - 'label' => 'Markdown', - 'value' => $embed_medium_linked['markdown'], - ], - ], - ]; - } - $embed_thumb_linked = [ - 'html' => '' . $image['filename'] . '', - ]; - $embed_thumb_linked['bbcode'] = html_to_bbcode($embed_thumb_linked['html']); - $embed_thumb_linked['markdown'] = '[![' . $image['thumb']['filename'] . '](' . $image['thumb']['url'] . ')](' . $image['url_short'] . ')'; - $embed['thumb-linked'] = [ - 'label' => _s('Thumbnail image (linked)'), - 'entries' => [ - [ - 'label' => 'HTML', - 'value' => htmlentities($embed_thumb_linked['html']), - ], - [ - 'label' => 'BBCode', - 'value' => $embed_thumb_linked['bbcode'], - ], - [ - 'label' => 'Markdown', - 'value' => $embed_thumb_linked['markdown'], - ], - ], - ]; - $embed_id = 1; - foreach ($embed as &$v) { - foreach ($v['entries'] as &$entry) { - $entry['id'] = 'embed-code-' . $embed_id; - ++$embed_id; + foreach ($embed_share_tpl as $code => $group) { + $entries = []; + $groupLabel = $group['label']; + foreach ($group['options'] as $option => $optionValue) { + $value = $optionValue['template']; + if (is_array($value)) { + $value = $value[$image['type']]; + } + $value = strtr($value, $sharing); + if ($value === '') { + continue; + } + if (str_contains($option, 'html')) { + $value = htmlentities($value); + } + $label = $optionValue['label']; + $label = str_ireplace($groupLabel, '', $label); + $label = ucfirst(trim($label)); + $entries[] = [ + 'label' => $label, + 'value' => $value, + 'id' => $option + ]; } + $embed[$code] = [ + 'label' => $group['label'], + 'entries' => $entries + ]; } $handler::setVar('embed', $embed); $addValue = session()['image_view_stock'] ?? []; diff --git a/app/legacy/routes/json.php b/app/legacy/routes/json.php index b3827a9..efcbdb5 100644 --- a/app/legacy/routes/json.php +++ b/app/legacy/routes/json.php @@ -130,7 +130,7 @@ return function (Handler $handler) { } $uploaded_id = intval($uploadToWebsite[0]); $json_array['status_code'] = 200; - $json_array['success'] = ['message' => 'image uploaded', 'code' => 200]; + $json_array['success'] = ['message' => 'file uploaded', 'code' => 200]; $image = Image::getSingle($uploaded_id); if ($image === []) { throw new LogicException( @@ -421,7 +421,7 @@ return function (Handler $handler) { 'image' => ['category_id', 'title', 'description', 'album_id', 'nsfw'], 'album' => ['name', 'privacy', 'album_id', 'description', 'password'], 'category' => ['name', 'description', 'url_key'], - 'storage' => ['name', 'bucket', 'region', 'url', 'server', 'capacity', 'is_https', 'is_active', 'api_id', 'key', 'secret', 'account_id', 'account_name'], + 'storage' => ['name', 'bucket', 'region', 'url', 'server', 'capacity', 'is_https', 'is_active', 'api_id', 'key', 'secret', 'account_id', 'account_name', 'type_chain'], 'ip_ban' => ['ip', 'expires', 'message'], ]; if (Handler::cond('content_manager')) { diff --git a/app/legacy/routes/login.php b/app/legacy/routes/login.php index 266668a..0f4a509 100644 --- a/app/legacy/routes/login.php +++ b/app/legacy/routes/login.php @@ -17,7 +17,6 @@ use Chevereto\Legacy\Classes\User; use Chevereto\Legacy\G\Handler; use function Chevereto\Legacy\G\redirect; use function Chevereto\Legacy\getSetting; -use function Chevereto\Legacy\getSettings; use function Chevereto\Legacy\must_use_captcha; use function Chevereto\Vars\env; use function Chevereto\Vars\post; @@ -111,7 +110,7 @@ return function (Handler $handler) { $request_log_insert['result'] = 'fail'; RequestLog::insert($request_log_insert); $error_message = _s('Wrong Username/Email password combination'); - if (getSettings()['captcha'] && must_use_captcha($failed_access_requests['day'] + 1)) { + if ((getSetting('captcha') ?? false) && must_use_captcha($failed_access_requests['day'] + 1)) { $captcha_needed = true; } } diff --git a/app/legacy/routes/oembed.php b/app/legacy/routes/oembed.php index bba1689..e5fa878 100644 --- a/app/legacy/routes/oembed.php +++ b/app/legacy/routes/oembed.php @@ -63,15 +63,25 @@ return function (Handler $handler) { } $data = [ 'version' => '1.0', - 'type' => 'photo', 'provider_name' => safe_html(Settings::get('website_name')), 'provider_url' => get_public_url(), 'title' => safe_html($image['title']), - 'url' => $image['display_url'], 'web_page' => $image['url_viewer'], 'width' => $image['width'], 'height' => $image['height'], ]; + switch ($image['type']) { + case 'video': + $data['html'] = ''; + $data['type'] = 'video'; + + break; + case 'image': + $data['url'] = $image['display_url']; + $data['type'] = 'photo'; + + break; + } if (isset($image['user'])) { $data = array_merge($data, [ 'author_name' => safe_html($image['user']['username']), diff --git a/app/legacy/routes/signup.php b/app/legacy/routes/signup.php index 812bb86..fddbd3d 100644 --- a/app/legacy/routes/signup.php +++ b/app/legacy/routes/signup.php @@ -226,7 +226,7 @@ return function (Handler $handler) { 'result' => 'fail' ]); $error_message = $error_message ?? _s('Check the errors in the form to continue.'); - if (getSettings()['captcha'] && must_use_captcha($failed_access_requests['day'] + 1)) { + if ((getSetting('captcha') ?? false) && must_use_captcha($failed_access_requests['day'] + 1)) { $captcha_needed = true; } } diff --git a/app/legacy/routes/user.php b/app/legacy/routes/user.php index 2f390bb..f78756c 100644 --- a/app/legacy/routes/user.php +++ b/app/legacy/routes/user.php @@ -104,7 +104,7 @@ return function (Handler $handler) { $user_routes = []; $user_views = [ 'images' => [ - 'title' => _s("%t by %s", ['%t' => _s('Images')]), + 'title' => _s("%t by %s", ['%t' => _s('Media')]), 'title_short' => _s("Images"), ], 'albums' => [ @@ -270,9 +270,8 @@ return function (Handler $handler) { break; } - $icon = 'fas fa-id-card'; $icon = [ - 'images' => 'fas fa-id-card', + 'images' => 'fas fa-photo-film', 'albums' => 'fas fa-images', 'liked' => 'fas fa-heart', 'following' => 'fas fa-rss', diff --git a/app/schemas/mysql-5/images.sql b/app/schemas/mysql-5/images.sql index 14b785d..624ab19 100644 --- a/app/schemas/mysql-5/images.sql +++ b/app/schemas/mysql-5/images.sql @@ -3,7 +3,7 @@ CREATE TABLE `%table_prefix%images` ( `image_id` bigint(32) NOT NULL AUTO_INCREMENT, `image_name` varchar(255) NOT NULL, `image_extension` varchar(255) NOT NULL, - `image_size` int(11) NOT NULL, + `image_size` bigint(11) UNSIGNED NOT NULL, `image_width` int(11) NOT NULL, `image_height` int(11) NOT NULL, `image_date` datetime NOT NULL, @@ -23,14 +23,22 @@ CREATE TABLE `%table_prefix%images` ( `image_original_exifdata` longtext, `image_views` bigint(32) NOT NULL DEFAULT '0', `image_category_id` bigint(32) DEFAULT NULL, - `image_chain` tinyint(128) NOT NULL, + `image_chain` tinyint(3) NOT NULL, `image_thumb_size` int(11) NOT NULL, `image_medium_size` int(11) NOT NULL DEFAULT '0', + `image_frame_size` int(11) NOT NULL DEFAULT '0', `image_expiration_date_gmt` datetime DEFAULT NULL, `image_likes` bigint(32) NOT NULL DEFAULT '0', `image_is_animated` tinyint(1) NOT NULL DEFAULT '0', `image_is_approved` tinyint(1) NOT NULL DEFAULT '1', `image_is_360` tinyint(1) NOT NULL DEFAULT '0', + `image_duration` int(11) NOT NULL DEFAULT '0', + `image_type` tinyint(3) UNSIGNED as (case + when `image_extension` in ('pdf', 'doc', 'md') then 4 + when `image_extension` in ('mp3', 'm4a', 'wav') then 3 + when `image_extension` in ('mp4', 'webm') then 2 + when `image_extension` in ('jpg', 'jpeg', 'gif', 'png', 'webp') then 1 + else 0 end) stored, PRIMARY KEY (`image_id`), KEY `image_name` (`image_name`), KEY `image_extension` (`image_extension`), @@ -56,5 +64,7 @@ CREATE TABLE `%table_prefix%images` ( KEY `image_is_approved` (`image_is_approved`), KEY `image_is_360` (`image_is_360`), KEY `image_album_id_image_id` (`image_album_id`, `image_id`), + KEY `image_duration` (`image_duration`), + KEY `image_type` (`image_type`), FULLTEXT KEY `searchindex` (`image_name`,`image_title`,`image_description`,`image_original_filename`) ) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; diff --git a/app/schemas/mysql-5/storages.sql b/app/schemas/mysql-5/storages.sql index f6e3c1b..85ca51e 100644 --- a/app/schemas/mysql-5/storages.sql +++ b/app/schemas/mysql-5/storages.sql @@ -16,6 +16,7 @@ CREATE TABLE `%table_prefix%storages` ( `storage_is_active` tinyint(1) NOT NULL DEFAULT '0', `storage_capacity` bigint(32) DEFAULT NULL, `storage_space_used` bigint(32) DEFAULT '0', + `storage_type_chain` tinyint(3) NOT NULL DEFAULT '1', PRIMARY KEY (`storage_id`), KEY `storage_api_id` (`storage_api_id`), KEY `storage_is_active` (`storage_is_active`) diff --git a/app/schemas/mysql-8/images.sql b/app/schemas/mysql-8/images.sql index 0c8f5f0..8572b63 100644 --- a/app/schemas/mysql-8/images.sql +++ b/app/schemas/mysql-8/images.sql @@ -3,7 +3,7 @@ CREATE TABLE `%table_prefix%images` ( `image_id` bigint(32) NOT NULL AUTO_INCREMENT, `image_name` varchar(255) NOT NULL, `image_extension` varchar(255) NOT NULL, - `image_size` int(11) NOT NULL, + `image_size` bigint(11) UNSIGNED NOT NULL, `image_width` int(11) NOT NULL, `image_height` int(11) NOT NULL, `image_date` datetime NOT NULL, @@ -23,14 +23,22 @@ CREATE TABLE `%table_prefix%images` ( `image_original_exifdata` longtext, `image_views` bigint(32) NOT NULL DEFAULT '0', `image_category_id` bigint(32) DEFAULT NULL, - `image_chain` tinyint(128) NOT NULL, + `image_chain` tinyint(3) NOT NULL, `image_thumb_size` int(11) NOT NULL, `image_medium_size` int(11) NOT NULL DEFAULT '0', + `image_frame_size` int(11) NOT NULL DEFAULT '0', `image_expiration_date_gmt` datetime DEFAULT NULL, `image_likes` bigint(32) NOT NULL DEFAULT '0', `image_is_animated` tinyint(1) NOT NULL DEFAULT '0', `image_is_approved` tinyint(1) NOT NULL DEFAULT '1', `image_is_360` tinyint(1) NOT NULL DEFAULT '0', + `image_duration` int(11) NOT NULL DEFAULT '0', + `image_type` tinyint(3) UNSIGNED as (case + when `image_extension` in ('pdf', 'doc', 'md') then 4 + when `image_extension` in ('mp3', 'm4a', 'wav') then 3 + when `image_extension` in ('mp4', 'webm') then 2 + when `image_extension` in ('jpg', 'jpeg', 'gif', 'png', 'webp') then 1 + else 0 end) stored, PRIMARY KEY (`image_id`), KEY `image_name` (`image_name`), KEY `image_extension` (`image_extension`), @@ -56,5 +64,7 @@ CREATE TABLE `%table_prefix%images` ( KEY `image_is_approved` (`image_is_approved`), KEY `image_is_360` (`image_is_360`), KEY `image_album_id_image_id` (`image_album_id`, `image_id`), + KEY `image_duration` (`image_duration`), + KEY `image_type` (`image_type`), FULLTEXT KEY `searchindex` (`image_name`,`image_title`,`image_description`,`image_original_filename`) -) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4; \ No newline at end of file +) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4; diff --git a/app/schemas/mysql-8/storages.sql b/app/schemas/mysql-8/storages.sql index f300bbb..152e7d0 100644 --- a/app/schemas/mysql-8/storages.sql +++ b/app/schemas/mysql-8/storages.sql @@ -16,7 +16,8 @@ CREATE TABLE `%table_prefix%storages` ( `storage_is_active` tinyint(1) NOT NULL DEFAULT '0', `storage_capacity` bigint(32) DEFAULT NULL, `storage_space_used` bigint(32) DEFAULT '0', + `storage_type_chain` tinyint(3) NOT NULL DEFAULT '1', PRIMARY KEY (`storage_id`), KEY `storage_api_id` (`storage_api_id`), KEY `storage_is_active` (`storage_is_active`) -) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4; \ No newline at end of file +) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4; diff --git a/app/src/Legacy/Classes/Album.php b/app/src/Legacy/Classes/Album.php index 86f863e..48afabe 100644 --- a/app/src/Legacy/Classes/Album.php +++ b/app/src/Legacy/Classes/Album.php @@ -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']; + } } } } diff --git a/app/src/Legacy/Classes/DB.php b/app/src/Legacy/Classes/DB.php index ea1f82c..851de82 100644 --- a/app/src/Legacy/Classes/DB.php +++ b/app/src/Legacy/Classes/DB.php @@ -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( diff --git a/app/src/Legacy/Classes/Fonts.php b/app/src/Legacy/Classes/Fonts.php new file mode 100644 index 0000000..7a0d86b --- /dev/null +++ b/app/src/Legacy/Classes/Fonts.php @@ -0,0 +1,76 @@ + + * + * 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; + } +} diff --git a/app/src/Legacy/Classes/Image.php b/app/src/Legacy/Classes/Image.php index 3d6a63d..89a714d 100644 --- a/app/src/Legacy/Classes/Image.php +++ b/app/src/Legacy/Classes/Image.php @@ -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; + } } diff --git a/app/src/Legacy/Classes/Listing.php b/app/src/Legacy/Classes/Listing.php index 68dd03b..ec75c3d 100644 --- a/app/src/Legacy/Classes/Listing.php +++ b/app/src/Legacy/Classes/Listing.php @@ -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']) { diff --git a/app/src/Legacy/Classes/Settings.php b/app/src/Legacy/Classes/Settings.php index e724271..68a101f 100644 --- a/app/src/Legacy/Classes/Settings.php +++ b/app/src/Legacy/Classes/Settings.php @@ -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; diff --git a/app/src/Legacy/Classes/Upload.php b/app/src/Legacy/Classes/Upload.php index 077426e..bc10c11 100644 --- a/app/src/Legacy/Classes/Upload.php +++ b/app/src/Legacy/Classes/Upload.php @@ -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']); diff --git a/app/src/Legacy/Classes/User.php b/app/src/Legacy/Classes/User.php index 1d33eb6..0bade8d 100644 --- a/app/src/Legacy/Classes/User.php +++ b/app/src/Legacy/Classes/User.php @@ -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) diff --git a/app/src/Legacy/G/DB.php b/app/src/Legacy/G/DB.php index c17659c..44fe4de 100644 --- a/app/src/Legacy/G/DB.php +++ b/app/src/Legacy/G/DB.php @@ -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 . ' '; } } } diff --git a/app/src/Legacy/G/functions.php b/app/src/Legacy/G/functions.php index 7c09a43..28ebd8d 100644 --- a/app/src/Legacy/G/functions.php +++ b/app/src/Legacy/G/functions.php @@ -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); diff --git a/app/src/Legacy/functions-render.php b/app/src/Legacy/functions-render.php index ad6c8fe..f86da16 100644 --- a/app/src/Legacy/functions-render.php +++ b/app/src/Legacy/functions-render.php @@ -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(' %s', _s('Pro')); + return sprintf(' %s', $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 diff --git a/app/src/Legacy/functions.php b/app/src/Legacy/functions.php index 8742aac..f17b123 100644 --- a/app/src/Legacy/functions.php +++ b/app/src/Legacy/functions.php @@ -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'], + ]; +} diff --git a/app/upgrading.php b/app/upgrading.php index 6cecb13..8780800 100644 --- a/app/upgrading.php +++ b/app/upgrading.php @@ -51,17 +51,15 @@ $workingDir = __DIR__ . '/.upgrading'; if (is_file($workingDir)) { unlink($workingDir); } -$runtimeTable = [ - 'log_errors' => ini_set('log_errors', true), - 'display_errors' => ini_set('display_errors', true), - 'error_log' => ini_set('error_log', $workingDir . '/error.log'), - 'ignore_user_abort' => ignore_user_abort(true), - 'time_limit' => @set_time_limit(0), - 'ini_set' => ini_set('default_charset', 'utf-8'), - 'setlocale' => setlocale(LC_ALL, 'en_US.UTF8'), - 'output_buffering' => ini_set('output_buffering', 'off'), - 'zlib.output_compression' => ini_set('zlib.output_compression', false), -]; +ini_set('log_errors', true); +ini_set('display_errors', true); +ini_set('error_log', $workingDir . '/error.log'); +ignore_user_abort(true); +@set_time_limit(0); +ini_set('default_charset', 'utf-8'); +setlocale(LC_ALL, 'en_US.UTF8'); +ini_set('output_buffering', 'off'); +ini_set('zlib.output_compression', false); $logProcess = $workingDir . '/process.log'; $lockUpgrading = $workingDir . '/upgrading.lock'; $lockDownloading = $workingDir . '/downloading.lock'; diff --git a/content/legacy/system/chevereto-ultimate-remix.png b/content/legacy/system/chevereto-ultimate-remix.png new file mode 100644 index 0000000..e884c8c Binary files /dev/null and b/content/legacy/system/chevereto-ultimate-remix.png differ diff --git a/content/legacy/system/style.css b/content/legacy/system/style.css index 929256b..cfebbbb 100644 --- a/content/legacy/system/style.css +++ b/content/legacy/system/style.css @@ -340,6 +340,10 @@ code { opacity: 0; } +.flex-box form input { + font-size: 0.83em; +} + .flex-box .loader--show, body.body--installing .flex-box .loader { opacity: 1; @@ -588,6 +592,11 @@ body.body--installing .flex-box .loader { color: #d74634; } +.input-password input[type="password"] { + -webkit-padding-end: 55px; + padding-inline-end: 55px; +} + .input-password { position: relative; } @@ -597,6 +606,7 @@ body.body--installing .flex-box .loader { inset-inline-end: 0; top: 50%; margin-top: 5px; + z-index: -1; } .input-password-strength { diff --git a/content/legacy/system/style.min.css b/content/legacy/system/style.min.css index 1761a03..17d527a 100644 --- a/content/legacy/system/style.min.css +++ b/content/legacy/system/style.min.css @@ -1 +1 @@ -body,html{height:100%}body{margin:0;background:#3498db;background:linear-gradient(to bottom,#3498db 0,#8e44ad 100%)}html#error body{background:#ecf0f1}html{color:#000;font:16px Helvetica,Arial,sans-serif;line-height:1.3}.body--block{margin:20px}.body--flex{margin:0;display:flex;flex-direction:column}.user-select-none{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.force-select{-webkit-user-select:all;-moz-user-select:all;-ms-user-select:all;user-select:all}main{width:100%;height:100%;padding:0;margin:0;border:0;display:flex;align-items:center;justify-content:center;overflow-y:auto;flex:1}@media (min-width:768px){main{padding:20px}}main>div{width:630px}.main--stack{width:100%;max-width:900px}*{-ms-box-sizing:border-box;box-sizing:border-box;outline:0}a{color:#3498db;outline:0;text-decoration:none}a:hover{text-decoration:underline}p,ul>li{line-height:140%}.soft-hidden{display:none}.p{margin-top:20px;margin-bottom:20px}.alert,.highlight,.log{font-size:.9em;padding:1em}.alert:empty,.highlight:empty{display:none}.highlight{background:rgba(255,255,255,.5);border-inline-start:4px solid #8e44ad}.alert{position:relative;background:rgba(241,196,15,.3);border-inline-start:4px solid #f1c40f;padding-inline-end:2em}.alert pre{overflow:auto}.alert code,.alert pre{background:rgba(241,196,15,.3)}.alert pre code{background:0 0}.shake{-webkit-animation:shake .5s cubic-bezier(.36,.07,.19,.97) both;animation:shake .5s cubic-bezier(.36,.07,.19,.97) both;transform:translate3d(0,0,0);-webkit-backface-visibility:hidden;backface-visibility:hidden;perspective:1000px}.alert-close{cursor:pointer;position:absolute;inset-inline-end:1em;top:1em;width:1em;height:1em;opacity:.3}.alert-close:hover{opacity:1}.alert-close:after,.alert-close:before{position:absolute;inset-inline-start:7.5px;content:' ';height:16px;width:2px;background-color:#333}.alert-close:before{transform:rotate(45deg)}.alert-close:after{transform:rotate(-45deg)}.button,button,input,select{font-family:Helvetica,Arial,sans-serif;padding:10px;color:#000}input{border:1px solid rgba(0,0,0,.1);background:0 0;border-top-color:transparent;border-left-color:transparent;border-right-color:transparent;padding-inline-start:0;transition:border-width 1s linear}input:focus,select:focus{border-bottom-color:#3498db}input:-webkit-autofill,input:-webkit-autofill:focus input:-webkit-autofill,input:-webkit-autofill:hover,select:-webkit-autofill,select:-webkit-autofill:focus,select:-webkit-autofill:hover,textarea:-webkit-autofill,textarea:-webkit-autofill:hover textarea:-webkit-autofill:focus{-webkit-text-fill-color:inherit;-webkit-box-shadow:0 0 0 1000px transparent inset;-webkit-transition:background-color 5000s ease-in-out 0s;transition:background-color 5000s ease-in-out 0s}.button,button{display:inline-block;font-size:.83em;font-weight:700;padding-inline-end:15px;padding-inline-start:15px;line-height:.83em;outline:0;cursor:pointer;text-shadow:1px 1px 0 rgba(255,255,255,.1);text-decoration:none;border:0;background-color:rgba(0,0,0,.05);box-shadow:inset 0 2px 5px transparent}.button:hover,button:hover{text-decoration:none}.button:active,button:active{box-shadow:inset 0 2px 5px rgba(0,0,0,.3)}.button.action,button.action{text-shadow:1px 1px 0 rgba(0,0,0,.05);color:#fff;background:#3498db}.button.action:hover,button.action:hover{background:#2980b9}button[disabled],input[disabled]{cursor:wait}input[data-disabled]{cursor:not-allowed}.input-label label{font-size:.9em;display:block;font-weight:700}h1{line-height:1em}code{font-size:.9em;font-family:monospace;background:rgba(0,0,0,.1)}.pre{display:block;background-color:rgba(0,0,0,.1);overflow:auto;height:180px;font-size:.9em;white-space:nowrap;width:100%;resize:none;border:none;margin:1em 0;-moz-tab-size:4;-o-tab-size:4;tab-size:4}@-webkit-keyframes spin{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes spin{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}.flex-box .loader{display:inline-block;border:.15em solid rgb(52 152 219 / 20%);border-top:.15em solid #3498db;border-radius:50%;width:1em;height:1em;-webkit-animation:spin 2s linear infinite;animation:spin 2s linear infinite;z-index:1;font-size:32px;position:absolute;top:20px;inset-inline-end:20px;margin:0;opacity:0}.flex-box .loader--show,body.body--installing .flex-box .loader{opacity:1}.animate{transition:all .2s ease}.animate--slow{transition-duration:.8s}.text-align-center{text-align:center}.flex{display:flex}.flex--full{min-height:100%;overflow:hidden}.screen{margin:auto;flex-wrap:wrap;flex-direction:row;justify-content:center;display:flex;visibility:visible}.screen--error{opacity:1;display:flex;transform:scale(1)}@-webkit-keyframes fadeInFromNone{0%{display:none;opacity:0}100%{opacity:1;transform:scale(1)}}@keyframes fadeInFromNone{0%{display:none;opacity:0}100%{opacity:1;transform:scale(1)}}.flex-item{flex:1 0 100%;justify-content:center}.flex-box{background:rgba(255,255,255,.8);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);background-size:cover;background-position:center;border-radius:10px;min-width:270px;position:relative;margin:20px;flex:1 0 0}.flex-box>div{margin:20px}.flex-box+.flex-box{margin-top:0}.log{background:rgba(255,255,255,.1);overflow:auto;max-height:10em;margin:0;padding:0}.log:empty{display:none}.log p{margin:0;padding:5px}.log p:nth-child(even){background:rgba(255,255,255,.25)}.radius{border-radius:3px}.error-box{background:0 0;box-shadow:none}.error-box a{font-weight:400;text-decoration:none}.error-box a:hover{-webkit-text-decoration-style:solid;text-decoration-style:solid;-webkit-text-decoration-color:#000;text-decoration-color:#000}.error-box-code{opacity:.4;font-size:.9em;border-top:1px solid rgba(0,0,0,.2);padding-top:10px}@media (min-width:680px){.col-8{width:310px}.col-width{width:630px}.flex-box+.flex-box{margin-top:20px;margin-inline-start:0}}.width-100p{width:100%}.header img,.header svg{height:40px;width:auto;max-height:100%;margin:20px auto;display:block}.header img path,.header svg path{fill:#fff}.install-details{font-size:.9em;font-family:monospace}.install-details pre{margin:0;font-family:inherit}@-webkit-keyframes shake{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}@keyframes shake{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}.display-block{display:block}.input-warning{position:absolute;inset-inline-end:0;top:0;font-size:80%}.red-warning{color:#d74634}.input-password{position:relative}.input-password .input-password-strength{position:absolute;inset-inline-end:0;top:50%;margin-top:5px}.input-password-strength{background:rgba(0,0,0,.1)}.input-password-strength,.input-password-strength span{display:block;width:50px;height:8px}.input-password-strength span{width:0%;background-color:transparent}.input-password-strength [data-veredict=very-weak]{background-color:#e74c3c}.input-password-strength [data-veredict=weak]{background-color:#e67e22}.input-password-strength [data-veredict=average],.input-password-strength [data-veredict=strong],.input-password-strength [data-veredict=stronger]{background-color:#2ecc71}.description-meta:empty{display:none} \ No newline at end of file +body,html{height:100%}body{margin:0;background:#3498db;background:linear-gradient(to bottom,#3498db 0,#8e44ad 100%)}html#error body{background:#ecf0f1}html{color:#000;font:16px Helvetica,Arial,sans-serif;line-height:1.3}.body--block{margin:20px}.body--flex{margin:0;display:flex;flex-direction:column}.user-select-none{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.force-select{-webkit-user-select:all;-moz-user-select:all;-ms-user-select:all;user-select:all}main{width:100%;height:100%;padding:0;margin:0;border:0;display:flex;align-items:center;justify-content:center;overflow-y:auto;flex:1}@media (min-width:768px){main{padding:20px}}main>div{width:630px}.main--stack{width:100%;max-width:900px}*{-ms-box-sizing:border-box;box-sizing:border-box;outline:0}a{color:#3498db;outline:0;text-decoration:none}a:hover{text-decoration:underline}p,ul>li{line-height:140%}.soft-hidden{display:none}.p{margin-top:20px;margin-bottom:20px}.alert,.highlight,.log{font-size:.9em;padding:1em}.alert:empty,.highlight:empty{display:none}.highlight{background:rgba(255,255,255,.5);border-inline-start:4px solid #8e44ad}.alert{position:relative;background:rgba(241,196,15,.3);border-inline-start:4px solid #f1c40f;padding-inline-end:2em}.alert pre{overflow:auto}.alert code,.alert pre{background:rgba(241,196,15,.3)}.alert pre code{background:0 0}.shake{-webkit-animation:shake .5s cubic-bezier(.36,.07,.19,.97) both;animation:shake .5s cubic-bezier(.36,.07,.19,.97) both;transform:translate3d(0,0,0);-webkit-backface-visibility:hidden;backface-visibility:hidden;perspective:1000px}.alert-close{cursor:pointer;position:absolute;inset-inline-end:1em;top:1em;width:1em;height:1em;opacity:.3}.alert-close:hover{opacity:1}.alert-close:after,.alert-close:before{position:absolute;inset-inline-start:7.5px;content:' ';height:16px;width:2px;background-color:#333}.alert-close:before{transform:rotate(45deg)}.alert-close:after{transform:rotate(-45deg)}.button,button,input,select{font-family:Helvetica,Arial,sans-serif;padding:10px;color:#000}input{border:1px solid rgba(0,0,0,.1);background:0 0;border-top-color:transparent;border-left-color:transparent;border-right-color:transparent;padding-inline-start:0;transition:border-width 1s linear}input:focus,select:focus{border-bottom-color:#3498db}input:-webkit-autofill,input:-webkit-autofill:focus input:-webkit-autofill,input:-webkit-autofill:hover,select:-webkit-autofill,select:-webkit-autofill:focus,select:-webkit-autofill:hover,textarea:-webkit-autofill,textarea:-webkit-autofill:hover textarea:-webkit-autofill:focus{-webkit-text-fill-color:inherit;-webkit-box-shadow:0 0 0 1000px transparent inset;-webkit-transition:background-color 5000s ease-in-out 0s;transition:background-color 5000s ease-in-out 0s}.button,button{display:inline-block;font-size:.83em;font-weight:700;padding-inline-end:15px;padding-inline-start:15px;line-height:.83em;outline:0;cursor:pointer;text-shadow:1px 1px 0 rgba(255,255,255,.1);text-decoration:none;border:0;background-color:rgba(0,0,0,.05);box-shadow:inset 0 2px 5px transparent}.button:hover,button:hover{text-decoration:none}.button:active,button:active{box-shadow:inset 0 2px 5px rgba(0,0,0,.3)}.button.action,button.action{text-shadow:1px 1px 0 rgba(0,0,0,.05);color:#fff;background:#3498db}.button.action:hover,button.action:hover{background:#2980b9}button[disabled],input[disabled]{cursor:wait}input[data-disabled]{cursor:not-allowed}.input-label label{font-size:.9em;display:block;font-weight:700}h1{line-height:1em}code{font-size:.9em;font-family:monospace;background:rgba(0,0,0,.1)}.pre{display:block;background-color:rgba(0,0,0,.1);overflow:auto;height:180px;font-size:.9em;white-space:nowrap;width:100%;resize:none;border:none;margin:1em 0;-moz-tab-size:4;-o-tab-size:4;tab-size:4}@-webkit-keyframes spin{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes spin{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}.flex-box .loader{display:inline-block;border:.15em solid rgb(52 152 219 / 20%);border-top:.15em solid #3498db;border-radius:50%;width:1em;height:1em;-webkit-animation:spin 2s linear infinite;animation:spin 2s linear infinite;z-index:1;font-size:32px;position:absolute;top:20px;inset-inline-end:20px;margin:0;opacity:0}.flex-box form input{font-size:.83em}.flex-box .loader--show,body.body--installing .flex-box .loader{opacity:1}.animate{transition:all .2s ease}.animate--slow{transition-duration:.8s}.text-align-center{text-align:center}.flex{display:flex}.flex--full{min-height:100%;overflow:hidden}.screen{margin:auto;flex-wrap:wrap;flex-direction:row;justify-content:center;display:flex;visibility:visible}.screen--error{opacity:1;display:flex;transform:scale(1)}@-webkit-keyframes fadeInFromNone{0%{display:none;opacity:0}100%{opacity:1;transform:scale(1)}}@keyframes fadeInFromNone{0%{display:none;opacity:0}100%{opacity:1;transform:scale(1)}}.flex-item{flex:1 0 100%;justify-content:center}.flex-box{background:rgba(255,255,255,.8);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);background-size:cover;background-position:center;border-radius:10px;min-width:270px;position:relative;margin:20px;flex:1 0 0}.flex-box>div{margin:20px}.flex-box+.flex-box{margin-top:0}.log{background:rgba(255,255,255,.1);overflow:auto;max-height:10em;margin:0;padding:0}.log:empty{display:none}.log p{margin:0;padding:5px}.log p:nth-child(even){background:rgba(255,255,255,.25)}.radius{border-radius:3px}.error-box{background:0 0;box-shadow:none}.error-box a{font-weight:400;text-decoration:none}.error-box a:hover{-webkit-text-decoration-style:solid;text-decoration-style:solid;-webkit-text-decoration-color:#000;text-decoration-color:#000}.error-box-code{opacity:.4;font-size:.9em;border-top:1px solid rgba(0,0,0,.2);padding-top:10px}@media (min-width:680px){.col-8{width:310px}.col-width{width:630px}.flex-box+.flex-box{margin-top:20px;margin-inline-start:0}}.width-100p{width:100%}.header img,.header svg{height:40px;width:auto;max-height:100%;margin:20px auto;display:block}.header img path,.header svg path{fill:#fff}.install-details{font-size:.9em;font-family:monospace}.install-details pre{margin:0;font-family:inherit}@-webkit-keyframes shake{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}@keyframes shake{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}.display-block{display:block}.input-warning{position:absolute;inset-inline-end:0;top:0;font-size:80%}.red-warning{color:#d74634}.input-password input[type=password]{-webkit-padding-end:55px;padding-inline-end:55px}.input-password{position:relative}.input-password .input-password-strength{position:absolute;inset-inline-end:0;top:50%;margin-top:5px;z-index:-1}.input-password-strength{background:rgba(0,0,0,.1)}.input-password-strength,.input-password-strength span{display:block;width:50px;height:8px}.input-password-strength span{width:0%;background-color:transparent}.input-password-strength [data-veredict=very-weak]{background-color:#e74c3c}.input-password-strength [data-veredict=weak]{background-color:#e67e22}.input-password-strength [data-veredict=average],.input-password-strength [data-veredict=strong],.input-password-strength [data-veredict=stronger]{background-color:#2ecc71}.description-meta:empty{display:none} \ No newline at end of file diff --git a/content/legacy/themes/Peafowl/head.php b/content/legacy/themes/Peafowl/head.php index b18bdb1..d86d7c7 100644 --- a/content/legacy/themes/Peafowl/head.php +++ b/content/legacy/themes/Peafowl/head.php @@ -186,6 +186,7 @@ if (!defined('ACCESS') || !ACCESS) { diff --git a/content/legacy/themes/Peafowl/header.php b/content/legacy/themes/Peafowl/header.php index a77cb9a..edbc8db 100644 --- a/content/legacy/themes/Peafowl/header.php +++ b/content/legacy/themes/Peafowl/header.php @@ -1,5 +1,6 @@