diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 56d8d7c..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -custom: ['https://chevereto.com/pricing'] diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 957f0d6..43ed4b7 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -46,8 +46,7 @@ jobs: chevereto/chevereto ${{ env.GHCR_SLUG }} tags: | - type=ref,event=branch - type=ref,event=pr + type=raw,value=latest,enable={{is_default_branch}} type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} diff --git a/.package/4.2.5.txt b/.package/4.2.5.txt deleted file mode 100644 index 982714e..0000000 --- a/.package/4.2.5.txt +++ /dev/null @@ -1,9 +0,0 @@ -Chevereto 4.2.5 (2025-01-31) - -- Added `putenv` to integrity-check -- Fixed "Twitter" branding assets to "X" -- Fixed bug in account confirmation process -- Fixed bug in guest owned content editing -- Fixed bug in search functionality -- Fixed bug in tags per file limitation -- Fixed bug with not working local storage in Chevereto Lite diff --git a/.package/4.3.0.txt b/.package/4.3.0.txt new file mode 100644 index 0000000..cb289b2 --- /dev/null +++ b/.package/4.3.0.txt @@ -0,0 +1,50 @@ +Chevereto 4.3.0 (2025-05-13) + +- Added API dashboard settings page +- Added application-level categories cache +- Added application-level guest listing cache +- Added application-level pages cache +- Added application-level settings cache +- Added application-level top tags cache +- Added application-level user albums cache +- Added application-level variables cache +- Added cache-flush command +- Added cache-view command +- Added CHEVERETO_ENABLE_API_USER +- Added CHEVERETO_MAX_LISTING_ITEMS_PER_PAGE limit +- Added CHEVERETO_MAX_TAGS limit +- Added chunked uploads +- Added circles for user avatars +- Added configurable API availability +- Added connect URL display for each login provider +- Added customizable semantics for file concerns +- Added customizable semantics for tag concerns +- Added customizable semantics for video concerns +- Added explicit instructions to "Something went wrong" message +- Added lossless JPEG auto-orientation using ExifTran +- Added lossless metadata removal using ExifTool +- Added more database composite indexes +- Added option to disable user color palette selection +- Added restricted password reset after repeated failures +- Added restricted two-factor access after repeated failures +- Added support for Cloudflare Turnstile captcha +- Added support for GoPro 360 photos +- Fixed bug affecting EXIF read for Sony cameras +- Fixed bug affecting private sub-albums +- Fixed bug in upload queue handler +- Fixed bug on upgrading.php +- Improved Dashboard settings menu sort display +- Improved database schema integer types +- Improved listing database queries +- Improved search engine (boolean matching) +- Improved stats query handling +- Improved user albums query +- Removed guest upload session binding +- Removed maximum upload limit imposed by php.ini +- Removed sendmail for Docker servicing +- Removed support for ModerateContent +- Removed unused dependencies and files +- Renamed "Asset storage" to "Site storage" +- Renamed "External storage" to "Upload storage" +- Renamed app/bin/legacy to app/bin/cli +- Updated file checksum algo to xxh64 diff --git a/README.md b/README.md index eee2a97..ad15fdf 100644 --- a/README.md +++ b/README.md @@ -9,23 +9,33 @@ [![Chevereto Docs](https://img.shields.io/badge/chevereto-docs-50C878?style=flat-square)](https://v4-docs.chevereto.com/) [![Chevereto Community](https://img.shields.io/badge/chevereto-community-blue?style=flat-square)](https://chevereto.com/community) [![Chevereto Demo](https://img.shields.io/badge/chevereto-demo-d4af37?style=flat-square&color=red)](https://demo.chevereto.com) -[![AGPL-3.0-only](https://img.shields.io/github/license/chevereto/chevereto?style=flat-square)](LICENSE) -[![Legacy stars](https://img.shields.io/github/stars/rodber/chevereto-free?style=flat-square&logo=github&label=Legacy%20stars&color=gold)](https://github.com/rodber/chevereto-free) +[![Chevereto Free](https://img.shields.io/badge/chevereto-editions-gold?style=flat-square)](https://v4-docs.chevereto.com/introduction/editions/compare.html) [![Awesome F/OSS](https://img.shields.io/badge/Awesome_F%2FOSS-Certified-black?colorA=&colorB=874efe&style=flat-square)](https://awsmfoss.com/chevereto/) > 🔔 [Subscribe](https://chevereto.com/go/newsletter) to don't miss any update regarding Chevereto. Chevereto is a robust, self-hosted media-sharing platform that prioritizes flexibility and control. It enables you to build and manage a media-sharing website on your own server, granting you complete autonomy over your hosting environment and policies. With Chevereto, you eliminate the risk of platform restrictions and shutdowns, ensuring your site operates entirely on your terms. -This is the repository for **Chevereto Free** edition. You can [compare editions](https://v4-docs.chevereto.com/introduction/editions/compare.html) to find the Chevereto edition that best suits your needs. - ![screen](.github/screen/user-listing-selected.webp) +## Features + +Chevereto offers a comprehensive suite of features that make it the ultimate image and video sharing software: + +* **Media Management**: Support for images, videos, categories, tags and albums with advanced organization tools +* **User Experience**: Customizable listings, responsive design, and intuitive user interface +* **Privacy Controls**: Granular content privacy settings and user management +* **Performance**: Optimized for speed with CDN support, caching, and image optimization +* **Customization**: Turnkey options, language, and extensive API for integration + +You can check the complete list of features in our [Features page](https://chevereto.com/features) and you can also [compare Chevereto editions](https://v4-docs.chevereto.com/introduction/editions/compare.html). + ## Requirements -* A [webserver](https://v4-docs.chevereto.com/application/stack/web-server.html) (Apache recommended) -* [PHP](https://v4-docs.chevereto.com/application/stack/php.html) 8.1+ with [extensions](https://v4-docs.chevereto.com/application/stack/php.html#extensions) -* [MySQL Server](https://v4-docs.chevereto.com/application/stack/mysql-server.html) 8.0.1+ or MariaDB Server 10.2.2+ +* [Webserver](https://v4-docs.chevereto.com/application/stack/web-server.html) +* [PHP](https://v4-docs.chevereto.com/application/stack/php.html) +* [MySQL Server](https://v4-docs.chevereto.com/application/stack/mysql-server.html) (MariaDB Server) +* [Redis](https://v4-docs.chevereto.com/application/stack/redis.html) (optional) ## Download diff --git a/app/src/Workflow/BaseWorkflow.php b/app/bin/cli old mode 100644 new mode 100755 similarity index 73% rename from app/src/Workflow/BaseWorkflow.php rename to app/bin/cli index 556695f..1e9b2f3 --- a/app/src/Workflow/BaseWorkflow.php +++ b/app/bin/cli @@ -1,3 +1,4 @@ +#!/usr/bin/env php =5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0|^5.0|^6.0|^7.0", - "satooshi/php-coveralls": "^2.0" - }, - "suggest": { - "ext-gmp": "GD or ImageMagick is required", - "ext-imagick": "GD or ImageMagick is required" - }, - "type": "library", - "autoload": { - "psr-4": { - "Jenssegers\\ImageHash\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Kenneth Rapp" - }, - { - "name": "Jens Segers", - "homepage": "https://jenssegers.com" - } - ], - "description": "Perceptual image hashing for PHP", - "homepage": "https://github.com/jenssegers/imagehash", - "keywords": [ - "ahash", - "dhash", - "hash", - "image hash", - "imagehash", - "perceptual", - "phash" - ], - "support": { - "issues": "https://github.com/jenssegers/imagehash/issues", - "source": "https://github.com/jenssegers/imagehash/tree/master" - }, - "time": "2018-05-05T15:27:52+00:00" - }, { "name": "jeroendesloovere/xmp-metadata-extractor", "version": "v2.0.0", @@ -2269,173 +2261,31 @@ }, "time": "2018-05-30T08:34:23+00:00" }, - { - "name": "league/flysystem", - "version": "2.5.0", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/flysystem.git", - "reference": "8aaffb653c5777781b0f7f69a5d937baf7ab6cdb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/8aaffb653c5777781b0f7f69a5d937baf7ab6cdb", - "reference": "8aaffb653c5777781b0f7f69a5d937baf7ab6cdb", - "shasum": "" - }, - "require": { - "ext-json": "*", - "league/mime-type-detection": "^1.0.0", - "php": "^7.2 || ^8.0" - }, - "conflict": { - "guzzlehttp/ringphp": "<1.1.1" - }, - "require-dev": { - "async-aws/s3": "^1.5", - "async-aws/simple-s3": "^1.0", - "aws/aws-sdk-php": "^3.132.4", - "composer/semver": "^3.0", - "ext-fileinfo": "*", - "ext-ftp": "*", - "friendsofphp/php-cs-fixer": "^3.2", - "google/cloud-storage": "^1.23", - "phpseclib/phpseclib": "^2.0", - "phpstan/phpstan": "^0.12.26", - "phpunit/phpunit": "^8.5 || ^9.4", - "sabre/dav": "^4.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "League\\Flysystem\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Frank de Jonge", - "email": "info@frankdejonge.nl" - } - ], - "description": "File storage abstraction for PHP", - "keywords": [ - "WebDAV", - "aws", - "cloud", - "file", - "files", - "filesystem", - "filesystems", - "ftp", - "s3", - "sftp", - "storage" - ], - "support": { - "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/2.5.0" - }, - "funding": [ - { - "url": "https://ecologi.com/frankdejonge", - "type": "custom" - }, - { - "url": "https://github.com/frankdejonge", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/league/flysystem", - "type": "tidelift" - } - ], - "time": "2022-09-17T21:02:32+00:00" - }, - { - "name": "league/mime-type-detection", - "version": "1.16.0", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9", - "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9", - "shasum": "" - }, - "require": { - "ext-fileinfo": "*", - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.2", - "phpstan/phpstan": "^0.12.68", - "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "League\\MimeTypeDetection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Frank de Jonge", - "email": "info@frankdejonge.nl" - } - ], - "description": "Mime-type detection for Flysystem", - "support": { - "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0" - }, - "funding": [ - { - "url": "https://github.com/frankdejonge", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/league/flysystem", - "type": "tidelift" - } - ], - "time": "2024-09-21T08:32:55+00:00" - }, { "name": "lychee-org/php-exif", - "version": "v0.7.14", + "version": "v1.0.2", "source": { "type": "git", "url": "https://github.com/LycheeOrg/php-exif.git", - "reference": "c0d1ce46b1bf55d951f9adcc95b3b417b7f1e76c" + "reference": "12c4976d2dea44fc4eb3b9dd33428b95ba7461c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/LycheeOrg/php-exif/zipball/c0d1ce46b1bf55d951f9adcc95b3b417b7f1e76c", - "reference": "c0d1ce46b1bf55d951f9adcc95b3b417b7f1e76c", + "url": "https://api.github.com/repos/LycheeOrg/php-exif/zipball/12c4976d2dea44fc4eb3b9dd33428b95ba7461c0", + "reference": "12c4976d2dea44fc4eb3b9dd33428b95ba7461c0", "shasum": "" }, "require": { "ext-fileinfo": "*", - "neitanod/forceutf8": "^2.0.4", - "php": "^8.0", + "fylax/forceutf8": "^3.0.1", + "php": "^8.1", "php-ffmpeg/php-ffmpeg": "^1.0", "thecodingmachine/safe": "^2.2" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.3", "infection/infection": "^0.26.13", - "lychee-org/phpstan-lychee": "dev-master", + "lychee-org/phpstan-lychee": "^1.0.1", "php-parallel-lint/php-parallel-lint": "^1.2", "phpmd/phpmd": "^2.9", "phpunit/phpunit": "^9.5.10", @@ -2480,9 +2330,108 @@ ], "support": { "issues": "https://github.com/LycheeOrg/php-exif/issues", - "source": "https://github.com/LycheeOrg/php-exif/tree/v0.7.14" + "source": "https://github.com/LycheeOrg/php-exif/tree/v1.0.2" }, - "time": "2022-10-28T15:10:47+00:00" + "time": "2023-04-11T11:03:27+00:00" + }, + { + "name": "matthiasmullie/scrapbook", + "version": "1.4.9", + "source": { + "type": "git", + "url": "https://github.com/matthiasmullie/scrapbook.git", + "reference": "da4178c1882136a8931ffd791df2b84a5aa74219" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/matthiasmullie/scrapbook/zipball/da4178c1882136a8931ffd791df2b84a5aa74219", + "reference": "da4178c1882136a8931ffd791df2b84a5aa74219", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/cache": "^1.0||~2.0", + "psr/simple-cache": "^1.0||~2.0" + }, + "provide": { + "psr/cache-implementation": "^1.0||~2.0", + "psr/simple-cache-implementation": "^1.0||~2.0" + }, + "require-dev": { + "ext-pcntl": "*", + "friendsofphp/php-cs-fixer": ">=2.0", + "phpunit/phpunit": ">=4.8" + }, + "suggest": { + "couchbase/couchbase": ">=2.0", + "ext-apc": ">=3.1.1", + "ext-couchbase": ">=2.0.0", + "ext-memcached": ">=2.0.0", + "ext-pdo": ">=0.1.0", + "ext-redis": ">=2.2.0 || 0.0.0.0", + "league/flysystem": ">=1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "MatthiasMullie\\Scrapbook\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthias Mullie", + "email": "scrapbook@mullie.eu", + "homepage": "https://www.mullie.eu", + "role": "Developer" + } + ], + "description": "Scrapbook is a PHP cache library, with adapters for e.g. Memcached, Redis, Couchbase, APC(u), SQL and additional capabilities (e.g. transactions, stampede protection) built on top.", + "homepage": "https://scrapbook.cash", + "keywords": [ + "Buffer", + "Flysystem", + "apc", + "buffered", + "cache", + "caching", + "commit", + "couchbase", + "filesystem", + "key", + "memcached", + "mitigation", + "mysql", + "postgresql", + "protection", + "psr-16", + "psr-6", + "psr-cache", + "psr-simple-cache", + "redis", + "rollback", + "sql", + "sqlite", + "stampede", + "store", + "transaction", + "transactional", + "value" + ], + "support": { + "issues": "https://github.com/matthiasmullie/scrapbook/issues", + "source": "https://github.com/matthiasmullie/scrapbook/tree/1.4.9" + }, + "funding": [ + { + "url": "https://github.com/matthiasmullie", + "type": "github" + } + ], + "time": "2022-11-10T09:28:49+00:00" }, { "name": "middlewares/utils", @@ -2548,16 +2497,16 @@ }, { "name": "mlocati/ip-lib", - "version": "1.18.1", + "version": "1.20.0", "source": { "type": "git", "url": "https://github.com/mlocati/ip-lib.git", - "reference": "08bb43b4949069c543ebdf099a6b2c322d0172ab" + "reference": "fd45fc3bf08ed6c7e665e2e70562082ac954afd4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mlocati/ip-lib/zipball/08bb43b4949069c543ebdf099a6b2c322d0172ab", - "reference": "08bb43b4949069c543ebdf099a6b2c322d0172ab", + "url": "https://api.github.com/repos/mlocati/ip-lib/zipball/fd45fc3bf08ed6c7e665e2e70562082ac954afd4", + "reference": "fd45fc3bf08ed6c7e665e2e70562082ac954afd4", "shasum": "" }, "require": { @@ -2603,7 +2552,7 @@ ], "support": { "issues": "https://github.com/mlocati/ip-lib/issues", - "source": "https://github.com/mlocati/ip-lib/tree/1.18.1" + "source": "https://github.com/mlocati/ip-lib/tree/1.20.0" }, "funding": [ { @@ -2615,7 +2564,7 @@ "type": "other" } ], - "time": "2024-10-29T15:44:19+00:00" + "time": "2025-02-04T17:30:58+00:00" }, { "name": "mobiledetect/mobiledetectlib", @@ -2679,47 +2628,6 @@ ], "time": "2023-11-07T21:57:25+00:00" }, - { - "name": "neitanod/forceutf8", - "version": "v2.0.4", - "source": { - "type": "git", - "url": "https://github.com/neitanod/forceutf8.git", - "reference": "c1fbe70bfb5ad41b8ec5785056b0e308b40d4831" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/neitanod/forceutf8/zipball/c1fbe70bfb5ad41b8ec5785056b0e308b40d4831", - "reference": "c1fbe70bfb5ad41b8ec5785056b0e308b40d4831", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "ForceUTF8\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastián Grignoli", - "email": "grignoli@gmail.com" - } - ], - "description": "PHP Class Encoding featuring popular Encoding::toUTF8() function --formerly known as forceUTF8()-- that fixes mixed encoded strings.", - "homepage": "https://github.com/neitanod/forceutf8", - "support": { - "issues": "https://github.com/neitanod/forceutf8/issues", - "source": "https://github.com/neitanod/forceutf8/tree/master" - }, - "time": "2019-12-10T14:09:14+00:00" - }, { "name": "nikic/fast-route", "version": "v1.3.0", @@ -3068,73 +2976,18 @@ }, "time": "2020-10-15T08:29:30+00:00" }, - { - "name": "php-ds/php-ds", - "version": "v1.5.0", - "source": { - "type": "git", - "url": "https://github.com/php-ds/polyfill.git", - "reference": "7b2c5f1843466d50769a0682ce6fa9ddaaa99cb4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-ds/polyfill/zipball/7b2c5f1843466d50769a0682ce6fa9ddaaa99cb4", - "reference": "7b2c5f1843466d50769a0682ce6fa9ddaaa99cb4", - "shasum": "" - }, - "require": { - "ext-json": "*", - "php": ">=7.0" - }, - "provide": { - "ext-ds": "1.5.0" - }, - "require-dev": { - "php-ds/tests": "^1.5" - }, - "suggest": { - "ext-ds": "to improve performance and reduce memory usage" - }, - "type": "library", - "autoload": { - "psr-4": { - "Ds\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Rudi Theunissen", - "email": "rudolf.theunissen@gmail.com" - } - ], - "keywords": [ - "data structures", - "ds", - "php", - "polyfill" - ], - "support": { - "issues": "https://github.com/php-ds/polyfill/issues", - "source": "https://github.com/php-ds/polyfill/tree/v1.5.0" - }, - "time": "2023-12-19T16:52:21+00:00" - }, { "name": "php-ffmpeg/php-ffmpeg", - "version": "v1.3.1", + "version": "v1.3.2", "source": { "type": "git", "url": "https://github.com/PHP-FFMpeg/PHP-FFMpeg.git", - "reference": "0fbbc4c6a6336155679adc800616001ae3328c7a" + "reference": "8e74bdc07ad200da7a6cfb21ec2652875e4368e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-FFMpeg/PHP-FFMpeg/zipball/0fbbc4c6a6336155679adc800616001ae3328c7a", - "reference": "0fbbc4c6a6336155679adc800616001ae3328c7a", + "url": "https://api.github.com/repos/PHP-FFMpeg/PHP-FFMpeg/zipball/8e74bdc07ad200da7a6cfb21ec2652875e4368e0", + "reference": "8e74bdc07ad200da7a6cfb21ec2652875e4368e0", "shasum": "" }, "require": { @@ -3208,22 +3061,22 @@ ], "support": { "issues": "https://github.com/PHP-FFMpeg/PHP-FFMpeg/issues", - "source": "https://github.com/PHP-FFMpeg/PHP-FFMpeg/tree/v1.3.1" + "source": "https://github.com/PHP-FFMpeg/PHP-FFMpeg/tree/v1.3.2" }, - "time": "2025-01-10T20:23:57+00:00" + "time": "2025-04-01T20:36:46+00:00" }, { "name": "phpmailer/phpmailer", - "version": "v6.9.3", + "version": "v6.10.0", "source": { "type": "git", "url": "https://github.com/PHPMailer/PHPMailer.git", - "reference": "2f5c94fe7493efc213f643c23b1b1c249d40f47e" + "reference": "bf74d75a1fde6beaa34a0ddae2ec5fce0f72a144" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/2f5c94fe7493efc213f643c23b1b1c249d40f47e", - "reference": "2f5c94fe7493efc213f643c23b1b1c249d40f47e", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/bf74d75a1fde6beaa34a0ddae2ec5fce0f72a144", + "reference": "bf74d75a1fde6beaa34a0ddae2ec5fce0f72a144", "shasum": "" }, "require": { @@ -3283,7 +3136,7 @@ "description": "PHPMailer is a full-featured email creation and transfer class for PHP", "support": { "issues": "https://github.com/PHPMailer/PHPMailer/issues", - "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.3" + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.10.0" }, "funding": [ { @@ -3291,7 +3144,7 @@ "type": "github" } ], - "time": "2024-11-24T18:04:13+00:00" + "time": "2025-04-24T15:19:31+00:00" }, { "name": "phpseclib/bcmath_compat", @@ -3957,6 +3810,57 @@ }, "time": "2021-05-03T11:20:27+00:00" }, + { + "name": "psr/simple-cache", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "8707bf3cea6f710bf6ef05491234e3ab06f6432a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/8707bf3cea6f710bf6ef05491234e3ab06f6432a", + "reference": "8707bf3cea6f710bf6ef05491234e3ab06f6432a", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/2.0.0" + }, + "time": "2021-10-29T13:22:09+00:00" + }, { "name": "psy/psysh", "version": "v0.11.22", @@ -4083,16 +3987,16 @@ }, { "name": "ramsey/collection", - "version": "2.0.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/ramsey/collection.git", - "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5" + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", - "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", "shasum": "" }, "require": { @@ -4100,25 +4004,22 @@ }, "require-dev": { "captainhook/plugin-composer": "^5.3", - "ergebnis/composer-normalize": "^2.28.3", - "fakerphp/faker": "^1.21", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", "hamcrest/hamcrest-php": "^2.0", - "jangregor/phpstan-prophecy": "^1.0", - "mockery/mockery": "^1.5", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", "php-parallel-lint/php-console-highlighter": "^1.0", - "php-parallel-lint/php-parallel-lint": "^1.3", - "phpcsstandards/phpcsutils": "^1.0.0-rc1", - "phpspec/prophecy-phpunit": "^2.0", - "phpstan/extension-installer": "^1.2", - "phpstan/phpstan": "^1.9", - "phpstan/phpstan-mockery": "^1.1", - "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^9.5", - "psalm/plugin-mockery": "^1.1", - "psalm/plugin-phpunit": "^0.18.4", - "ramsey/coding-standard": "^2.0.3", - "ramsey/conventional-commits": "^1.3", - "vimeo/psalm": "^5.4" + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" }, "type": "library", "extra": { @@ -4156,19 +4057,9 @@ ], "support": { "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/2.0.0" + "source": "https://github.com/ramsey/collection/tree/2.1.1" }, - "funding": [ - { - "url": "https://github.com/ramsey", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", - "type": "tidelift" - } - ], - "time": "2022-12-31T21:50:55+00:00" + "time": "2025-03-22T05:38:12+00:00" }, { "name": "ramsey/uuid", @@ -5215,16 +5106,16 @@ }, { "name": "symfony/console", - "version": "v6.4.17", + "version": "v6.4.21", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "799445db3f15768ecc382ac5699e6da0520a0a04" + "reference": "a3011c7b7adb58d89f6c0d822abb641d7a5f9719" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/799445db3f15768ecc382ac5699e6da0520a0a04", - "reference": "799445db3f15768ecc382ac5699e6da0520a0a04", + "url": "https://api.github.com/repos/symfony/console/zipball/a3011c7b7adb58d89f6c0d822abb641d7a5f9719", + "reference": "a3011c7b7adb58d89f6c0d822abb641d7a5f9719", "shasum": "" }, "require": { @@ -5289,7 +5180,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.17" + "source": "https://github.com/symfony/console/tree/v6.4.21" }, "funding": [ { @@ -5305,7 +5196,7 @@ "type": "tidelift" } ], - "time": "2024-12-07T12:07:30+00:00" + "time": "2025-04-07T15:42:41+00:00" }, { "name": "symfony/deprecation-contracts", @@ -5376,7 +5267,7 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -5435,7 +5326,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" }, "funding": [ { @@ -5455,7 +5346,7 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", @@ -5513,7 +5404,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" }, "funding": [ { @@ -5533,7 +5424,7 @@ }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -5594,7 +5485,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" }, "funding": [ { @@ -5614,19 +5505,20 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { + "ext-iconv": "*", "php": ">=7.2" }, "provide": { @@ -5674,7 +5566,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" }, "funding": [ { @@ -5690,11 +5582,11 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-12-23T08:48:59+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", @@ -5750,7 +5642,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.32.0" }, "funding": [ { @@ -5770,16 +5662,16 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", "shasum": "" }, "require": { @@ -5830,7 +5722,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" }, "funding": [ { @@ -5846,20 +5738,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-01-02T08:10:11+00:00" }, { "name": "symfony/process", - "version": "v6.4.15", + "version": "v6.4.20", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "3cb242f059c14ae08591c5c4087d1fe443564392" + "reference": "e2a61c16af36c9a07e5c9906498b73e091949a20" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/3cb242f059c14ae08591c5c4087d1fe443564392", - "reference": "3cb242f059c14ae08591c5c4087d1fe443564392", + "url": "https://api.github.com/repos/symfony/process/zipball/e2a61c16af36c9a07e5c9906498b73e091949a20", + "reference": "e2a61c16af36c9a07e5c9906498b73e091949a20", "shasum": "" }, "require": { @@ -5891,7 +5783,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.4.15" + "source": "https://github.com/symfony/process/tree/v6.4.20" }, "funding": [ { @@ -5907,7 +5799,7 @@ "type": "tidelift" } ], - "time": "2024-11-06T14:19:14+00:00" + "time": "2025-03-10T17:11:00+00:00" }, { "name": "symfony/service-contracts", @@ -5994,16 +5886,16 @@ }, { "name": "symfony/string", - "version": "v6.4.15", + "version": "v6.4.21", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "73a5e66ea2e1677c98d4449177c5a9cf9d8b4c6f" + "reference": "73e2c6966a5aef1d4892873ed5322245295370c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/73a5e66ea2e1677c98d4449177c5a9cf9d8b4c6f", - "reference": "73a5e66ea2e1677c98d4449177c5a9cf9d8b4c6f", + "url": "https://api.github.com/repos/symfony/string/zipball/73e2c6966a5aef1d4892873ed5322245295370c6", + "reference": "73e2c6966a5aef1d4892873ed5322245295370c6", "shasum": "" }, "require": { @@ -6060,7 +5952,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.4.15" + "source": "https://github.com/symfony/string/tree/v6.4.21" }, "funding": [ { @@ -6076,20 +5968,20 @@ "type": "tidelift" } ], - "time": "2024-11-13T13:31:12+00:00" + "time": "2025-04-18T15:23:29+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.4.18", + "version": "v6.4.21", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "4ad10cf8b020e77ba665305bb7804389884b4837" + "reference": "22560f80c0c5cd58cc0bcaf73455ffd81eb380d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/4ad10cf8b020e77ba665305bb7804389884b4837", - "reference": "4ad10cf8b020e77ba665305bb7804389884b4837", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/22560f80c0c5cd58cc0bcaf73455ffd81eb380d5", + "reference": "22560f80c0c5cd58cc0bcaf73455ffd81eb380d5", "shasum": "" }, "require": { @@ -6145,7 +6037,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.4.18" + "source": "https://github.com/symfony/var-dumper/tree/v6.4.21" }, "funding": [ { @@ -6161,20 +6053,20 @@ "type": "tidelift" } ], - "time": "2025-01-17T11:26:11+00:00" + "time": "2025-04-09T07:34:50+00:00" }, { "name": "symfony/var-exporter", - "version": "v6.4.13", + "version": "v6.4.21", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "0f605f72a363f8743001038a176eeb2a11223b51" + "reference": "717e7544aa99752c54ecba5c0e17459c48317472" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/0f605f72a363f8743001038a176eeb2a11223b51", - "reference": "0f605f72a363f8743001038a176eeb2a11223b51", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/717e7544aa99752c54ecba5c0e17459c48317472", + "reference": "717e7544aa99752c54ecba5c0e17459c48317472", "shasum": "" }, "require": { @@ -6222,7 +6114,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v6.4.13" + "source": "https://github.com/symfony/var-exporter/tree/v6.4.21" }, "funding": [ { @@ -6238,20 +6130,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2025-04-27T21:06:26+00:00" }, { "name": "symfony/yaml", - "version": "v6.4.18", + "version": "v6.4.21", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "bf598c9d9bb4a22f495a4e26e4c4fce2f8ecefc5" + "reference": "f01987f45676778b474468aa266fe2eda1f2bc7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/bf598c9d9bb4a22f495a4e26e4c4fce2f8ecefc5", - "reference": "bf598c9d9bb4a22f495a4e26e4c4fce2f8ecefc5", + "url": "https://api.github.com/repos/symfony/yaml/zipball/f01987f45676778b474468aa266fe2eda1f2bc7e", + "reference": "f01987f45676778b474468aa266fe2eda1f2bc7e", "shasum": "" }, "require": { @@ -6294,7 +6186,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v6.4.18" + "source": "https://github.com/symfony/yaml/tree/v6.4.21" }, "funding": [ { @@ -6310,7 +6202,7 @@ "type": "tidelift" } ], - "time": "2025-01-07T09:44:41+00:00" + "time": "2025-04-04T09:48:44+00:00" }, { "name": "thecodingmachine/safe", @@ -6453,16 +6345,16 @@ }, { "name": "xrdebug/php", - "version": "2.0.3", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/xrdebug/php.git", - "reference": "a7dc68d274d585d8506e788eb8b11c07d81b1bd6" + "reference": "83827b79ac9e3ff8a46702c54969d9012e47f472" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/xrdebug/php/zipball/a7dc68d274d585d8506e788eb8b11c07d81b1bd6", - "reference": "a7dc68d274d585d8506e788eb8b11c07d81b1bd6", + "url": "https://api.github.com/repos/xrdebug/php/zipball/83827b79ac9e3ff8a46702c54969d9012e47f472", + "reference": "83827b79ac9e3ff8a46702c54969d9012e47f472", "shasum": "" }, "require": { @@ -6514,9 +6406,9 @@ ], "support": { "issues": "https://github.com/xrdebug/php/issues", - "source": "https://github.com/xrdebug/php/tree/2.0.3" + "source": "https://github.com/xrdebug/php/tree/3.0.0" }, - "time": "2024-12-09T13:25:59+00:00" + "time": "2024-12-25T17:49:34+00:00" }, { "name": "xrdebug/xrdebug", @@ -6660,16 +6552,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.12.1", + "version": "1.13.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", "shasum": "" }, "require": { @@ -6708,7 +6600,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1" }, "funding": [ { @@ -6716,7 +6608,7 @@ "type": "tidelift" } ], - "time": "2024-11-08T17:47:46+00:00" + "time": "2025-04-29T12:36:36+00:00" }, { "name": "phar-io/manifest", @@ -6838,16 +6730,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.16", + "version": "1.12.25", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "e0bb5cb78545aae631220735aa706eac633a6be9" + "reference": "e310849a19e02b8bfcbb63147f495d8f872dd96f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e0bb5cb78545aae631220735aa706eac633a6be9", - "reference": "e0bb5cb78545aae631220735aa706eac633a6be9", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e310849a19e02b8bfcbb63147f495d8f872dd96f", + "reference": "e310849a19e02b8bfcbb63147f495d8f872dd96f", "shasum": "" }, "require": { @@ -6892,7 +6784,7 @@ "type": "github" } ], - "time": "2025-01-21T14:50:05+00:00" + "time": "2025-04-27T12:20:45+00:00" }, { "name": "phpunit/php-code-coverage", @@ -7215,16 +7107,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.22", + "version": "9.6.23", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c" + "reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f80235cb4d3caa59ae09be3adf1ded27521d1a9c", - "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/43d2cb18d0675c38bd44982a5d1d88f6d53d8d95", + "reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95", "shasum": "" }, "require": { @@ -7235,7 +7127,7 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.12.1", + "myclabs/deep-copy": "^1.13.1", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=7.3", @@ -7298,7 +7190,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.22" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.23" }, "funding": [ { @@ -7309,12 +7201,20 @@ "url": "https://github.com/sebastianbergmann", "type": "github" }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, { "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", "type": "tidelift" } ], - "time": "2024-12-05T13:48:26+00:00" + "time": "2025-05-02T06:40:34+00:00" }, { "name": "sebastian/cli-parser", @@ -8281,16 +8181,16 @@ }, { "name": "symplify/easy-coding-standard", - "version": "12.5.8", + "version": "12.5.17", "source": { "type": "git", "url": "https://github.com/easy-coding-standard/easy-coding-standard.git", - "reference": "2bf0e468dc9679f3835c835cd3fd4a25ff6e4e14" + "reference": "9ffb593275f3687a9a7f9b816a67e6a3470e9661" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/2bf0e468dc9679f3835c835cd3fd4a25ff6e4e14", - "reference": "2bf0e468dc9679f3835c835cd3fd4a25ff6e4e14", + "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/9ffb593275f3687a9a7f9b816a67e6a3470e9661", + "reference": "9ffb593275f3687a9a7f9b816a67e6a3470e9661", "shasum": "" }, "require": { @@ -8326,7 +8226,7 @@ ], "support": { "issues": "https://github.com/easy-coding-standard/easy-coding-standard/issues", - "source": "https://github.com/easy-coding-standard/easy-coding-standard/tree/12.5.8" + "source": "https://github.com/easy-coding-standard/easy-coding-standard/tree/12.5.17" }, "funding": [ { @@ -8338,7 +8238,7 @@ "type": "github" } ], - "time": "2025-01-31T13:59:38+00:00" + "time": "2025-05-13T13:16:20+00:00" }, { "name": "theseer/tokenizer", diff --git a/app/env-default.php b/app/env-default.php index 96b7540..a90ab67 100644 --- a/app/env-default.php +++ b/app/env-default.php @@ -10,8 +10,17 @@ */ return [ + 'CHEVERETO_BINARY_EXIFTOOL' => 'exiftool', + 'CHEVERETO_BINARY_EXIFTRAN' => 'exiftran', 'CHEVERETO_BINARY_FFMPEG' => 'ffmpeg', 'CHEVERETO_BINARY_FFPROBE' => 'ffprobe', + 'CHEVERETO_CACHE_DRIVER' => 'redis', + 'CHEVERETO_CACHE_HOST' => '', + 'CHEVERETO_CACHE_KEY_PREFIX' => 'chv:', + 'CHEVERETO_CACHE_PASSWORD' => '', + 'CHEVERETO_CACHE_PORT' => '', + 'CHEVERETO_CACHE_TIME_MICRO' => '60', + 'CHEVERETO_CACHE_USER' => '', 'CHEVERETO_CONTEXT' => '', 'CHEVERETO_DB_DRIVER' => 'mysql', 'CHEVERETO_DB_HOST' => 'localhost', @@ -24,6 +33,7 @@ return [ 'CHEVERETO_DEBUG_LEVEL' => '1', 'CHEVERETO_EDITION' => 'pro', 'CHEVERETO_ENABLE_API_GUEST' => '1', + 'CHEVERETO_ENABLE_API_USER' => '1', 'CHEVERETO_ENABLE_BANNERS' => '1', 'CHEVERETO_ENABLE_BULK_IMPORTER' => '1', 'CHEVERETO_ENABLE_CAPTCHA' => '1', @@ -55,7 +65,7 @@ return [ 'CHEVERETO_ENABLE_SEO_ALBUM_URL' => '1', 'CHEVERETO_ENABLE_SEO_IMAGE_URL' => '1', 'CHEVERETO_ENABLE_SERVICE_AKISMET' => '1', - 'CHEVERETO_ENABLE_SERVICE_MODERATECONTENT' => '1', + 'CHEVERETO_ENABLE_SERVICE_MODERATECONTENT' => '0', 'CHEVERETO_ENABLE_SERVICE_PROJECTARACHNID' => '1', 'CHEVERETO_ENABLE_SERVICE_STOPFORUMSPAM' => '1', 'CHEVERETO_ENABLE_STOPWORDS' => '1', @@ -80,18 +90,22 @@ return [ 'CHEVERETO_IMAGE_LIBRARY' => 'imagick', 'CHEVERETO_MAX_ADMINS' => '0', 'CHEVERETO_MAX_ALBUMS' => '0', + 'CHEVERETO_MAX_CACHE_TTL' => '86400', 'CHEVERETO_MAX_CATEGORIES' => '0', + 'CHEVERETO_MAX_CHUNK_UPLOAD_SIZE' => '16M', 'CHEVERETO_MAX_EXECUTION_TIME_SECONDS' => '30', 'CHEVERETO_MAX_FILES' => '0', + 'CHEVERETO_MAX_LISTING_ITEMS_PER_PAGE' => '0', 'CHEVERETO_MAX_LOGIN_PROVIDERS' => '0', 'CHEVERETO_MAX_MANAGERS' => '0', 'CHEVERETO_MAX_MEMORY_SIZE' => '512M', 'CHEVERETO_MAX_PAGES' => '0', - 'CHEVERETO_MAX_POST_SIZE' => '100M', + 'CHEVERETO_MAX_POST_SIZE' => '64M', 'CHEVERETO_MAX_STORAGES' => '0', 'CHEVERETO_MAX_TAGS_PER_FILE' => '0', 'CHEVERETO_MAX_TAGS_PER_LISTING' => '0', 'CHEVERETO_MAX_TAGS' => '0', + 'CHEVERETO_MAX_UPLOAD_FILE_SIZE' => '64M', 'CHEVERETO_MAX_UPLOAD_SIZE' => '100M', 'CHEVERETO_MAX_USER_ALBUMS_LIST' => '500', 'CHEVERETO_MAX_USERS' => '0', diff --git a/app/legacy/commands/cache-flush.php b/app/legacy/commands/cache-flush.php new file mode 100644 index 0000000..87ba9b3 --- /dev/null +++ b/app/legacy/commands/cache-flush.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Chevereto\Legacy\Classes\Cache; +use Chevereto\Legacy\Classes\Categories; +use Chevereto\Legacy\Classes\Tags; + +if (! Cache::isEnabled()) { + echo "[ERROR] Cache is not enabled\n"; + exit(255); +} +$keyValue = Cache::instance(); +$redis = $keyValue->redis(); +$prefix = $keyValue->getKey(''); +$topLevel = [ + 'pages_visible', + 'settings', + 'variables', + Categories::CACHE_KEY, + Tags::CACHE_KEY, +]; +$nested = [ + 'ip', + 'l', + 'rl', + 'u', +]; +foreach ($topLevel as $topKey) { + deleteCache($redis, "{$prefix}{$topKey}"); +} +foreach ($nested as $nestedKey) { + iterateCache($redis, "{$prefix}{$nestedKey}:"); +} +iterateCache($redis, $prefix); +function iterateCache(Redis $redis, string $prefix): void +{ + $iterator = null; + while ($iterator !== 0) { + $scan = $redis->scan($iterator, "{$prefix}*"); + foreach ($scan as $key) { + if (str_starts_with($key, "{$prefix}SESSION") + || str_ends_with($key, '.stampede') + ) { + continue; + } + deleteCache($redis, $key); + } + } +} +function deleteCache(Redis $redis, string $key): void +{ + $result = (bool) $redis->del($key); + $status = 'DELETE'; + if ($result === false && ! $redis->get($key)) { + $status = ' 404'; + } + echo "* {$status} > {$key}\n"; +} diff --git a/app/legacy/commands/cache-view.php b/app/legacy/commands/cache-view.php new file mode 100644 index 0000000..11254a0 --- /dev/null +++ b/app/legacy/commands/cache-view.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Chevereto\Legacy\Classes\Cache; +use Chevereto\Legacy\Classes\Categories; +use Chevereto\Legacy\Classes\Tags; +use function Chevereto\Legacy\G\format_bytes; + +if (! Cache::isEnabled()) { + echo "[ERROR] Cache is not enabled\n"; + exit(255); +} +$keyValue = Cache::instance(); +$redis = $keyValue->redis(); +$prefix = $keyValue->getKey(''); +$topLevel = [ + 'pages_visible', + 'settings', + 'variables', + Categories::CACHE_KEY, + Tags::CACHE_KEY, +]; +foreach ($topLevel as $topKey) { + $cacheKey = $prefix . $topKey; + deleteCache($redis, $cacheKey); +} +$nested = [ + 'ip', + 'l', + 'rl', + 'u', +]; +foreach ($nested as $nestedKey) { + $iterator = null; + while ($iterator !== 0) { + $scanKeys = $keyValue->redis()->scan($iterator, "{$prefix}{$nestedKey}:*"); + foreach ($scanKeys as $scanKey) { + deleteCache($redis, $scanKey); + } + } +} +function deleteCache(Redis $redis, string $key): void +{ + $exists = $redis->exists($key); + if (! $exists) { + printf( + "| %-60s | %-12s | %-8s |\n", + $key, + '--', + '--' + ); + + return; + } + $ttl = $redis->ttl($key); + if ($ttl === -1) { + $ttlStr = 'no-expire'; + } elseif ($ttl === -2) { + $ttlStr = 'expired'; + } else { + $ttlStr = $ttl; + } + $bytes = $redis->rawCommand('MEMORY', 'USAGE', $key); + $sizeReadable = format_bytes($bytes); + printf( + "| %-60s | %-12s | %-8s |\n", + $key, + $ttlStr, + $sizeReadable + ); +} diff --git a/app/legacy/commands/cron.php b/app/legacy/commands/cron.php index 748345b..efd73b0 100644 --- a/app/legacy/commands/cron.php +++ b/app/legacy/commands/cron.php @@ -40,10 +40,11 @@ if (getSetting('maintenance')) { exit(255); } $jobs = [ - 'deleteExpiredImages', + 'deleteExpiredFiles', 'cleanUnconfirmedUsers', 'removeDeleteLog', 'storageDelete', + 'deleteExpiredUploads', ]; if ((bool) env()['CHEVERETO_ENABLE_UPDATE_CHECK']) { $jobs[] = 'checkForUpdates'; @@ -152,9 +153,9 @@ function storageDelete(): void } echoLocked($job); } -function deleteExpiredImages(): void +function deleteExpiredFiles(): void { - $job = 'delete-expired-images'; + $job = 'delete-expired-files'; $lock = new Lock($job); if ($lock->create()) { Image::deleteExpired(50); @@ -241,3 +242,46 @@ function checkHtaccess() { include __DIR__ . '/htaccess-enforce.php'; } +function deleteExpiredUploads(): void +{ + $job = 'delete-expired-uploads'; + $lock = new Lock($job); + if ($lock->create()) { + $uploadsTable = DB::getTable('uploads'); + $uploadsChunksTable = DB::getTable('uploads_chunks'); + $db = DB::getInstance(); + $db->query( + <<bind(':time', datetime_sub(datetimegmt(), 'P1D')); + $chunks = $db->fetchAll(); + if (! $chunks) { + $lock->destroy(); + + return; + } + $ids = []; + foreach ($chunks as $chunk) { + $ids[] = (int) $chunk['id']; + $chunkPath = $chunk['path']; + if (file_exists($chunkPath)) { + unlink($chunkPath); + } + } + $ids = array_unique($ids); + $idsString = implode(',', $ids); + $db->query( + <<exec(); + $lock->destroy(); + + return; + } + echoLocked($job); +} diff --git a/app/legacy/commands/install.php b/app/legacy/commands/install.php index b9e196b..fd6856c 100644 --- a/app/legacy/commands/install.php +++ b/app/legacy/commands/install.php @@ -15,4 +15,5 @@ if (cheveretoVersionInstalled() !== '') { echo "[ERROR] Chevereto is already installed, try with the update command\n"; exit(255); } + require_once PATH_APP_LEGACY_INSTALL . 'installer.php'; diff --git a/app/legacy/commands/js.php b/app/legacy/commands/js.php index a9e1b88..dac8591 100644 --- a/app/legacy/commands/js.php +++ b/app/legacy/commands/js.php @@ -9,9 +9,14 @@ * file that was distributed with this source code. */ +use Symfony\Component\Process\Exception\ProcessFailedException; +use Symfony\Component\Process\Process; +use function Chevereto\Legacy\G\str_replace_last; + $workingDir = PATH_PUBLIC_CONTENT_LEGACY_THEMES_PEAFOWL_LIB; $target = 'chevereto-all.js'; $outputFile = $workingDir . $target; +$outputMinifiedFile = $workingDir . str_replace_last('.js', '.min.js', $target); echo "* Compile JavaScript\n"; echo "---\n"; $fh = fopen($outputFile, 'w'); @@ -23,6 +28,7 @@ $files = [ 'js/peafowl.js', 'js/images-loaded.js', 'js/load-image.js', + 'js/xxhash-wasm.js', 'js/clipboard.js', 'js/chevereto.js', ]; @@ -37,5 +43,19 @@ foreach ($files as $file) { } fclose($fh); echo "---\n"; -echo "💯 [OK] {$outputFile}\n"; +echo "[OK] {$outputFile}\n"; +$process = new Process([ + 'uglifyjs', + $outputFile, + '-o', + $outputMinifiedFile, + '-c', + '-m', +]); +$process->run(); +if (! $process->isSuccessful()) { + throw new ProcessFailedException($process); +} +echo "[OK] {$outputMinifiedFile}\n"; + exit(0); diff --git a/app/legacy/commands/update.php b/app/legacy/commands/update.php index bb7bf64..8a291e4 100644 --- a/app/legacy/commands/update.php +++ b/app/legacy/commands/update.php @@ -15,4 +15,5 @@ if (cheveretoVersionInstalled() === '') { echo "[ERROR] Chevereto is not installed, try with the install command.\n"; exit(255); } + require_once PATH_APP_LEGACY_INSTALL . 'installer.php'; diff --git a/app/legacy/entrypoints/cli.php b/app/legacy/entrypoints/cli.php index e6397b3..dcc6cbf 100644 --- a/app/legacy/entrypoints/cli.php +++ b/app/legacy/entrypoints/cli.php @@ -9,6 +9,7 @@ * file that was distributed with this source code. */ +use Chevere\ThrowableHandler\ThrowableHandler; use function Chevereto\Legacy\getCheveretoEnv; use function Chevereto\Legacy\loaderHandler; @@ -24,27 +25,34 @@ if ($opts === []) { } $access = $opts['C']; $options = [ + 'bulk-importer', + 'cache-view', + 'cache-flush', 'cron', - 'update', - 'encrypt-secrets', 'decrypt-secrets', + 'encrypt-secrets', 'htaccess-checksum', 'htaccess-enforce', - 'bulk-importer', 'install', - 'langs', 'js', + 'langs', 'password-reset', 'setting-get', 'setting-update', + 'update', 'version', ]; if (! in_array($access, $options, true)) { echo 'Invalid command' . PHP_EOL; exit(255); } +if (defined('APP_BIN_LEGACY')) { + echo 'Note: This CLI is migrating to app/bin/cli' . PHP_EOL . PHP_EOL; +} define('ACCESS', $access); require_once __DIR__ . '/../load/php-boot.php'; +set_error_handler(ThrowableHandler::ERROR_AS_EXCEPTION); +set_exception_handler(ThrowableHandler::CONSOLE); require_once loaderHandler( $_COOKIE, getCheveretoEnv(), diff --git a/app/legacy/install/installer.php b/app/legacy/install/installer.php index 489cea4..f97b68d 100644 --- a/app/legacy/install/installer.php +++ b/app/legacy/install/installer.php @@ -40,7 +40,7 @@ use function Chevereto\Vars\post; if (PHP_SAPI !== 'cli') { /** @var Handler $handler */ - $context = $handler->request_array()[0] ?? false; + $context = $handler->requestArray()[0] ?? false; if (! $context) { throw new LogicException( message('Missing context') @@ -99,7 +99,7 @@ $settings_updates = [ 'email_from_email' => 'from@chevereto.internal', 'email_from_name' => 'Chevereto', 'email_incoming_email' => 'incoming@chevereto.internal', - 'email_mode' => 'mail', + 'email_mode' => env()['CHEVERETO_SERVICING'] === 'server' ? 'mail' : 'smtp', 'email_smtp_server' => '', 'email_smtp_server_password' => '', 'email_smtp_server_port' => '', @@ -626,7 +626,6 @@ $settings_updates = [ 'route_video' => 'video', 'route_audio' => 'audio', 'cache_ttl' => '0', - 'upload_max_filesize_mb' => (string) min(100, bytes_to_mb(get_ini_bytes(ini_get('upload_max_filesize')))), 'image_load_max_filesize_mb' => '5', 'upload_max_filesize_mb_guest' => '10', 'arachnid_api_username' => '', @@ -637,6 +636,18 @@ $settings_updates = [ '4.2.3' => null, '4.2.4' => null, '4.2.5' => null, + '4.3.0' => [ + 'semantics_video' => '', + 'semantics_videos' => '', + 'semantics_tag' => '', + 'semantics_tags' => '', + 'semantics_file' => '', + 'semantics_files' => '', + 'theme_palette_user_select' => 1, + 'upload_max_filesize_mb' => (string) bytes_to_mb(get_ini_bytes(env()['CHEVERETO_MAX_UPLOAD_SIZE'])), + 'enable_api_user' => 1, + 'enable_api_guest' => 0, + ], ]; /** @@ -1031,12 +1042,33 @@ if ($installed_version !== '' && empty($paramsCheck)) { } $isUtf8mb4 = $installed_version === '' || version_compare($installed_version, '3.12.10', '>'); + $modifyIntUnsignedNotNullAutoIncrement = [ + 'op' => 'MODIFY', + 'type' => 'INT UNSIGNED', + 'prop' => 'NOT NULL AUTO_INCREMENT', + ]; + $modifyIntUnsignedNotNull = [ + 'op' => 'MODIFY', + 'type' => 'INT UNSIGNED', + 'prop' => 'NOT NULL', + ]; + $modifyBigIntUnsignedNotNull = array_merge( + $modifyIntUnsignedNotNull, + [ + 'type' => 'BIGINT UNSIGNED', + ] + ); + $modifyIntUnsignedDefaultNull = [ + 'op' => 'MODIFY', + 'type' => 'INT UNSIGNED', + 'prop' => 'DEFAULT NULL', + ]; $update_table = [ '3.1.0' => [ 'logins' => [ 'login_resource_id' => [ 'op' => 'MODIFY', - 'type' => 'varchar(255)', + 'type' => 'VARCHAR(255)', 'prop' => 'DEFAULT NULL', ], 'login_secret' => [ @@ -1048,7 +1080,7 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'users' => [ 'user_name' => [ 'op' => 'MODIFY', - 'type' => 'varchar(255)', + 'type' => 'VARCHAR(255)', 'prop' => 'DEFAULT NULL', ], ], @@ -1069,7 +1101,7 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'albums' => [ 'album_privacy' => [ 'op' => 'MODIFY', - 'type' => "enum('public','password','private','private_but_link','custom')", + 'type' => "ENUM('public','password','private','private_but_link','custom')", 'prop' => "DEFAULT 'public'", ], ], @@ -1078,7 +1110,7 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'images' => [ 'image_category_id' => [ 'op' => 'ADD', - 'type' => 'bigint(32)', + 'type' => 'INT UNSIGNED', 'prop' => 'DEFAULT NULL', ], ], @@ -1101,12 +1133,12 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'image_storage' => [ 'op' => 'CHANGE', 'to' => 'image_storage_mode', - 'type' => "enum('datefolder','direct','old')", + 'type' => "ENUM('datefolder','direct','old')", 'prop' => "NOT NULL DEFAULT 'datefolder'", ], 'image_chain' => [ 'op' => 'ADD', - 'type' => 'tinyint(128)', + 'type' => 'TINYINT', 'prop' => 'NOT NULL', 'tail' => << [ 'storage_region' => [ 'op' => 'ADD', - 'type' => 'varchar(255)', + 'type' => 'VARCHAR(255)', 'prop' => 'DEFAULT NULL', ], ], @@ -1130,17 +1162,17 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'storages' => [ 'storage_server' => [ 'op' => 'ADD', - 'type' => 'varchar(255)', + 'type' => 'VARCHAR(255)', 'prop' => 'DEFAULT NULL', ], 'storage_capacity' => [ 'op' => 'ADD', - 'type' => 'bigint(32)', + 'type' => 'BIGINT UNSIGNED', 'prop' => 'DEFAULT NULL', ], 'storage_space_used' => [ 'op' => 'ADD', - 'type' => 'bigint(32)', + 'type' => 'BIGINT UNSIGNED', 'prop' => "DEFAULT '0'", 'tail' => << [ 'image_thumb_size' => [ 'op' => 'ADD', - 'type' => 'int(11)', + 'type' => 'INT UNSIGNED', 'prop' => 'NOT NULL', ], 'image_medium_size' => [ 'op' => 'ADD', - 'type' => 'int(11)', + 'type' => 'INT UNSIGNED', 'prop' => "NOT NULL DEFAULT '0'", ], ], @@ -1166,7 +1198,7 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'queues' => [ 'queue_type' => [ 'op' => 'MODIFY', - 'type' => "enum('storage-delete')", + 'type' => "ENUM('storage-delete')", 'prop' => 'NOT NULL', 'tail' => << [ 'storage_server' => [ 'op' => 'MODIFY', - 'type' => 'varchar(255)', + 'type' => 'VARCHAR(255)', 'prop' => 'DEFAULT NULL', ], ], @@ -1199,7 +1231,7 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'images' => [ 'image_title' => [ 'op' => 'ADD', - 'type' => 'varchar(100)', // 3.6.5 + 'type' => 'VARCHAR(100)', // 3.6.5 'prop' => 'DEFAULT NULL', 'tail' => << [ 'album_name' => [ 'op' => 'MODIFY', - 'type' => 'varchar(100)', // 3.6.5 + 'type' => 'VARCHAR(100)', // 3.6.5 'prop' => 'NOT NULL', ], ], @@ -1221,12 +1253,12 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'queues' => [ 'queue_attempts' => [ 'op' => 'ADD', - 'type' => 'varchar(255)', + 'type' => 'VARCHAR(255)', 'prop' => 'DEFAULT 0', ], 'queue_status' => [ 'op' => 'ADD', - 'type' => "enum('pending','failed')", + 'type' => "ENUM('pending','failed')", 'prop' => "NOT NULL DEFAULT 'pending'", ], ], @@ -1244,17 +1276,17 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'users' => [ 'user_newsletter_subscribe' => [ 'op' => 'ADD', - 'type' => 'tinyint(1)', + 'type' => 'TINYINT', 'prop' => "NOT NULL DEFAULT '1'", ], 'user_show_nsfw_listings' => [ 'op' => 'ADD', - 'type' => 'tinyint(1)', + 'type' => 'TINYINT', 'prop' => "NOT NULL DEFAULT '0'", ], 'user_bio' => [ 'op' => 'ADD', - 'type' => 'varchar(255)', + 'type' => 'VARCHAR(255)', 'prop' => 'DEFAULT NULL', ], ], @@ -1277,12 +1309,12 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'storages' => [ 'storage_account_id' => [ 'op' => 'ADD', - 'type' => 'varchar(255)', + 'type' => 'VARCHAR(255)', 'prop' => 'DEFAULT NULL', ], 'storage_account_name' => [ 'op' => 'ADD', - 'type' => 'varchar(255)', + 'type' => 'VARCHAR(255)', 'prop' => 'DEFAULT NULL', ], ], @@ -1303,14 +1335,14 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'images' => [ 'image_title' => [ 'op' => 'MODIFY', - 'type' => 'varchar(100)', + 'type' => 'VARCHAR(100)', 'prop' => 'DEFAULT NULL', ], ], 'albums' => [ 'album_name' => [ 'op' => 'MODIFY', - 'type' => 'varchar(100)', + 'type' => 'VARCHAR(100)', 'prop' => 'NOT NULL', ], ], @@ -1322,12 +1354,12 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'users' => [ 'user_image_keep_exif' => [ 'op' => 'ADD', - 'type' => 'tinyint(1)', + 'type' => 'TINYINT', 'prop' => "NOT NULL DEFAULT '1'", ], 'user_image_expiration' => [ 'op' => 'ADD', - 'type' => 'varchar(255)', + 'type' => 'VARCHAR(255)', 'prop' => 'DEFAULT NULL', ], ], @@ -1348,56 +1380,56 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'albums' => [ 'album_creation_ip' => [ 'op' => 'ADD', - 'type' => 'varchar(255)', + 'type' => 'VARCHAR(255)', 'prop' => 'NOT NULL', ], 'album_likes' => [ 'op' => 'ADD', - 'type' => 'bigint(32)', + 'type' => 'INT UNSIGNED', 'prop' => "NOT NULL DEFAULT '0'", ], ], 'images' => [ 'image_likes' => [ 'op' => 'ADD', - 'type' => 'bigint(32)', + 'type' => 'INT UNSIGNED', 'prop' => "NOT NULL DEFAULT '0'", ], ], 'users' => [ 'user_registration_ip' => [ 'op' => 'ADD', - 'type' => 'varchar(255)', + 'type' => 'VARCHAR(255)', 'prop' => 'NOT NULL', ], 'user_likes' => [ 'op' => 'ADD', - 'type' => 'bigint(32)', + 'type' => 'INT UNSIGNED', 'prop' => "NOT NULL DEFAULT '0' COMMENT 'Likes made to content owned by this user'", ], 'user_liked' => [ 'op' => 'ADD', - 'type' => 'bigint(32)', + 'type' => 'INT UNSIGNED', 'prop' => "NOT NULL DEFAULT '0' COMMENT 'Likes made by this user'", ], 'user_following' => [ 'op' => 'ADD', - 'type' => 'bigint(32)', + 'type' => 'INT UNSIGNED', 'prop' => "NOT NULL DEFAULT '0'", ], 'user_followers' => [ 'op' => 'ADD', - 'type' => 'bigint(32)', + 'type' => 'INT UNSIGNED', 'prop' => "NOT NULL DEFAULT '0'", ], 'user_content_views' => [ 'op' => 'ADD', - 'type' => 'bigint(32)', + 'type' => 'INT UNSIGNED', 'prop' => "NOT NULL DEFAULT '0'", ], 'user_notifications_unread' => [ 'op' => 'ADD', - 'type' => 'bigint(32)', + 'type' => 'INT UNSIGNED', 'prop' => "NOT NULL DEFAULT '0'", ], ], @@ -1407,14 +1439,14 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'users' => [ 'user_is_private' => [ 'op' => 'ADD', - 'type' => 'tinyint(1)', + 'type' => 'TINYINT', 'prop' => "NOT NULL DEFAULT '0'", ], ], 'storages' => [ 'storage_service' => [ 'op' => 'ADD', - 'type' => 'varchar(255)', + 'type' => 'VARCHAR(255)', 'prop' => 'DEFAULT NULL', ], ], @@ -1423,7 +1455,7 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'images' => [ 'image_is_animated' => [ 'op' => 'ADD', - 'type' => 'tinyint(1)', + 'type' => 'TINYINT', 'prop' => "NOT NULL DEFAULT '0'", ], ], @@ -1438,7 +1470,7 @@ if ($installed_version !== '' && empty($paramsCheck)) { //'request_type' => [], void in 4.0.0-beta.10 'request_content_id' => [ 'op' => 'ADD', - 'type' => 'bigint(32)', + 'type' => 'INT UNSIGNED', 'prop' => 'DEFAULT NULL', ], ], @@ -1447,39 +1479,39 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'albums' => [ 'album_views' => [ 'op' => 'ADD', - 'type' => 'bigint(32)', + 'type' => 'INT UNSIGNED', 'prop' => "NOT NULL DEFAULT '0'", ], ], 'likes' => [ 'like_content_type' => [ 'op' => 'MODIFY', - 'type' => "enum('image','album')", + 'type' => "ENUM('image','album')", 'prop' => 'DEFAULT NULL', ], ], 'notifications' => [ 'notification_content_type' => [ 'op' => 'MODIFY', - 'type' => "enum('user','image','album')", + 'type' => "ENUM('user','image','album')", 'prop' => 'NOT NULL', ], ], 'stats' => [ 'stat_album_views' => [ 'op' => 'ADD', - 'type' => 'bigint(32)', + 'type' => 'INT UNSIGNED', 'prop' => "NOT NULL DEFAULT '0'", ], 'stat_album_likes' => [ 'op' => 'ADD', - 'type' => 'bigint(32)', + 'type' => 'INT UNSIGNED', 'prop' => "NOT NULL DEFAULT '0'", ], 'stat_likes' => [ 'op' => 'CHANGE', 'to' => 'stat_image_likes', - 'type' => 'bigint(32)', + 'type' => 'INT UNSIGNED', 'prop' => "NOT NULL DEFAULT '0'", ], ], @@ -1488,7 +1520,7 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'images' => [ 'image_source_md5' => [ 'op' => 'ADD', - 'type' => 'varchar(32)', + 'type' => 'VARCHAR(32)', 'prop' => 'DEFAULT NULL', ], ], @@ -1504,26 +1536,27 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'images' => [ 'image_storage_mode' => [ 'op' => 'MODIFY', - 'type' => "enum('datefolder','direct','old','path')", + 'type' => "ENUM('datefolder','direct','old','path')", 'prop' => "NOT NULL DEFAULT 'datefolder'", ], 'image_path' => [ 'op' => 'ADD', - 'type' => 'varchar(4096)', - 'prop' => 'DEFAULT NULL', - ], - ], - 'albums' => [ - 'album_user_id' => [ - 'op' => 'MODIFY', - 'type' => 'bigint(32)', + 'type' => 'VARCHAR(4096)', 'prop' => 'DEFAULT NULL', ], ], + // 4.3.0 + // 'albums' => [ + // 'album_user_id' => [ + // 'op' => 'MODIFY', + // 'type' => 'INT UNSIGNED', + // 'prop' => 'DEFAULT NULL', + // ], + // ], 'users' => [ 'user_is_manager' => [ 'op' => 'ADD', - 'type' => 'tinyint(1)', + 'type' => 'TINYINT', 'prop' => "NOT NULL DEFAULT '0'", ], ], @@ -1541,7 +1574,7 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'pages' => [ 'page_internal' => [ 'op' => 'ADD', - 'type' => 'varchar(255)', + 'type' => 'VARCHAR(255)', 'prop' => 'DEFAULT NULL', ], ], @@ -1555,21 +1588,21 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'settings' => [ 'setting_name' => [ 'op' => 'MODIFY', - 'type' => 'varchar(255)', + 'type' => 'VARCHAR(255)', 'prop' => 'CHARACTER SET utf8 COLLATE utf8_bin NOT NULL', ], ], 'deletions' => [ 'deleted_content_ip' => [ 'op' => 'MODIFY', - 'type' => 'varchar(255)', + 'type' => 'VARCHAR(255)', 'prop' => 'NOT NULL', ], ], 'ip_bans' => [ 'ip_ban_ip' => [ 'op' => 'MODIFY', - 'type' => 'varchar(255)', + 'type' => 'VARCHAR(255)', 'prop' => 'NOT NULL', ], 'ip_ban_message' => [ @@ -1581,29 +1614,29 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'pages' => [ 'page_internal' => [ 'op' => 'MODIFY', - 'type' => 'varchar(255)', + 'type' => 'VARCHAR(255)', 'prop' => 'DEFAULT NULL', ], ], 'users' => [ 'user_username' => [ 'op' => 'MODIFY', - 'type' => 'varchar(255)', + 'type' => 'VARCHAR(255)', 'prop' => 'NOT NULL', ], 'user_email' => [ 'op' => 'MODIFY', - 'type' => 'varchar(255)', + 'type' => 'VARCHAR(255)', 'prop' => 'DEFAULT NULL', ], 'user_image_expiration' => [ 'op' => 'MODIFY', - 'type' => 'varchar(255)', + 'type' => 'VARCHAR(255)', 'prop' => 'DEFAULT NULL', ], 'user_registration_ip' => [ 'op' => 'MODIFY', - 'type' => 'varchar(255)', + 'type' => 'VARCHAR(255)', 'prop' => 'NOT NULL', ], ], @@ -1693,14 +1726,14 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'deletions' => [ 'deleted_content_original_filename' => [ 'op' => 'MODIFY', - 'type' => 'varchar(255)', + 'type' => 'VARCHAR(255)', 'prop' => 'DEFAULT NULL', ], ], 'logins' => [ 'login_type' => [ 'op' => 'MODIFY', - 'type' => "enum('password','session','cookie','facebook','twitter','google','vk','cookie_facebook','cookie_twitter','cookie_google','cookie_vk')", + 'type' => "ENUM('password','session','cookie','facebook','twitter','google','vk','cookie_facebook','cookie_twitter','cookie_google','cookie_vk')", 'prop' => 'NOT NULL', ], ], @@ -1709,7 +1742,7 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'imports' => [ 'import_continuous' => [ 'op' => 'ADD', - 'type' => 'tinyint(1)', + 'type' => 'TINYINT', 'prop' => "NOT NULL DEFAULT '0'", ], ], @@ -1730,7 +1763,7 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'images' => [ 'image_is_approved' => [ 'op' => 'ADD', - 'type' => 'tinyint(1)', + 'type' => 'TINYINT', 'prop' => "NOT NULL DEFAULT '1'", ], ], @@ -1739,19 +1772,19 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'albums' => [ 'album_cover_id' => [ 'op' => 'ADD', - 'type' => 'bigint(32)', + 'type' => 'INT UNSIGNED', 'prop' => 'DEFAULT NULL', ], 'album_parent_id' => [ 'op' => 'ADD', - 'type' => 'bigint(32)', + 'type' => 'INT UNSIGNED', 'prop' => 'DEFAULT NULL', ], ], 'images' => [ 'image_is_360' => [ 'op' => 'ADD', - 'type' => 'tinyint(1)', + 'type' => 'TINYINT', 'prop' => "NOT NULL DEFAULT '0'", ], ], @@ -1810,7 +1843,7 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'users' => [ 'user_palette_id' => [ 'op' => 'ADD', - 'type' => 'int(11)', + 'type' => 'INT UNSIGNED', 'prop' => "NOT NULL DEFAULT '0'", ], ], @@ -1827,7 +1860,7 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'requests' => [ 'request_type' => [ 'op' => 'MODIFY', - 'type' => "enum('upload','signup','account-edit','account-password-forgot','account-password-reset','account-resend-activation','account-email-needed','account-change-email','account-activate','login','content-password','account-two-factor')", + 'type' => "ENUM('upload','signup','account-edit','account-password-forgot','account-password-reset','account-resend-activation','account-email-needed','account-change-email','account-activate','login','content-password','account-two-factor')", 'prop' => 'NOT NULL', ], ], @@ -1885,7 +1918,7 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'albums' => [ 'album_cta_enable' => [ 'op' => 'ADD', - 'type' => 'tinyint(1)', + 'type' => 'TINYINT', 'prop' => "NOT NULL DEFAULT '0'", ], 'album_cta' => [ @@ -1899,29 +1932,25 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'storages' => [ 'storage_type_chain' => [ 'op' => 'ADD', - 'type' => 'tinyint(3) UNSIGNED', + 'type' => 'TINYINT UNSIGNED', 'prop' => 'NOT NULL DEFAULT "1"', ], ], 'images' => [ - 'image_size' => [ - 'op' => 'MODIFY', - 'type' => 'bigint(11) UNSIGNED', - 'prop' => 'NOT NULL', - ], + 'image_size' => $modifyBigIntUnsignedNotNull, 'image_frame_size' => [ 'op' => 'ADD', - 'type' => 'int(11)', + 'type' => 'INT UNSIGNED', 'prop' => "NOT NULL DEFAULT '0'", ], 'image_duration' => [ 'op' => 'ADD', - 'type' => 'int(11)', + 'type' => 'INT UNSIGNED', 'prop' => "NOT NULL DEFAULT '0'", ], 'image_type' => [ 'op' => 'ADD', - 'type' => 'tinyint(3) UNSIGNED', + 'type' => 'TINYINT UNSIGNED', 'prop' => "as (case when `image_extension` in ('pdf','doc','md') then 4 when `image_extension` in ('mp3','m4a','wav') then 3 @@ -1935,14 +1964,14 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'users' => [ 'user_file_meta_tag_camera_model' => [ 'op' => 'ADD', - 'type' => 'tinyint(1)', + 'type' => 'TINYINT', 'prop' => "NOT NULL DEFAULT '0'", ], ], 'categories' => [ 'category_url_key' => [ 'op' => 'MODIFY', - 'type' => 'varchar(32)', + 'type' => 'VARCHAR(32)', 'collation' => 'utf8mb4_bin', 'prop' => 'NOT NULL', ], @@ -1950,7 +1979,7 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'storages' => [ 'storage_use_path_style_endpoint' => [ 'op' => 'ADD', - 'type' => 'tinyint(1) UNSIGNED', + 'type' => 'TINYINT UNSIGNED', 'prop' => 'NOT NULL DEFAULT "0"', ], 'storage_deleted_at' => [ @@ -1962,7 +1991,7 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'images' => [ 'image_type' => [ 'op' => 'MODIFY', - 'type' => 'tinyint(3) UNSIGNED', + 'type' => 'TINYINT UNSIGNED', 'prop' => "as (case when `image_extension` in ('pdf','doc','md') then 4 when `image_extension` in ('mp3','m4a','wav') then 3 @@ -1974,17 +2003,17 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'stats' => [ 'stat_tags' => [ 'op' => 'ADD', - 'type' => 'bigint(32) UNSIGNED', + 'type' => 'INT UNSIGNED', 'prop' => "NOT NULL DEFAULT '0'", ], 'stat_cron_runs' => [ 'op' => 'ADD', - 'type' => 'bigint(32) UNSIGNED', + 'type' => 'INT UNSIGNED', 'prop' => "NOT NULL DEFAULT '0'", ], 'stat_cron_time' => [ 'op' => 'ADD', - 'type' => 'bigint(32) UNSIGNED', + 'type' => 'INT UNSIGNED', 'prop' => "NOT NULL DEFAULT '0'", ], ], @@ -2025,11 +2054,159 @@ if ($installed_version !== '' && empty($paramsCheck)) { 'importing' => [ 'importing_content_id' => [ 'op' => 'MODIFY', - 'type' => 'bigint(32)', + 'type' => 'INT UNSIGNED', 'prop' => 'DEFAULT NULL', ], ], ], + '4.3.0' => [ + 'query.1' => $db->getSqlDropForeignKey('tags_albums', 'tags_albums_ibfk_1') + . $db->getSqlDropForeignKey('tags_albums', 'tags_albums_ibfk_2') + . $db->getSqlDropForeignKey('tags_albums', 'tags_albums_ibfk_3') + . $db->getSqlDropForeignKey('tags_users', 'tags_users_ibfk_1') + . $db->getSqlDropForeignKey('tags_users', 'tags_users_ibfk_2') + . $db->getSqlDropForeignKey('tags_files', 'tags_files_ibfk_1') + . $db->getSqlDropForeignKey('tags_files', 'tags_files_ibfk_2') + . $db->getSqlDropIndex('images', 'image_md5') + . $db->getSqlDropIndex('images', 'image_source_md5') + . $db->getSqlDropIndex('deletions', 'deleted_content_md5'), + 'assets' => [ + 'asset_md5' => [ + 'op' => 'CHANGE', + 'to' => 'asset_checksum', + 'type' => 'VARCHAR(32)', + 'prop' => 'NOT NULL', + ], + ], + 'albums' => [ + 'album_id' => $modifyIntUnsignedNotNullAutoIncrement, + 'album_user_id' => $modifyIntUnsignedDefaultNull, + 'album_cover_id' => $modifyIntUnsignedDefaultNull, + 'album_parent_id' => $modifyIntUnsignedDefaultNull, + ], + 'api_keys' => [ + 'api_key_id' => $modifyIntUnsignedNotNullAutoIncrement, + 'api_key_user_id' => $modifyIntUnsignedDefaultNull, + ], + 'confirmations' => [ + 'confirmation_id' => $modifyIntUnsignedNotNullAutoIncrement, + 'confirmation_user_id' => $modifyIntUnsignedNotNull, + ], + 'deletions' => [ + 'deleted_id' => $modifyIntUnsignedNotNullAutoIncrement, + 'deleted_content_id' => $modifyIntUnsignedNotNull, + 'deleted_content_user_id' => $modifyIntUnsignedDefaultNull, + 'deleted_content_md5' => [ + 'op' => 'CHANGE', + 'to' => 'deleted_content_checksum', + 'type' => 'VARCHAR(32)', + 'prop' => 'DEFAULT NULL', + ], + ], + 'follows' => [ + 'follow_id' => $modifyIntUnsignedNotNullAutoIncrement, + 'follow_user_id' => $modifyIntUnsignedNotNull, + 'follow_followed_user_id' => $modifyIntUnsignedNotNull, + ], + 'images' => [ + 'image_id' => $modifyIntUnsignedNotNullAutoIncrement, + 'image_user_id' => $modifyIntUnsignedDefaultNull, + 'image_album_id' => $modifyIntUnsignedDefaultNull, + 'image_storage_id' => $modifyIntUnsignedDefaultNull, + 'image_category_id' => $modifyIntUnsignedDefaultNull, + 'image_md5' => [ + 'op' => 'CHANGE', + 'to' => 'image_checksum', + 'type' => 'VARCHAR(32)', + 'prop' => 'NOT NULL', + ], + 'image_source_md5' => [ + 'op' => 'CHANGE', + 'to' => 'image_source_checksum', + 'type' => 'VARCHAR(32)', + 'prop' => 'DEFAULT NULL', + ], + ], + 'logins' => [ + 'login_id' => $modifyIntUnsignedNotNullAutoIncrement, + 'login_user_id' => $modifyIntUnsignedNotNull, + ], + 'login_passwords' => [ + 'login_password_id' => $modifyIntUnsignedNotNullAutoIncrement, + 'login_password_user_id' => $modifyIntUnsignedNotNull, + ], + 'login_cookies' => [ + 'login_cookie_id' => $modifyIntUnsignedNotNullAutoIncrement, + 'login_cookie_user_id' => $modifyIntUnsignedNotNull, + 'login_cookie_connection_id' => [ + 'op' => 'MODIFY', + 'type' => 'INT UNSIGNED', + 'prop' => 'DEFAULT 0', + ], + ], + 'login_connections' => [ + 'login_connection_id' => $modifyIntUnsignedNotNullAutoIncrement, + 'login_connection_user_id' => $modifyIntUnsignedNotNull, + 'login_connection_provider_id' => $modifyIntUnsignedNotNull, + ], + 'likes' => [ + 'like_id' => $modifyIntUnsignedNotNullAutoIncrement, + 'like_user_id' => $modifyIntUnsignedDefaultNull, + 'like_content_id' => $modifyIntUnsignedNotNull, + 'like_content_user_id' => $modifyIntUnsignedDefaultNull, + ], + 'notifications' => [ + 'notification_id' => $modifyIntUnsignedNotNullAutoIncrement, + 'notification_user_id' => $modifyIntUnsignedNotNull, + 'notification_trigger_user_id' => $modifyIntUnsignedDefaultNull, + 'notification_type_id' => $modifyIntUnsignedNotNull, + ], + 'tags_albums' => [ + 'tag_album_tag_id' => $modifyIntUnsignedNotNull, + 'tag_album_user_id' => $modifyIntUnsignedNotNull, + 'tag_album_album_id' => $modifyIntUnsignedNotNull, + ], + 'tags_files' => [ + 'tag_file_tag_id' => $modifyIntUnsignedNotNull, + 'tag_file_file_id' => $modifyIntUnsignedNotNull, + ], + 'tags_users' => [ + 'tag_user_tag_id' => $modifyIntUnsignedNotNull, + 'tag_user_user_id' => $modifyIntUnsignedNotNull, + ], + 'tags' => [ + 'tag_id' => $modifyIntUnsignedNotNullAutoIncrement, + 'tag_user_id' => $modifyIntUnsignedNotNull, + ], + 'two_factors' => [ + 'two_factor_id' => $modifyIntUnsignedNotNullAutoIncrement, + 'two_factor_user_id' => $modifyIntUnsignedDefaultNull, + ], + 'requests' => [ + 'request_id' => $modifyIntUnsignedNotNullAutoIncrement, + 'request_user_id' => $modifyIntUnsignedDefaultNull, + 'request_content_id' => $modifyIntUnsignedDefaultNull, + ], + 'users' => [ + 'user_id' => $modifyIntUnsignedNotNullAutoIncrement, + 'user_palette_id' => [ + 'op' => 'MODIFY', + 'type' => 'INT UNSIGNED', + 'prop' => "NOT NULL DEFAULT '0'", + ], + ], + 'uploads' => [], + 'uploads_chunks' => [], + 'query.2' => << $changes) { + if (version_compare($version, $installed_version, '<=')) { + continue; + } foreach ($changes as $table => $columns) { - if ($table === 'query') { + if ($table === 'query' || str_starts_with($table, 'query.')) { + if (version_compare($version, $installed_version, '>')) { + $sql_update[] = (string) $columns; + } + continue; } $schema_table = $schema[$table] ?? []; @@ -2101,11 +2285,16 @@ if ($installed_version !== '' && empty($paramsCheck)) { $columnCollation = mb_strtolower($column_meta['collation'] ?? ''); $collationUpdated = $columnCollation !== '' && $schemaCollation !== $columnCollation; + $dataType = mb_strtoupper($schema_column['DATA_TYPE']); + $columnMetaType = mb_strtoupper($column_meta['type']); + $schemaColumn = mb_strtoupper($schema_column['COLUMN_TYPE']); + if (in_array($dataType, ['INT', 'TINYINT', 'BIGINT'])) { + $schemaColumn = preg_replace('/\(\d+\)/', '', $schemaColumn); + } if ( array_key_exists($column, $schema[$table]) && ( - mb_strtolower($schema_column['COLUMN_TYPE']) !== mb_strtolower($column_meta['type']) - || ($isGenerated && $schemaExpression !== $columnExpression) + $schemaColumn !== $columnMetaType || $collationUpdated || preg_match('/DEFAULT NULL/i', $column_meta['prop'] ?? '') && $schema_column['IS_NULLABLE'] === 'NO' @@ -2119,7 +2308,9 @@ if ($installed_version !== '' && empty($paramsCheck)) { break; case 'CHANGE': - if (array_key_exists($column, $schema[$table])) { + if (array_key_exists($column, $schema[$table]) + && ! array_key_exists($column_meta['to'], $schema[$table]) + ) { $query = '`%column` `%to` %type'; } @@ -2146,9 +2337,6 @@ if ($installed_version !== '' && empty($paramsCheck)) { } } } - if (isset($changes['query']) && version_compare($version, $installed_version, '>')) { - $sql_update[] = (string) $changes['query']; - } } foreach ($CHV_indexes as $table => $indexes) { $field_prefix = DB::getFieldPrefix($table); @@ -2210,11 +2398,11 @@ if ($installed_version !== '' && empty($paramsCheck)) { } } $APP_VERSION = APP_VERSION; - $sql_update[] = <<query($sql_update); xr($sql_update); + $updated = false; try { logger("[STATUS] Updating Chevereto database (this may take a while)...\n"); logger("[SQL]\n{$sql_update}\n"); $updated = $db->exec(); + $versionDatabase = $db::get( + table: 'variables', + where: [ + 'variable_name' => 'chevereto_version_installed', + ], + limit: 1, + )['variable_value'] ?? ''; + if ($versionDatabase !== APP_VERSION) { + throw new LogicException(); + } } catch (Throwable $e) { throw new LogicException( (string) message( @@ -2283,6 +2482,8 @@ if ($installed_version !== '' && empty($paramsCheck)) { ); } if ($updated) { + new Settings(reCache: true); + new Variable(reCache: true); $itWasUpdated = true; } $doing = 'updated'; @@ -2412,32 +2613,32 @@ EOT; <<withIsDebug($doDebug); $internalHandler = $publicHandler->withIsDebug(true); + $doDebug = in_array($debugLevel, [2, 3], true) || isDebug(); + if ($doDebug === false) { + $publicHandler = $publicHandler + ->withIsDebug($doDebug) + ->withPutExtra( + 'Why am I seeing this?', + <<withPutExtra( + 'Administrator guide', + << +
  • Refer to the Chevereto documentation to understand how to debug this error.
  • +
  • Need help? Visit Chevereto support to open a ticket.
  • + + + HTML + ); + } $method = server()['REQUEST_METHOD'] ?? ''; $uri = server()['REQUEST_URI'] ?? ''; $uri = strtok($uri, '?'); diff --git a/app/legacy/load/web.php b/app/legacy/load/web.php index ce7773b..1ebe9e6 100644 --- a/app/legacy/load/web.php +++ b/app/legacy/load/web.php @@ -10,7 +10,8 @@ */ use Chevereto\Config\Config; -use Chevereto\Legacy\Classes\DB; +use Chevereto\Legacy\Classes\Cache; +use Chevereto\Legacy\Classes\Categories; use Chevereto\Legacy\Classes\Fonts; use Chevereto\Legacy\Classes\IpBan; use Chevereto\Legacy\Classes\L10n; @@ -19,7 +20,7 @@ use Chevereto\Legacy\Classes\Page; use Chevereto\Legacy\Classes\Palettes; use Chevereto\Legacy\Classes\RequestLog; use Chevereto\Legacy\Classes\Settings; -use Chevereto\Legacy\Classes\Tag; +use Chevereto\Legacy\Classes\Tags; use Chevereto\Legacy\Classes\User; use Chevereto\Legacy\G\Handler; use function Chevereto\Legacy\badgePaid; @@ -52,31 +53,19 @@ if (cheveretoVersionInstalled() === '') { loadTemplate: ! REPL, // @phpstan-ignore-line before: function ($handler) { headersNoCache(); - if ($handler->request_array()[0] !== 'install') { + if ($handler->requestArray()[0] !== 'install') { redirect('install', 302); } }, ); } -$bannedIp = IpBan::getSingle(); -if ($bannedIp !== []) { - headersNoCache(); - // TODO: Cache until ban expires - if (is_url($bannedIp['message'] ?? false)) { - redirect($bannedIp['message'], 301); - } else { - $exitMessage = $bannedIp['message'] ?? ''; - $exitMessage = match ($exitMessage) { - '' => _s('You have been forbidden to use this website.'), - default => $bannedIp['message'], - }; - exit($exitMessage); - } -} $hook_before = function (Handler $handler) { header('Permissions-Policy: unload=()'); header('Permissions-Policy: interest-cohort=()'); header("Content-Security-Policy: frame-ancestors 'none'"); + $dayCacheRoutes = [ + 'webmanifest', + ]; $exitEarlyRoutes = [ 'webmanifest', ]; @@ -96,17 +85,46 @@ $hook_before = function (Handler $handler) { 'settings', 'redirect', ]; + $doNotCheckBanRoutes = [ + 'webmanifest', + ]; + if (! in_array($handler->requestArray()[0], $doNotCheckBanRoutes, true)) { + $bannedIp = IpBan::getSingle(); + if ($bannedIp !== []) { + headersNoCache(); + // TODO: Cache until ban expires + if (is_url($bannedIp['message'] ?? false)) { + redirect($bannedIp['message'], 301); + } else { + $exitMessage = $bannedIp['message'] ?? ''; + $exitMessage = match ($exitMessage) { + '' => _s('You have been forbidden to use this website.'), + default => $bannedIp['message'], + }; + exit($exitMessage); + } + } + } $cache_ttl = (int) max(0, getSetting('cache_ttl') ?? 0); - if (in_array($handler->request_array()[0], $doNotCacheRoutes, true)) { + if (in_array($handler->requestArray()[0], $dayCacheRoutes, true)) { + $cache_ttl = 86400; // 1 day + } + if (in_array($handler->requestArray()[0], $doNotCacheRoutes, true)) { headersNoCache(); } elseif ($cache_ttl > 0) { headersResetCache(); header("Cache-Control: private, max-age={$cache_ttl}"); } - if (in_array($handler->request_array()[0], $exitEarlyRoutes, true)) { + if (in_array($handler->requestArray()[0], $exitEarlyRoutes, true)) { return; } - $failed_access_requests = RequestLog::getCounts(['login', 'signup'], 'fail'); + $failTriggers = [ + 'account-password-forgot', + 'account-two-factor', + 'login', + 'signup', + ]; + $failed_access_requests = RequestLog::getCounts($failTriggers, 'fail'); if (is_max_invalid_request($failed_access_requests['day'])) { set_status_header(403); } else { @@ -119,9 +137,9 @@ $hook_before = function (Handler $handler) { if (Login::getUser()['status'] === 'banned') { set_status_header(403); } - if (sessionVar()->hasKey('challenge_two_factor') + if (sessionVar()->has('challenge_two_factor') && ! in_array($handler->getRoutePath(), ['account/two-factor', 'captcha-verify', 'logout'], true) - && $handler->request_array()[0] !== 'page' + && $handler->requestArray()[0] !== 'page' ) { headersNoCache(); redirect('account/two-factor', 302); @@ -138,9 +156,10 @@ $hook_before = function (Handler $handler) { ); if (http_response_code() === 403) { headersNoCache(); + echo '403 Forbidden'; exit(); } - if ($handler->request_array()[0] !== 'api' + if ($handler->requestArray()[0] !== 'api' && Settings::get('enable_uploads_url') && ! Login::isAdmin()) { Settings::setValue('enable_uploads_url', 0); } @@ -212,10 +231,10 @@ $hook_before = function (Handler $handler) { $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'], true)) { + if (in_array($handler->requestArray()[0], ['login', 'signup', 'account'], true)) { $paletteId = 0; } else { - $paletteId = Login::isLoggedUser() + $paletteId = Login::isLoggedUser() & Settings::get('theme_palette_user_select') ? Login::getUser()['palette_id'] : Settings::get('theme_palette'); } @@ -227,7 +246,7 @@ $hook_before = function (Handler $handler) { $handler::setVar('theme_palette', $paletteId); $handler::setVar('theme_palette_handle', $theme_palette_handle); if ($handler::cond('maintenance') - && $handler->request_array()[0] === 'dashboard') { + && $handler->requestArray()[0] === 'dashboard') { headersNoCache(); redirect('login', 302); } @@ -273,24 +292,24 @@ $hook_before = function (Handler $handler) { $userMapPaths[] = getSetting('user_profile_view') === 'files' ? 'albums' : 'files'; - if ($handler->request_array()[0] === '/' + if ($handler->requestArray()[0] === '/' && getSetting('website_mode_personal_routing') === '/' && in_array(key($querystr), ['random'], true) ) { $handler->mapRoute('index'); - } elseif ($handler->request_array()[0] === 'search' - && in_array($handler->request_array()[1] ?? [], ['images', 'albums', 'users'], true) + } elseif ($handler->requestArray()[0] === 'search' + && in_array($handler->requestArray()[1] ?? [], ['images', 'albums', 'users'], true) ) { $handler->mapRoute('search'); - } elseif ($handler->request_array()[0] === getSetting('website_mode_personal_routing') + } elseif ($handler->requestArray()[0] === getSetting('website_mode_personal_routing') || (getSetting('website_mode_personal_routing') === '/' - && in_array($handler->request_array()[0], $userMapPaths, true)) + && in_array($handler->requestArray()[0], $userMapPaths, true)) ) { $handler->mapRoute('user', [ 'id' => getSetting('website_mode_personal_uid'), ]); } - if ($handler->request_array()[0] === '/' + if ($handler->requestArray()[0] === '/' && ! in_array(key($querystr), ['random', 'lang'], true) && ! $handler::cond('mapped_route') ) { @@ -315,17 +334,17 @@ $hook_before = function (Handler $handler) { } } } else { - if ($base !== 'index' and ! is_route_available($handler->request_array()[0])) { + if ($base !== 'index' and ! is_route_available($handler->requestArray()[0])) { $mapTo = getSetting('root_route'); $handler->mapRoute($mapTo); } } $virtual_routes = ['image', 'album', 'user', 'video', 'audio']; - if (in_array($handler->request_array()[0], $virtual_routes, true)) { - $virtual_route = getSetting('route_' . $handler->request_array()[0]); - if ($handler->request_array()[0] !== $virtual_route) { + if (in_array($handler->requestArray()[0], $virtual_routes, true)) { + $virtual_route = getSetting('route_' . $handler->requestArray()[0]); + if ($handler->requestArray()[0] !== $virtual_route) { $virtualized_url = str_replace( - get_base_url($handler->request_array()[0]), + get_base_url($handler->requestArray()[0]), get_base_url($virtual_route), get_current_url() ); @@ -334,9 +353,9 @@ $hook_before = function (Handler $handler) { return; } } - if ($base !== 'index' && ! is_route_available($handler->request_array()[0])) { + if ($base !== 'index' && ! is_route_available($handler->requestArray()[0])) { foreach ($virtual_routes as $k) { - if ($handler->request_array()[0] === getSetting('route_' . $k)) { + if ($handler->requestArray()[0] === getSetting('route_' . $k)) { $handler->mapRoute($k); } } @@ -352,7 +371,7 @@ $hook_before = function (Handler $handler) { if (getSetting('enable_signups')) { $allowed_requests[] = 'signup'; } - if (! in_array($handler->request_array()[0], $allowed_requests, true)) { + if (! in_array($handler->requestArray()[0], $allowed_requests, true)) { headersNoCache(); redirect('login', 302); } @@ -400,43 +419,13 @@ $hook_before = function (Handler $handler) { } $handler::setCond('moderate_uploads', $moderate_uploads); $categories = []; - $tags_top = []; + $tagsTop = []; if ($handler::cond('explore_enabled') || $base === 'dashboard') { - try { - $categories_db = DB::queryFetchAll( - 'SELECT * FROM ' - . DB::getTable('categories') - . ' ORDER BY category_name ASC;' - ); - foreach ($categories_db as $k => $v) { - $key = $v['category_id']; - $categories[$key] = $v; - $categories[$key]['category_url'] = get_base_url('category/' . $v['category_url_key']); - $categories[$key] = DB::formatRow($categories[$key]); - } - } catch (Throwable) { - } - - try { - $tagsTable = DB::getTable('tags'); - $tags_db = DB::queryFetchAll( - << $v) { - $tag = array_merge($v, Tag::row($v['name'])); - $tags_top[] = $tag; - } - } catch (Throwable) { - } + $categories = Categories::get(); + $tagsTop = Tags::top(); } $handler::setVar('categories', $categories); - $handler::setVar('tags_top', $tags_top); + $handler::setVar('tags_top', $tagsTop); $explore_discovery = [ 'recent' => [ 'label' => _s('Recent'), @@ -497,49 +486,66 @@ $hook_before = function (Handler $handler) { $handler::setVar('explore_discovery', $explore_discovery); $handler::setVar('explore_content', $explore_content); $versionInstalled = cheveretoVersionInstalled(); - $pages_visible = []; + $pages_link_visible = []; if (version_compare($versionInstalled, '3.6.7', '>=')) { - $pages_visible_db = Page::getAll( - args: [ - 'is_active' => '1', - 'is_link_visible' => '1', - ], - sort: [ - 'field' => 'sort_display', - 'order' => 'ASC', - ] - ); - $pos_page_tos = array_search('tos', array_column($pages_visible_db, 'internal')); - $pos_page_privacy = array_search('privacy', array_column($pages_visible_db, 'internal')); - $page_tos = $pos_page_tos === false - ? null - : $pages_visible_db[$pos_page_tos]; - $page_privacy = $pos_page_privacy === false - ? null - : $pages_visible_db[$pos_page_privacy]; - $handler::setVar('page_tos', $page_tos); - $handler::setVar('page_privacy', $page_privacy); + $cachedPagesVisibleRows = Cache::instance()->get('pages_visible'); + if ($cachedPagesVisibleRows === false) { + $pagesVisibleRows = Page::getAll( + args: [ + 'is_active' => '1', + 'is_link_visible' => '1', + ], + sort: [ + 'field' => 'sort_display', + 'order' => 'ASC', + ] + ); + $posPageTos = array_search('tos', array_column($pagesVisibleRows, 'internal')); + $posPagePrivacy = array_search('privacy', array_column($pagesVisibleRows, 'internal')); + Cache::instance()->set( + 'pages_visible', + [ + 'rows' => $pagesVisibleRows, + 'pos_page_tos' => $posPageTos, + 'pos_page_privacy' => $posPagePrivacy, + ], + 3600 + ); + } else { + $pagesVisibleRows = $cachedPagesVisibleRows['rows'] ?? []; + $posPageTos = $cachedPagesVisibleRows['pos_page_tos'] ?? false; + $posPagePrivacy = $cachedPagesVisibleRows['pos_page_privacy'] ?? false; + } + $pageTos = $posPageTos === false ? null : $pagesVisibleRows[$posPageTos]; + $pagePrivacy = $posPagePrivacy === false ? null : $pagesVisibleRows[$posPagePrivacy]; + $handler::setVar('page_tos', $pageTos); + $handler::setVar('page_privacy', $pagePrivacy); } if ((bool) env()['CHEVERETO_ENABLE_PAGES']) { - foreach ($pages_visible_db ?? [] as $k => $v) { + foreach ($pagesVisibleRows ?? [] as $k => $v) { if (! ($v['is_active'] ?? false) && ! ($v['is_link_visible'] ?? false)) { continue; } - $pages_visible[$v['id']] = $v; + $pages_link_visible[$v['id']] = $v; } } - $api_page = [ - 'type' => 'link', - 'link_url' => get_base_url('api-v1'), - 'icon' => 'fas fa-project-diagram', - 'title' => 'API', - 'is_active' => 1, - 'is_link_visible' => 1, - 'attr_target' => '_self', - 'sort_display' => -2, - ]; - Page::fill($api_page); - $pages_visible[] = $api_page; + $apiEnabled = ((bool) env()['CHEVERETO_ENABLE_API_USER'] || (bool) env()['CHEVERETO_ENABLE_API_GUEST']) + && (getSetting('enable_api_user') || getSetting('enable_api_guest')); + $handler::setCond('api_enabled', $apiEnabled); + if ($apiEnabled) { + $api_page = [ + 'type' => 'link', + 'link_url' => get_base_url('api-v1'), + 'icon' => 'fas fa-project-diagram', + 'title' => 'API', + 'is_active' => 1, + 'is_link_visible' => 1, + 'attr_target' => '_self', + 'sort_display' => -2, + ]; + Page::fill($api_page); + $pages_link_visible[] = $api_page; + } if (getSetting('enable_plugin_route')) { $plugin_page = [ 'type' => 'link', @@ -552,12 +558,12 @@ $hook_before = function (Handler $handler) { 'sort_display' => -1, ]; Page::fill($plugin_page); - $pages_visible[] = $plugin_page; + $pages_link_visible[] = $plugin_page; } - uasort($pages_visible, function ($a, $b) { + uasort($pages_link_visible, function ($a, $b) { return $a['sort_display'] - $b['sort_display']; }); - $handler::setVar('pages_link_visible', $pages_visible); + $handler::setVar('pages_link_visible', $pages_link_visible); $upload_enabled = Login::isAdmin() ?: getSetting('enable_uploads'); $upload_allowed = $upload_enabled; if (! Login::getUser()) { @@ -576,7 +582,7 @@ $hook_before = function (Handler $handler) { Settings::setValue('upload_max_filesize_mb', getSetting('upload_max_filesize_mb_guest')); } if ($upload_allowed - && in_array($handler->request_array()[0], ['login', 'signup', 'account'], true) + && in_array($handler->requestArray()[0], ['login', 'signup', 'account'], true) ) { $upload_allowed = false; } @@ -585,7 +591,7 @@ $hook_before = function (Handler $handler) { if ($handler::cond('maintenance') || $handler::cond('show_consent_screen')) { $handler::setCond('private_gate', true); $allowed_requests = ['login', 'account', 'connect', 'captcha-verify', 'oembed']; - if (! in_array($handler->request_array()[0], $allowed_requests, true)) { + if (! in_array($handler->requestArray()[0], $allowed_requests, true)) { $handler->preventRoute($handler::cond('show_consent_screen') ? 'consent-screen' : 'maintenance'); } } @@ -602,7 +608,7 @@ $hook_before = function (Handler $handler) { 'webmanifest', 'tag-autocomplete', ]; - if (! in_array($handler->request_array()[0], $excludeLastUrl, true)) { + if (! in_array($handler->requestArray()[0], $excludeLastUrl, true)) { sessionVar()->put('last_url', get_current_url()); } $detect = new Mobile_Detect(); @@ -627,12 +633,12 @@ $hook_before = function (Handler $handler) { }; $hook_after = function (Handler $handler) { if (array_key_exists('deleted', get()) - && in_array($handler->template(), ['user', 'album'], true) + && in_array($handler->template(), ['user', 'album'], true) ) { set_status_header(303); } if ($handler->template() === '404') { - if (sessionVar()->hasKey('last_url')) { + if (sessionVar()->has('last_url')) { sessionVar()->remove('last_url'); } $handler::setVar('doctitle', _s("That page doesn't exist") . ' (404) - ' . getSetting('website_name')); diff --git a/app/legacy/routes/account.php b/app/legacy/routes/account.php index f0a2e99..c47cf30 100644 --- a/app/legacy/routes/account.php +++ b/app/legacy/routes/account.php @@ -89,7 +89,7 @@ return function (Handler $handler) { if (! TwoFactor::hasFor($logged_user['id'])) { redirect('settings/security', 302); } - if (! sessionVar()->hasKey('challenge_two_factor')) { + if (! sessionVar()->has('challenge_two_factor')) { redirect($logged_user['url'] ?? '', 302); } diff --git a/app/legacy/routes/album.php b/app/legacy/routes/album.php index f6f99fd..2388497 100644 --- a/app/legacy/routes/album.php +++ b/app/legacy/routes/album.php @@ -368,7 +368,7 @@ return function (Handler $handler) { $handler::setVar('meta_description', $meta_description); if ($handler::cond('content_manager') || $is_owner) { $handler::setVar('user_items_editor', [ - 'user_albums' => User::getAlbums((int) $album['user']['id']), + 'user_albums' => User::getAlbums($album['user']), 'type' => $type, ]); } diff --git a/app/legacy/routes/api-v1.php b/app/legacy/routes/api-v1.php index 9a5ae70..63146c1 100644 --- a/app/legacy/routes/api-v1.php +++ b/app/legacy/routes/api-v1.php @@ -12,7 +12,7 @@ use Chevereto\Legacy\G\Handler; return function (Handler $handler) { - if ($handler->isRequestLevel(2)) { + if (! $handler::cond('api_enabled') || $handler->isRequestLevel(2)) { $handler->issueError(404); return; diff --git a/app/legacy/routes/api.php b/app/legacy/routes/api.php index 3261991..3b74b73 100644 --- a/app/legacy/routes/api.php +++ b/app/legacy/routes/api.php @@ -44,6 +44,12 @@ use function Chevereto\Vars\request; use function Chevereto\Vars\server; return function (Handler $handler) { + if (! $handler::cond('api_enabled')) { + $handler->issueError(404); + + return; + } + try { $user = []; $REQUEST = request(); diff --git a/app/legacy/routes/dashboard.php b/app/legacy/routes/dashboard.php index 336ea81..457e238 100644 --- a/app/legacy/routes/dashboard.php +++ b/app/legacy/routes/dashboard.php @@ -12,7 +12,10 @@ use Chevereto\Config\Config; use Chevereto\Legacy\Classes\Akismet; use Chevereto\Legacy\Classes\AssetStorage; +use Chevereto\Legacy\Classes\Cache; use Chevereto\Legacy\Classes\DB; +use Chevereto\Legacy\Classes\ExifTool; +use Chevereto\Legacy\Classes\ExifTran; use Chevereto\Legacy\Classes\Image; use Chevereto\Legacy\Classes\L10n; use Chevereto\Legacy\Classes\Listing; @@ -45,7 +48,6 @@ use function Chevereto\Legacy\G\fetch_url; use function Chevereto\Legacy\G\format_bytes; use function Chevereto\Legacy\G\get_app_version; use function Chevereto\Legacy\G\get_base_url; -use function Chevereto\Legacy\G\get_bytes; use function Chevereto\Legacy\G\get_client_ip; use function Chevereto\Legacy\G\get_ffmpeg_error; use function Chevereto\Legacy\G\get_ini_bytes; @@ -133,6 +135,18 @@ return function (Handler $handler) { return; } + $request = implode('/', $handler->request()); + $redirects = [ + 'settings/external-storage' => 'dashboard/settings/upload-storage', + 'settings/asset-storage' => 'dashboard/settings/site-storage', + 'settings/guest-api' => 'dashboard/settings/api', + ]; + $redirectTo = $redirects[$request] ?? null; + if ($redirectTo) { + redirect($redirectTo); + + return; + } $route_prefix = 'dashboard'; $routes = [ 'stats' => _s('Home'), @@ -178,6 +192,7 @@ return function (Handler $handler) { 'tags' => 'fas fa-tags', ]; $settings_sections = [ + 'api' => 'API', 'website' => _s('Website'), 'content' => _s('Content'), 'listings' => _s('Listings'), @@ -190,14 +205,13 @@ return function (Handler $handler) { 'email' => _s('Email'), 'tools' => _s('Tools'), 'logo' => _s('Logo'), - 'asset-storage' => _s('Asset storage'), - 'external-storage' => _s('External storage'), + 'site-storage' => _s('Site storage'), + 'upload-storage' => _s('Upload storage'), 'upload-plugin' => _s('Upload plugin'), 'homepage' => _s('Homepage'), 'pages' => _s('Pages'), 'consent-screen' => _s('Consent screen'), 'users' => _n('User', 'Users', 20), - 'guest-api' => _s('Guests %s', 'API'), 'login-providers' => _s('Login providers'), 'routing' => _s('Routing'), 'external-services' => _s('External services'), @@ -218,11 +232,11 @@ return function (Handler $handler) { 'cookie-compliance' => 'fas fa-cookie-bite', 'email' => 'fas fa-at', 'external-services' => 'fas fa-concierge-bell', - 'asset-storage' => 'fas fa-warehouse', - 'external-storage' => 'fas fa-hdd', + 'site-storage' => 'fas fa-warehouse', + 'upload-storage' => 'fas fa-hdd', 'file-uploads' => 'fas fa-cloud-upload-alt', 'flood-protection' => 'fas fa-faucet', - 'guest-api' => 'fas fa-project-diagram', + 'api' => 'fas fa-project-diagram', 'homepage' => 'fas fa-home', 'ip-bans' => 'fas fa-ban', 'languages' => 'fas fa-language', @@ -246,7 +260,6 @@ return function (Handler $handler) { 'cookie-compliance' => ['pro', 'CHEVERETO_ENABLE_COOKIE_COMPLIANCE'], 'external-services' => ['pro', 'CHEVERETO_ENABLE_EXTERNAL_SERVICES'], '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' => ['lite', 'CHEVERETO_ENABLE_LOGIN_PROVIDERS'], @@ -425,68 +438,10 @@ return function (Handler $handler) { $cronRemark .= ' — ' . _s('not running') . ''; } if ((env()['CHEVERETO_SERVICING'] ?? null) === 'docker') { - $cronRemark .= '
    docker exec -it --user www-data ' . (gethostname() ?: 'chv-container') . ' app/bin/legacy -C cron
    '; + $cronRemark .= '
    docker exec -it --user www-data ' . (gethostname() ?: 'chv-container') . ' app/bin/cli -C cron
    '; $errorLogRemark .= '
    docker logs ' . (gethostname() ?: 'chv-container') . ' -f 1>/dev/null
    '; } } - $ffmpegContent = ' '; - - try { - $missing = [ - 'proc_open' => ! function_exists('proc_open'), - 'proc_close' => ! function_exists('proc_close'), - ]; - $missing = array_filter($missing); - if ($missing) { - throw new Exception( - _s( - 'PHP function [%s] not available in this PHP installation', - implode(', ', array_keys($missing)) - ) - ); - } - $ffmpegErrors = []; - - try { - $ffmpeg = FFMpeg::create( - [ - 'ffmpeg.binaries' => env()['CHEVERETO_BINARY_FFMPEG'], - 'ffprobe.binaries' => env()['CHEVERETO_BINARY_FFPROBE'], - ] - ); - } catch (Throwable $e) { - $ffmpegErrors[] = get_ffmpeg_error($e); - } - - try { - $ffprobe = FFProbe::create( - [ - 'ffprobe.binaries' => env()['CHEVERETO_BINARY_FFPROBE'], - ] - ); - $ffprobe->getFFProbeDriver()->getName(); - } catch (Throwable $e) { - $ffmpegErrors[] = get_ffmpeg_error($e); - } - if ($ffmpegErrors !== []) { - throw new Exception(implode(', ', $ffmpegErrors)); - } - $ffmpegContent .= 'FFmpeg'; - if (isset($ffmpeg) && env()['CHEVERETO_CONTEXT'] !== 'saas') { - $ffmpegContent .= ' bin: ' - . env()['CHEVERETO_BINARY_FFMPEG'] - . ' version ' - . $ffmpeg->getFFMpegDriver()->getVersion() - . '
    ' - . ' FFprobe bin:' - . env()['CHEVERETO_BINARY_FFPROBE']; - } - } catch (Throwable $e) { - $ffmpegContent = ' Error: ' - . get_ffmpeg_error($e) - . ''; - } - $chv_versioning = explode('.', APP_VERSION); $chv_version_major = $chv_versioning[0] . '.X'; $chv_version_minor = $chv_versioning[0] . '.' . $chv_versioning[1]; @@ -501,17 +456,10 @@ return function (Handler $handler) { . '
    ' . $version_check . $linksButtons . '
    ', ], - 'max_upload_size' => [ + 'upload_max_filesize' => [ 'label' => _s('Max. upload file size'), 'content' => ' ' . format_bytes(get_ini_bytes(ini_get('upload_max_filesize'))), ], - 'graphics' => [ - 'label' => _s('Graphics library'), - ], - 'video' => [ - 'label' => _s('Video processing'), - 'content' => $ffmpegContent, - ], 'rebuild_stats' => [ 'label' => _s('Stats'), 'content' => '' . _s('Rebuild stats') . '', @@ -572,11 +520,113 @@ return function (Handler $handler) { '%label%' => $link['label'], ]); } - if (env()['CHEVERETO_CONTEXT'] !== 'saas') { + $ffmpegContent = ' '; + + try { + $missing = [ + 'proc_open' => ! function_exists('proc_open'), + 'proc_close' => ! function_exists('proc_close'), + ]; + $missing = array_filter($missing); + if ($missing) { + throw new Exception( + _s( + 'PHP function [%s] not available in this PHP installation', + implode(', ', array_keys($missing)) + ) + ); + } + $ffmpegErrors = []; + + try { + $ffmpeg = FFMpeg::create( + [ + 'ffmpeg.binaries' => env()['CHEVERETO_BINARY_FFMPEG'], + // 'ffprobe.binaries' => env()['CHEVERETO_BINARY_FFPROBE'], + ] + ); + } catch (Throwable $e) { + $ffmpegErrors[] = get_ffmpeg_error($e); + } + + try { + $ffprobe = FFProbe::create( + [ + 'ffprobe.binaries' => env()['CHEVERETO_BINARY_FFPROBE'], + ] + ); + $ffprobe->getFFProbeDriver()->getName(); + } catch (Throwable $e) { + $ffmpegErrors[] = get_ffmpeg_error($e); + } + if ($ffmpegErrors !== []) { + throw new Exception(implode('
    ', $ffmpegErrors)); + } + $ffmpegContent .= 'FFmpeg'; + if (isset($ffmpeg) && env()['CHEVERETO_CONTEXT'] !== 'saas') { + $ffmpegContent .= ' bin: ' + . env()['CHEVERETO_BINARY_FFMPEG'] + . ' version ' + . $ffmpeg->getFFMpegDriver()->getVersion() + . '
    ' + . ' FFprobe bin: ' + . env()['CHEVERETO_BINARY_FFPROBE']; + } + } catch (Throwable $e) { + $ffmpegContent = ' Error: ' + . get_ffmpeg_error($e) + . ''; + } + $graphicsLibraryContent = ' '; + if (ImageManagerStatic::getManager()->config['driver'] === 'imagick') { + $graphicVersion = Imagick::getVersion()['versionString']; + $graphicsLibraryContent .= $graphicVersion; + } else { + $graphicVersion = 'GD Version ' . gd_info()['GD Version']; + $graphicsLibraryContent .= $graphicVersion + . ' JPEG:' . gd_info()['JPEG Support'] + . ' GIF:' . gd_info()['GIF Read Support'] . '/' . gd_info()['GIF Create Support'] + . ' PNG:' . gd_info()['PNG Support'] + . ' WEBP:' . (gd_info()['WebP Support'] ?? 0) + . ' WBMP:' . gd_info()['WBMP Support'] + . ' XBM:' . gd_info()['XBM Support']; + } + $exifToolBinary = env()['CHEVERETO_BINARY_EXIFTOOL'] ?? ''; + $exifToolContent = ' ExifTool'; + if ($exifToolBinary !== '') { + $exifToolContent .= ' bin: ' . $exifToolBinary; + + try { + $exifTool = new ExifTool(env()['CHEVERETO_BINARY_EXIFTOOL']); + $exifToolContent .= ' version ' . $exifTool->version(); + } catch (RuntimeException $e) { + $exifToolContent .= ' ' . $e->getMessage() . ''; + } + } else { + $exifToolContent .= ' ' . _s('Not available') . ''; + } + $exifTranBinary = env()['CHEVERETO_BINARY_EXIFTRAN'] ?? ''; + $exifTranContent = ' ExifTran'; + if ($exifTranBinary !== '') { + $exifTranContent .= ' bin: ' . $exifTranBinary; + + try { + new ExifTran($exifTranBinary); + } catch (RuntimeException $e) { + $exifTranContent .= ' ' . $e->getMessage() . ''; + } + } else { + $exifTranContent .= ' ' . _s('Not available') . ''; + } $mysqlVersion = $db->getAttr(PDO::ATTR_SERVER_VERSION); $db->closeCursor(); $mysqlServerInfo = $db->getAttr(PDO::ATTR_SERVER_INFO); + $redisVersion = _s('Disabled'); + if (Cache::isEnabled()) { + $redisInfo = Cache::instance()->redis()->info(); + $redisVersion = 'Redis v' . $redisInfo['redis_version']; + } $phpIniLoaded = php_ini_loaded_file(); $phpIniFiles = php_ini_scanned_files() ?: 'N/A'; $phpIniFiles = explode(',', $phpIniFiles); @@ -594,7 +644,7 @@ return function (Handler $handler) { ], 'cli' => [ 'label' => 'CLI', - 'content' => ' ' . PATH_PUBLIC . 'app/bin/legacy', + 'content' => ' ' . PATH_PUBLIC . 'app/bin/cli', ], 'cron' => [ 'label' => _s('Cron last ran'), @@ -626,6 +676,11 @@ return function (Handler $handler) { . '
    ' . $mysqlServerInfo, ], + 'cache' => [ + 'label' => 'Cache', + 'content' => ' ' + . $redisVersion, + ], 'php_version' => [ 'label' => _s('PHP version'), 'content' => ' ' @@ -648,29 +703,26 @@ return function (Handler $handler) { 'label' => _s('Memory limit'), 'content' => ' ' . format_bytes(get_ini_bytes(ini_get('memory_limit'))), ], + 'graphics' => [ + 'label' => _s('Graphics library'), + 'content' => $graphicsLibraryContent, + ], + 'exiftool' => [ + 'label' => 'ExifTool', + 'content' => $exifToolContent, + ], + 'exiftran' => [ + 'label' => 'ExifTran', + 'content' => $exifTranContent, + ], + 'video' => [ + 'label' => _s('Video processing'), + 'content' => $ffmpegContent, + ], ]; - $pos = array_search('max_upload_size', array_keys($system_values), true); + $pos = array_search('upload_max_filesize', array_keys($system_values), true); array_splice($system_values, $pos, 0, $system_values_more); } - - $graphicsLibraryContent = ' '; - if (ImageManagerStatic::getManager()->config['driver'] === 'imagick') { - $graphicVersion = env()['CHEVERETO_CONTEXT'] === 'saas' - ? 'ImageMagick' - : Imagick::getVersion()['versionString']; - $system_values['graphics']['content'] = $graphicsLibraryContent . $graphicVersion; - } else { - $graphicVersion = env()['CHEVERETO_CONTEXT'] === 'saas' - ? 'GD ' - : ('GD Version ' . gd_info()['GD Version']); - $system_values['graphics']['content'] = $graphicsLibraryContent . $graphicVersion - . ' JPEG:' . gd_info()['JPEG Support'] - . ' GIF:' . gd_info()['GIF Read Support'] . '/' . gd_info()['GIF Create Support'] - . ' PNG:' . gd_info()['PNG Support'] - . ' WEBP:' . (gd_info()['WebP Support'] ?? 0) - . ' WBMP:' . gd_info()['WBMP Support'] - . ' XBM:' . gd_info()['XBM Support']; - } $handler::setVar('system_values', $system_values); $handler::setVar('totals', $totals); $handler::setVar('totals_display', $totals_display); @@ -717,7 +769,7 @@ return function (Handler $handler) { ]; if ($current) { $handler::setVar('settings', $settings_sections[$k]); - if (in_array($k, ['categories', 'ip-bans', 'external-storage'], true)) { + if (in_array($k, ['categories', 'ip-bans', 'upload-storage'], true)) { $handler::setCond('show_submit', false); } } @@ -727,6 +779,9 @@ return function (Handler $handler) { return; } + uasort($settings_sections, function ($a, $b) { + return strcoll($a['label'], $b['label']); + }); $handler::setVar('settings_menu', $settings_sections); if (isset($handler->request()[1])) { $requestSetting = $handler->request()[1]; @@ -791,7 +846,7 @@ return function (Handler $handler) { break; - case 'external-storage': + case 'upload-storage': $disk_used_all = Stat::getTotals()['disk_used']; $disk_used_external = DB::queryFetchSingle('SELECT SUM(storage_space_used) space_used FROM ' . DB::getTable('storages') . ';')['space_used']; $storage_usage = [ @@ -857,7 +912,7 @@ return function (Handler $handler) { $page = Page::getSingle($handler->request()[3], 'id'); if ($page) { // Workaround for default pages - if (starts_with('default/', $page['file_path'])) { + if (starts_with('default/', $page['file_path'] ?? '')) { $page['file_path'] = null; } } else { @@ -1060,9 +1115,11 @@ return function (Handler $handler) { } } if (($handler->request()[1] ?? null) === 'pages') { - $page_file_path_clean = trim(sanitize_relative_path($POST['page_file_path']), '/'); - $POST['page_file_path'] = str_replace('default/', '', $page_file_path_clean); - $POST['page_file_path_absolute'] = Page::getPath($POST['page_file_path']); + if (Config::enabled()->phpPages()) { + $page_file_path_clean = trim(sanitize_relative_path($POST['page_file_path']), '/'); + $POST['page_file_path'] = str_replace('default/', '', $page_file_path_clean); + $POST['page_file_path_absolute'] = Page::getPath($POST['page_file_path']); + } if (! filter_var($POST['page_sort_display'], FILTER_VALIDATE_INT)) { $POST['page_sort_display'] = null; } @@ -1076,9 +1133,13 @@ return function (Handler $handler) { $handler::updateVar('safe_post', [ 'page_is_active' => $POST['page_is_active'], 'page_is_link_visible' => $POST['page_is_link_visible'], - 'page_file_path_absolute' => $POST['page_file_path_absolute'], + // 'page_file_path_absolute' => $POST['page_file_path_absolute'], ]); } + $mailApis = ['smtp']; + if (env()['CHEVERETO_SERVICING'] !== 'docker') { + $mailApis[] = 'mail'; + } $validations = [ 'website_name' => [ 'validate' => isset($POST['website_name']), @@ -1166,7 +1227,7 @@ return function (Handler $handler) { 'error_msg' => _s('Invalid user id'), ], 'email_mode' => [ - 'validate' => isset($POST['email_mode']) && in_array($POST['email_mode'], ['smtp', 'mail'], true), + 'validate' => isset($POST['email_mode']) && in_array($POST['email_mode'], $mailApis, true), 'error_msg' => _s('Invalid email mode'), ], 'email_smtp_server_port' => [ @@ -1249,10 +1310,6 @@ return function (Handler $handler) { 'validate' => isset($POST['page_type'], $POST['page_url_key']) && $POST['page_type'] === 'internal' ? preg_match('/^[\w\-\_\/]+$/', $POST['page_url_key']) : true, 'error_msg' => _s('Invalid URL key'), ], - 'page_file_path' => [ - 'validate' => isset($POST['page_type'], $POST['page_file_path']) && $POST['page_type'] === 'internal' ? preg_match('/^[\w\-\_\/]+\.' . (Config::enabled()->phpPages() ? 'html|php' : 'html') . '$/', $POST['page_file_path']) : true, - 'error_msg' => _s('Invalid file path'), - ], 'page_link_url' => [ 'validate' => isset($POST['page_type'], $POST['page_link_url']) && $POST['page_type'] === 'link' ? is_url_web($POST['page_link_url']) : true, 'error_msg' => _s('Invalid link URL'), @@ -1308,6 +1365,12 @@ return function (Handler $handler) { 'error_msg' => _s('Invalid key'), ], ]; + if (Config::enabled()->phpPages()) { + $validations['page_file_path'] = [ + 'validate' => isset($POST['page_type'], $POST['page_file_path']) && $POST['page_type'] === 'internal' ? preg_match('/^[\w\-\_\/]+\.html|php$/', $POST['page_file_path']) : true, + 'error_msg' => _s('Invalid file path'), + ]; + } $customRoutes = []; foreach (['image', 'album', 'user'] as $test) { $tryValue = $POST['route_' . $test] ?? null; @@ -1389,8 +1452,6 @@ return function (Handler $handler) { if (isset($POST[$k])) { if (! is_numeric($POST[$k]) || $POST[$k] == 0) { $error_max_filesize = _s('Invalid value'); - } elseif (get_bytes($POST[$k] . 'MB') > Settings::get('true_upload_max_filesize')) { - $error_max_filesize = _s('Max. allowed %s', format_bytes(Settings::get('true_upload_max_filesize'))); } $validations[$k] = [ 'validate' => ! isset($error_max_filesize), @@ -1736,15 +1797,17 @@ return function (Handler $handler) { : null; try { - Page::writePage([ - 'file_path' => $POST['page_file_path'], - 'code' => $page_write_code, - ]); - if ($handler->request()[2] === 'edit' - && isset($page['file_path']) - && ! hash_equals((string) $page['file_path'], (string) $POST['page_file_path']) - ) { - unlinkIfExists(Page::getPath($page['file_path'])); + if (Config::enabled()->phpPages()) { + Page::writePage([ + 'file_path' => $POST['page_file_path'], + 'code' => $page_write_code, + ]); + if ($handler->request()[2] === 'edit' + && isset($page['file_path']) + && ! hash_equals((string) $page['file_path'], (string) $POST['page_file_path']) + ) { + unlinkIfExists(Page::getPath($page['file_path'])); + } } if (isset($page['id'])) { Page::update((int) $page['id'], [ @@ -1841,7 +1904,7 @@ return function (Handler $handler) { } $oldSettings = Settings::get(); if ($update_settings !== [] && Settings::update($update_settings)) { - new Settings(); + new Settings(reCache: true); $diffSettings = array_diff_key($oldSettings, Settings::get()); foreach ($diffSettings as $k => $v) { Settings::setValue($k, $v); diff --git a/app/legacy/routes/image.php b/app/legacy/routes/image.php index 3a82c33..a30b184 100644 --- a/app/legacy/routes/image.php +++ b/app/legacy/routes/image.php @@ -150,7 +150,7 @@ return function (Handler $handler) { } if (isset($image['user']['id']) && ($handler::cond('content_manager') || $is_owner)) { - $image['user']['albums'] = User::getAlbums((int) $image['user']['id']); + $image['user']['albums'] = User::getAlbums($image['user']); } $is_album_cover = false; if (isset($image['album']['id'])) { diff --git a/app/legacy/routes/json.php b/app/legacy/routes/json.php index 02b160b..86e94f9 100644 --- a/app/legacy/routes/json.php +++ b/app/legacy/routes/json.php @@ -14,6 +14,7 @@ use Chevereto\Config\Config; use Chevereto\Legacy\Classes\Akismet; use Chevereto\Legacy\Classes\Album; use Chevereto\Legacy\Classes\ApiKey; +use Chevereto\Legacy\Classes\Categories; use Chevereto\Legacy\Classes\Category; use Chevereto\Legacy\Classes\DB; use Chevereto\Legacy\Classes\Follow; @@ -31,10 +32,12 @@ use Chevereto\Legacy\Classes\Stat; use Chevereto\Legacy\Classes\Storage; use Chevereto\Legacy\Classes\Tag; use Chevereto\Legacy\Classes\TwoFactor; +use Chevereto\Legacy\Classes\Upload; use Chevereto\Legacy\Classes\User; use Chevereto\Legacy\G\Handler; use Hybridauth\Hybridauth; use function Chevere\Message\message; +use function Chevere\Standard\randomString; use function Chevere\ThrowableHandler\throwableHandler; use function Chevere\Writer\writers; use function Chevere\xrDebug\PHP\throwableHandler as XrThrowableHandler; @@ -47,6 +50,8 @@ use function Chevereto\Legacy\G\datetime; use function Chevereto\Legacy\G\datetimegmt; use function Chevereto\Legacy\G\fetch_url; use function Chevereto\Legacy\G\get_base_url; +use function Chevereto\Legacy\G\get_bytes; +use function Chevereto\Legacy\G\get_client_ip; use function Chevereto\Legacy\G\get_current_url; use function Chevereto\Legacy\G\get_public_url; use function Chevereto\Legacy\G\json_document_output; @@ -54,6 +59,7 @@ use function Chevereto\Legacy\G\nullify_string; use function Chevereto\Legacy\G\require_theme_file; use function Chevereto\Legacy\G\starts_with; use function Chevereto\Legacy\getSetting; +use function Chevereto\Legacy\getVariable; use function Chevereto\Legacy\isDebug; use function Chevereto\Legacy\isShowEmbedContent; use function Chevereto\Legacy\send_mail; @@ -62,14 +68,17 @@ use function Chevereto\Vars\env; use function Chevereto\Vars\files; use function Chevereto\Vars\post; use function Chevereto\Vars\request; +use function Chevereto\Vars\requestHeaders; use function Chevereto\Vars\session; return function (Handler $handler) { try { - $REQUEST = request(); - $FILES = files(); $POST = post(); - if (! $handler::checkAuthToken(request()['auth_token'] ?? '')) { + $REQUEST = request(); + $HEADERS = requestHeaders(); + $REQUEST['auth_token'] ??= $HEADERS['X-Auth-Token'] ?? ''; + $REQUEST['action'] ??= $HEADERS['X-Action'] ?? ''; + if (! $handler::checkAuthToken($REQUEST['auth_token'] ?? '')) { throw new Exception(_s('Request denied'), 401); } $logged_user = Login::getUser(); @@ -88,32 +97,188 @@ return function (Handler $handler) { } $import = new Import(); } + if (in_array($doing, ['chunked-upload', 'upload-chunk', 'upload'], true)) { + if (! $handler::cond('upload_allowed')) { + throw new Exception(_s('Request denied'), 403); + } + $REQUEST['type'] ??= $HEADERS['X-Type'] ?? ''; + if ($doing !== 'upload-chunk') { + $source = $REQUEST['type'] === 'file' + ? files()['source'] + : $REQUEST['source']; + } + /** @var ?int $ownerId */ + $ownerId = $logged_user['id'] ?? null; + $REQUEST['owner'] ??= $HEADERS['X-Owner'] ?? null; + if ((Login::isAdmin() || Login::isManager()) && ! empty($REQUEST['owner'])) { + $ownerId = decodeID($REQUEST['owner']); + } + } + $chunkUploadSize = getSetting('chunk_upload_size'); switch ($doing) { + case 'chunked-upload': + $maxSize = get_bytes(getSetting('upload_max_filesize_mb') . ' MB'); + $checksum = $REQUEST['checksum'] ?? ''; + $size = (int) ($REQUEST['size'] ?? 0); + if (! preg_match('/^[a-f0-9]{16,}$/', $checksum)) { + throw new Exception('Invalid file checksum', 100); + } + if ($size === 0) { + throw new Exception('Invalid file size', 100); + } + if ($source === '') { + throw new Exception('Invalid file name', 100); + } + $extension = strtolower(pathinfo($source, PATHINFO_EXTENSION)); + if ($extension === '') { + throw new Exception('Missing file extension', 100); + } + if (! in_array($extension, Image::getEnabledImageExtensions(), true)) { + throw new Exception('Unsupported file extension', 100); + } + if ($size > $maxSize) { + throw new Exception('File size exceeds maximum', 101); + } + $do_dupe_check = ! getSetting('enable_duplicate_uploads') && ! Login::isAdmin(); + if ($do_dupe_check && (Image::isDuplicatedChunkUpload($checksum) || Image::isDuplicatedUpload($checksum))) { + throw new Exception(_s('Duplicated upload'), 101); + } + $token = randomString(64); + $uploadId = DB::insert('uploads', [ + 'user_id' => $ownerId, + 'uploader_ip' => get_client_ip(), + 'token' => $token, + 'checksum' => $checksum, + 'params' => json_encode([ + 'source' => $REQUEST['source'], + ]), + 'chunks' => ceil($size / $chunkUploadSize), + ]); + $json_array['status_code'] = 200; + $json_array['success'] = [ + 'message' => 'chunked upload', + 'code' => 200, + 'upload_id' => encodeID($uploadId), + 'token' => $token, + 'hash' => hash_hmac( + 'sha256', + $uploadId . $token, + getVariable('crypt_salt')->string() + ), + ]; + + break; + case 'upload-chunk': + if ($logged_user !== []) { + session_write_close(); + } + $uploadId = decodeID($HEADERS['X-Upload-Id']); + $index = (int) ($HEADERS['X-Index'] ?? 0); + $token = $HEADERS['X-Token'] ?? ''; + $hash = $HEADERS['X-Hash'] ?? ''; + if ($index === 0) { + throw new Exception('Invalid chunk index', 100); + } + if ($token === '') { + throw new Exception('Invalid token', 100); + } + if ($hash === '') { + throw new Exception('Invalid hash', 100); + } + $calcHash = hash_hmac( + 'sha256', + $uploadId . $token, + getVariable('crypt_salt')->string() + ); + if (! hash_equals($calcHash, $hash)) { + throw new Exception('Invalid hash', 100); + } + $uploadWhere = [ + 'id' => $uploadId, + 'token' => $token, + ]; + if ($logged_user !== []) { + $uploadWhere['user_id'] = $logged_user['id']; + } + $uploadRow = DB::get( + table: 'uploads', + where: $uploadWhere, + limit: 1, + ); + if (! $uploadRow) { + throw new Exception('Missing upload id', 100); + } + if ($index > $uploadRow['upload_chunks']) { + throw new Exception('Invalid chunk index', 100); + } + $db = DB::getInstance(); + $db->query( + 'SELECT COUNT(*) c FROM ' + . DB::getTable('uploads_chunks') + . ' WHERE upload_chunk_upload_id=:upload_id AND upload_chunk_index=:chunk_index;' + ); + $db->bind(':upload_id', $uploadId); + $db->bind(':chunk_index', $index); + if ($db->fetchSingle()['c'] > 0) { + throw new Exception('Chunk already uploaded', 100); + } + // $chunkFile = $source['tmp_name']; + // if (! file_exists($chunkFile)) { + // throw new Exception('Missing chunk file', 100); + // } + // $chunkFilesize = filesize($chunkFile); + // if ($chunkFilesize === 0) { + // throw new Exception('Empty chunk file', 100); + // } + // if ($chunkFilesize > $chunkUploadSize) { + // throw new Exception('Chunk file size exceeds maximum', 101); + // } + // Handle chunk upload as a stream (for "source" stream input) + $chunkFile = Upload::getTempNam(suffix: "{$uploadId}_{$index}"); + $inputStream = fopen('php://input', 'rb'); + if ($inputStream === false) { + throw new Exception('Failed to open input stream', 100); + } + $outputStream = fopen($chunkFile, 'wb'); + if ($outputStream === false) { + fclose($inputStream); + + throw new Exception('Failed to open chunk file for writing', 100); + } + stream_copy_to_stream($inputStream, $outputStream); + fclose($inputStream); + fclose($outputStream); + if (! file_exists($chunkFile) || filesize($chunkFile) === 0) { + throw new Exception('Failed to write chunk file', 100); + } + DB::insert('uploads_chunks', [ + 'upload_id' => $uploadId, + 'index' => $index, + 'path' => $chunkFile, + ]); + $json_array['status_code'] = 200; + $json_array['success'] = [ + 'message' => 'chunk uploaded', + 'code' => 200, + ]; + + break; case 'upload': // EX 100 // NOTE: This is considering assets and user uploads as the same "upload" action - - $source = $REQUEST['type'] === 'file' - ? $FILES['source'] - : $REQUEST['source']; $type = $REQUEST['type']; - /** @var ?int $owner_id */ - $owner_id = ! empty($REQUEST['owner']) - ? decodeID($REQUEST['owner']) - : ($logged_user['id'] ?? null); - if (isset($REQUEST['what']) && in_array($REQUEST['what'], ['avatar', 'background'], true) ) { if ($logged_user === []) { throw new Exception(_s('Login needed'), 403); } - if (! $handler::cond('content_manager') && $owner_id !== $logged_user['id']) { + if (! $handler::cond('content_manager') && $ownerId !== $logged_user['id']) { throw new Exception('Invalid content owner request', 115); } $user_picture_upload = User::uploadPicture( - $owner_id === $logged_user['id'] + $ownerId === $logged_user['id'] ? $logged_user - : $owner_id, + : $ownerId, $REQUEST['what'], $source ); @@ -125,15 +290,13 @@ return function (Handler $handler) { break; } - if (! $handler::cond('upload_allowed')) { - throw new Exception(_s('Request denied'), 403); - } if ($handler::cond('forced_private_mode')) { $REQUEST['privacy'] = getSetting('website_content_privacy_mode'); } if (! empty($REQUEST['album_id'])) { $REQUEST['album_id'] = decodeID($REQUEST['album_id']); } + // TODO: Unify this check if (! $handler::cond('content_manager') && getSetting('akismet')) { Akismet::checkImage( title: $REQUEST['title'] ?? null, @@ -144,7 +307,7 @@ return function (Handler $handler) { } $uploadToWebsite = Image::uploadToWebsite($source, $logged_user, $REQUEST); if ($logged_user !== []) { - session_write_close(); // guest session uploads + session_write_close(); } $uploaded_id = intval($uploadToWebsite[0]); $json_array['status_code'] = 200; @@ -198,7 +361,7 @@ return function (Handler $handler) { if (! empty($REQUEST['albumid'])) { $album_id = decodeID($REQUEST['albumid']); } - $owner_id = null; + $ownerId = null; $where = ''; switch ($list_request) { case 'images': @@ -219,11 +382,11 @@ return function (Handler $handler) { ]; } if (! empty($REQUEST['userid'])) { - $owner_id = decodeID($REQUEST['userid']); + $ownerId = decodeID($REQUEST['userid']); $where .= ($where === '' ? 'WHERE' : ' AND') . ' image_user_id=:image_user_id'; $binds[] = [ 'param' => ':image_user_id', - 'value' => $owner_id, + 'value' => $ownerId, ]; } if (isset($album_id)) { @@ -234,12 +397,12 @@ return function (Handler $handler) { ]; $album = Album::getSingle($album_id); if ($album['user']['id'] ?? false) { - $owner_id = $album['user']['id']; + $ownerId = $album['user']['id']; } if ($album['privacy'] === 'password' && ( ! $handler::cond('content_manager') - && $owner_id !== ($logged_user['id'] ?? 0) + && $ownerId !== ($logged_user['id'] ?? 0) && ! Album::checkSessionPassword($album) ) ) { @@ -267,11 +430,11 @@ return function (Handler $handler) { $binds = []; $where = ''; if (! empty($REQUEST['userid'])) { - $owner_id = decodeID($REQUEST['userid']); + $ownerId = decodeID($REQUEST['userid']); $where .= 'WHERE album_user_id=:album_user_id'; $binds[] = [ 'param' => ':album_user_id', - 'value' => $owner_id, + 'value' => $ownerId, ]; } if (isset($REQUEST['from'])) { @@ -404,14 +567,14 @@ return function (Handler $handler) { } } $listing->setWhere($where); - if (isset($owner_id)) { - $listing->setOwner((int) $owner_id); + if (isset($ownerId)) { + $listing->setOwner((int) $ownerId); } $listing->setRequester($logged_user); if (in_array($list_request, ['images', 'albums'], true) && ( $handler::cond('content_manager') - || ($logged_user !== [] && $owner_id === $logged_user['id']) + || ($logged_user !== [] && $ownerId === $logged_user['id']) ) ) { $listing->setTools(true); @@ -432,6 +595,7 @@ return function (Handler $handler) { $listing->bind($bind['param'], $bind['value']); } } + $listing->setOutputAssoc(true); $listing->exec(); $json_array['status_code'] = 200; if ($doing === 'get-album-contents' @@ -456,7 +620,7 @@ return function (Handler $handler) { $editing_request = $REQUEST['editing']; $editing = $editing_request; $type = $REQUEST['edit']; - $owner_id = ! empty($REQUEST['owner']) ? decodeID($REQUEST['owner']) : $logged_user['id']; + $ownerId = ! empty($REQUEST['owner']) ? decodeID($REQUEST['owner']) : $logged_user['id']; if (! in_array($type, ['image', 'album', 'images', 'albums', 'category', 'tag', 'storage', 'ip_ban'], true)) { throw new Exception('Invalid edit request', 100); } @@ -702,6 +866,7 @@ return function (Handler $handler) { 'code' => 200, ]; $json_array['category'] = $category; + Categories::deleteCache(); break; case 'tag': @@ -891,6 +1056,7 @@ return function (Handler $handler) { 'code' => 200, ]; $json_array['category'] = $category; + Categories::deleteCache(); break; case 'add-ip_ban': @@ -965,12 +1131,7 @@ return function (Handler $handler) { throw new Exception(_s('Login needed'), 403); } $editing = $REQUEST['editing']; - $owner_id = $logged_user['id']; - if (! $handler::cond('content_manager') - && $owner_id !== $logged_user['id'] - ) { - throw new Exception('Invalid content owner request', 110); - } + $ownerId = $logged_user['id']; $ids = []; foreach ($editing['ids'] as $id) { $ids[] = decodeID($id); @@ -1039,23 +1200,23 @@ return function (Handler $handler) { if ($logged_user === [] && $album['new'] === false) { throw new Exception('Invalid request', 403); } - $owner_id = ! empty($REQUEST['owner']) + $ownerId = ! empty($REQUEST['owner']) ? decodeID($REQUEST['owner']) : ($logged_user['id'] ?? null); - if (! $handler::cond('content_manager') && $owner_id !== ($logged_user['id'] ?? null)) { - throw new Exception('Invalid content owner request' . var_export($owner_id, true), 112); + if (! $handler::cond('content_manager') && $ownerId !== ($logged_user['id'] ?? null)) { + throw new Exception('Invalid content owner request', 112); } if ($handler::cond('forced_private_mode')) { $album['privacy'] = getSetting('website_content_privacy_mode'); } if (! $handler::cond('content_manager') && getSetting('akismet') && $album['new']) { - Akismet::checkAlbum($album['name'], $album['description'], $owner_id === $logged_user['id'] ? $logged_user_source_db : null); + Akismet::checkAlbum($album['name'], $album['description'], $ownerId === $logged_user['id'] ? $logged_user_source_db : null); } $album_id = $album['new'] ? Album::insert([ 'name' => $album['name'], - 'user_id' => $owner_id, + 'user_id' => $ownerId, 'privacy' => $album['privacy'], 'description' => $album['description'], 'password' => $album['password'] ?? null, @@ -1116,7 +1277,7 @@ return function (Handler $handler) { } $album_move_db = isset($album_db['album_id']) ? Album::getSingle(id: (int) $album_db['album_id'], pretty: false) - : User::getStreamAlbum($owner_id); + : User::getStreamAlbum($ownerId); $json_array['status_code'] = 200; $json_array['success'] = [ 'message' => 'Content added to album', @@ -1155,7 +1316,7 @@ return function (Handler $handler) { ) { throw new Exception('Forbidden action', 403); } - $owner_id = isset($REQUEST['owner']) + $ownerId = isset($REQUEST['owner']) ? decodeID($REQUEST['owner']) : $logged_user['id']; $multiple = ($REQUEST['multiple'] ?? null) == 'true'; @@ -1165,7 +1326,7 @@ return function (Handler $handler) { } if ( in_array($type, ['avatar', 'background', 'user', 'ip_ban', 'api_key', 'two_factor'], true) - && ! $handler::cond('content_manager') && $owner_id !== $logged_user['id'] + && ! $handler::cond('content_manager') && $ownerId !== $logged_user['id'] ) { throw new Exception('Invalid content owner request', 113); } @@ -1182,7 +1343,7 @@ return function (Handler $handler) { throw new Exception('Invalid content manager request', 115); } if (in_array($type, ['avatar', 'background'], true)) { - User::deletePicture($owner_id === $logged_user['id'] ? $logged_user : $owner_id, $type); + User::deletePicture($ownerId === $logged_user['id'] ? $logged_user : $ownerId, $type); $json_array['status_code'] = 200; $json_array['success'] = [ 'message' => 'Profile background deleted', @@ -1193,9 +1354,9 @@ return function (Handler $handler) { } if ($type === 'two_factor') { $userTarget = intval( - $owner_id === $logged_user['id'] + $ownerId === $logged_user['id'] ? $logged_user['id'] - : $owner_id + : $ownerId ); if (! TwoFactor::hasFor($userTarget)) { $status_code = 403; @@ -1215,9 +1376,9 @@ return function (Handler $handler) { } if ($type === 'api_key') { $userTarget = intval( - $owner_id === $logged_user['id'] + $ownerId === $logged_user['id'] ? $logged_user['id'] - : $owner_id + : $ownerId ); $apiKey = ApiKey::getUserKey($userTarget); if ($apiKey !== []) { @@ -1232,7 +1393,9 @@ return function (Handler $handler) { break; } if ($type === 'user') { - $delete_user_id = $owner_id === $logged_user['id'] ? $logged_user : $owner_id; + $delete_user_id = $ownerId === $logged_user['id'] + ? $logged_user + : $ownerId; $delete_user = User::getSingle($delete_user_id, 'id'); if ($delete_user === []) { throw new Exception(_s('%s not found', _n('User', 'Users', 1)), 100); @@ -1266,6 +1429,7 @@ return function (Handler $handler) { ], [ 'category_id' => $deleting['id'], ]); + Categories::deleteCache(); } else { throw new Exception('Error deleting category', 400); } @@ -1738,7 +1902,7 @@ return function (Handler $handler) { break; case 'paletteSet': - if ($logged_user === []) { + if ($logged_user === [] || ! getSetting('theme_palette_user_select')) { throw new Exception('Invalid request', 403); } $palette_id = (int) $REQUEST['palette_id']; diff --git a/app/legacy/routes/page.php b/app/legacy/routes/page.php index d6993e7..ff8182d 100644 --- a/app/legacy/routes/page.php +++ b/app/legacy/routes/page.php @@ -9,9 +9,11 @@ * file that was distributed with this source code. */ +use Chevereto\Legacy\Classes\Cache; use Chevereto\Legacy\Classes\Page; use Chevereto\Legacy\G\Handler; use function Chevereto\Legacy\G\add_ending_slash; +use function Chevereto\Legacy\G\str_replace_last; use function Chevereto\Vars\env; return function (Handler $handler) { @@ -20,26 +22,42 @@ return function (Handler $handler) { return; } - $request_url_key = implode('/', $handler->request()); - $page = Page::getSingle($request_url_key); + $urlKey = implode('/', $handler->request()); + $cacheKey = Page::getCacheKey($urlKey); + $page = Cache::instance()->get($cacheKey); + if ($page === false) { + $page = Page::getSingle($urlKey); + if ($page !== []) { + Cache::instance()->set($cacheKey, $page, 3600); + } + } if (! $page || ! $page['is_active'] || $page['type'] !== 'internal') { $handler->issueError(404); return; } - if (! $page['file_path_absolute']) { - $handler->issueError(404); + if ((bool) env()['CHEVERETO_ENABLE_PHP_PAGES']) { + if (! fileExists($page['file_path_absolute'] ?? null)) { + $handler->issueError(404); - return; + return; + } + $pathinfo = pathinfo($page['file_path_absolute']); + $handler->setPathTheme(add_ending_slash($pathinfo['dirname'])); + } else { + if ($page['code'] === null) { + $file = str_replace_last('.php', '.html', $page['file_path_absolute']); + if (fileExists($file)) { + $page['code'] = file_get_contents($file); + } + if ($page['code'] !== null) { + Page::update($page['id'], [ + 'code' => $page['code'], + ]); + } + } + $handler->setContent($page['code'] ?? ''); } - if (! file_exists($page['file_path_absolute'])) { - $handler->issueError(404); - - return; - } - $pathinfo = pathinfo($page['file_path_absolute']); - $handler->setPathTheme(add_ending_slash($pathinfo['dirname'])); - $handler->setTemplate($pathinfo['filename']); $page_metas = [ 'pre_doctitle' => $page['title'], 'meta_description' => htmlspecialchars($page['description'] ?? ''), @@ -52,3 +70,12 @@ return function (Handler $handler) { $handler->setVar($k, $v); } }; + +function fileExists(?string $file): bool +{ + if ($file === null || $file === '') { + return false; + } + + return file_exists($file); +} diff --git a/app/legacy/routes/settings.php b/app/legacy/routes/settings.php index 2a1d2af..054886d 100644 --- a/app/legacy/routes/settings.php +++ b/app/legacy/routes/settings.php @@ -396,7 +396,7 @@ return function (Handler $handler) { break; case 'security': - if (! TwoFactor::hasFor($user['id']) && sessionVar()->hasKey('two_factor_secret')) { + if (! TwoFactor::hasFor($user['id']) && sessionVar()->has('two_factor_secret')) { $twoFactor = new TwoFactor(); $twoFactor = $twoFactor->withSecret(session()['two_factor_secret']); sessionVar()->remove('two_factor_secret'); diff --git a/app/legacy/routes/tag.php b/app/legacy/routes/tag.php index 52b7c32..2222242 100644 --- a/app/legacy/routes/tag.php +++ b/app/legacy/routes/tag.php @@ -85,6 +85,7 @@ return function (Handler $handler) { $sumViews[] = $tag['id']; } } + $sumViews = array_unique($sumViews); $tags_names = array_column($tags, 'name'); $tag_string = implode(', ', $tags_names); $tag_string_no_spaces = implode(',', $tags_names); diff --git a/app/legacy/routes/user.php b/app/legacy/routes/user.php index ed1e039..7c4c2fa 100644 --- a/app/legacy/routes/user.php +++ b/app/legacy/routes/user.php @@ -410,15 +410,18 @@ return function (Handler $handler) { } } if (! isset($tabs)) { - $tabs = Listing::getTabs([ - 'listing' => $type, - 'basename' => $base_user_url, - 'tools' => $tools, - 'tools_available' => $tools_available ?? [], - 'params_hidden' => $params_hidden, - 'params_remove_keys' => $params_remove_keys ?? null, - 'tag' => rawurldecode($tag_string_no_spaces), - ], [], true); + $tabs = Listing::getTabs( + args: [ + 'listing' => $type, + 'basename' => $base_user_url, + 'tools' => $tools, + 'tools_available' => $tools_available ?? [], + 'params_hidden' => $params_hidden, + 'params_remove_keys' => $params_remove_keys ?? null, + 'tag' => rawurldecode($tag_string_no_spaces), + ], + expanded: true + ); $currentKey = $tabs['currentKey']; $tabs = $tabs['tabs']; } diff --git a/app/routing/admin.api-v4.php b/app/routing/admin.api-v4.php deleted file mode 100644 index 092d468..0000000 --- a/app/routing/admin.api-v4.php +++ /dev/null @@ -1,121 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -use function Chevere\Router\route; -use function Chevere\Router\routes; -use Chevereto\Controllers\Api\V4\Ban\Ip\BanIpDeleteController; -use Chevereto\Controllers\Api\V4\Ban\Ip\BanIpPatchController; -use Chevereto\Controllers\Api\V4\Ban\Ip\BanIpPostController; -use Chevereto\Controllers\Api\V4\Category\CategoryPostController; -use Chevereto\Controllers\Api\V4\Image\Bulk\ImageBulkPatchController; -use Chevereto\Controllers\Api\V4\Stat\Rebuild\StatRebuildPostController; -use Chevereto\Controllers\Api\V4\Storage\Migrate\StorageMigratePostController; -use Chevereto\Controllers\Api\V4\Storage\Stat\Regen\StorageStatRegenPostController; -use Chevereto\Controllers\Api\V4\Storage\StoragePostController; -use Chevereto\Controllers\Api\V4\Tool\Id\Decode\ToolDecodeIdGetController; -use Chevereto\Controllers\Api\V4\Tool\Id\Encode\ToolEncodeIdGetController; -use Chevereto\Controllers\Api\V4\Tool\Probe\Email\ToolProbeEmailPostController; -use Chevereto\Controllers\Api\V4\User\Export\UserExportGetController; -use Chevereto\Controllers\Api\V4\User\UserGetController; -use Chevereto\Controllers\Api\V4\User\UserPostController; - -$prefix = '/api/4/admin/'; - -return routes( - route( - path: $prefix . 'bans/ip/', - POST: new BanIpPostController(), - ), - route( - path: $prefix . 'bans/ip/{ip}/', - DELETE: new BanIpDeleteController(), - PATCH: new BanIpPatchController(), - ), - route( - path: $prefix . 'categories/', - POST: new CategoryPostController(), - ), - route( - path: $prefix . 'categories/{id}/', - // DELETE: , - // PATCH: , - ), - route( - path: $prefix . 'images/bulk/approve/', - PATCH: new ImageBulkPatchController(), - ), - route( - path: $prefix . 'imports/', - // POST: , - ), - route( - path: $prefix . 'imports/{id}/', - // DELETE: , - // GET: , - // PATCH: , - ), - route( - path: $prefix . 'imports/{id}/process/', - // POST: , - ), - route( - path: $prefix . 'imports/{id}/reset/', - // POST: , - ), - route( - path: $prefix . 'imports/{id}/resume/', - // POST: , - ), - route( - path: $prefix . 'stats/rebuild/', - POST: new StatRebuildPostController(), - ), - route( - path: $prefix . 'storages/', - POST: new StoragePostController(), - ), - route( - path: $prefix . 'storages/{id}/', - // PATCH: , - ), - route( - path: $prefix . 'storages/{id}/migrate/', - POST: new StorageMigratePostController(), - ), - route( - path: $prefix . 'storages/{id}/stats/regen/', - POST: new StorageStatRegenPostController(), - ), - route( - path: $prefix . 'tools/id/{id}/decode/', - GET: new ToolDecodeIdGetController(), - ), - route( - path: $prefix . 'tools/id/{id}/encode/', - GET: new ToolEncodeIdGetController(), - ), - route( - path: $prefix . 'tools/probe/email/', - POST: new ToolProbeEmailPostController(), - ), - route( - path: $prefix . 'users/', - POST: new UserPostController(), - ), - route( - path: $prefix . 'users/{id}/', - GET: new UserGetController(), - ), - route( - path: $prefix . 'users/{id}/export/', - GET: new UserExportGetController(), - ), -); diff --git a/app/routing/admin.web.php b/app/routing/admin.web.php deleted file mode 100644 index a8335de..0000000 --- a/app/routing/admin.web.php +++ /dev/null @@ -1,39 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -use function App\Controllers\legacyController; -use function Chevere\Router\route; -use function Chevere\Router\routes; - -return routes( - route( - name: 'dashboard', - path: '/dashboard/', - GET: legacyController('route.dashboard.php'), - POST: legacyController('route.dashboard.php'), - ), - route( - name: 'importer-jobs', - path: '/importer-jobs/', - GET: legacyController('route.importer-jobs.php'), - ), - route( - name: 'install', - path: '/install/', - GET: legacyController('route.install.php'), - POST: legacyController('route.install.php'), - ), - route( - name: 'update', - path: '/update/', - POST: legacyController('route.update.php'), - ), -); diff --git a/app/routing/user.api-v1.php b/app/routing/user.api-v1.php deleted file mode 100644 index 42fcae6..0000000 --- a/app/routing/user.api-v1.php +++ /dev/null @@ -1,21 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -use function Chevere\Router\route; -use function Chevere\Router\routes; -use Chevereto\Controllers\Api\V1\Upload\UploadPostController; - -return routes( - route( - path: '/api/1/upload/', - POST: new UploadPostController() - ), -); diff --git a/app/routing/user.api-v4.php b/app/routing/user.api-v4.php deleted file mode 100644 index 9d481ae..0000000 --- a/app/routing/user.api-v4.php +++ /dev/null @@ -1,127 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - - -use function Chevere\Router\route; -use function Chevere\Router\routes; -use Chevereto\Controllers\Api\V4\Album\AlbumDeleteController; -use Chevereto\Controllers\Api\V4\Album\AlbumGetController; -use Chevereto\Controllers\Api\V4\Album\AlbumPatchController; -use Chevereto\Controllers\Api\V4\Album\AlbumPostController; -use Chevereto\Controllers\Api\V4\Album\Like\AlbumLikeDeleteController; -use Chevereto\Controllers\Api\V4\Album\Like\AlbumLikePostController; -use Chevereto\Controllers\Api\V4\Image\Bulk\ImageBulkPatchController; -use Chevereto\Controllers\Api\V4\Image\ImageGetController; -use Chevereto\Controllers\Api\V4\Image\ImagePatchController; -use Chevereto\Controllers\Api\V4\Image\ImagePostController; -use Chevereto\Controllers\Api\V4\Image\Like\ImageLikeDeleteController; -use Chevereto\Controllers\Api\V4\Image\Like\ImageLikePostController; -use Chevereto\Controllers\Api\V4\User\Asset\Avatar\UserAssetAvatarDeleteController; -use Chevereto\Controllers\Api\V4\User\Asset\Avatar\UserAssetAvatarPostController; -use Chevereto\Controllers\Api\V4\User\Asset\Background\UserAssetBackgroundDeleteController; -use Chevereto\Controllers\Api\V4\User\Asset\Background\UserAssetBackgroundPostController; -use Chevereto\Controllers\Api\V4\User\Follow\UserFollowDeleteController; -use Chevereto\Controllers\Api\V4\User\Follow\UserFollowPostController; -use Chevereto\Controllers\Api\V4\User\Setting\UserSettingPatchController; - -$prefix = '/api/4/user/'; - -return routes( - route( - path: $prefix . 'account/notifications/social/', - // GET: , - ), - route( - path: $prefix . 'account/notifications/social/{id}/', - // PATCH: , - ), - route( - path: $prefix . 'account/settings/', - PATCH: new UserSettingPatchController(), - ), - route( - path: $prefix . 'account/login/{service}/', - // DELETE: , - ), - route( - path: $prefix . 'albums/', - POST: new AlbumPostController(), - ), - route( - path: $prefix . 'albums/{id}/', - DELETE: new AlbumDeleteController(), - GET: new AlbumGetController(), - PATCH: new AlbumPatchController(), - ), - route( - path: $prefix . 'albums/{id}/contents/', - // GET: , - ), - route( - path: $prefix . 'albums/{id}/like/', - DELETE: new AlbumLikeDeleteController(), - POST: new AlbumLikePostController(), - ), - route( - path: $prefix . 'albums/bulk/', - // DELETE: , - ), - route( - path: $prefix . 'albums/bulk/parent/', - // PATCH: , - ), - route( - path: $prefix . 'albums/list/', - // GET: - ), - route( - path: $prefix . 'images/', - POST: new ImagePostController(), - ), - route( - path: $prefix . 'images/{id}/', - // DELETE: , - GET: new ImageGetController(), - PATCH: new ImagePatchController(), - ), - route( - path: $prefix . 'images/{id}/like/', - DELETE: new ImageLikeDeleteController(), - POST: new ImageLikePostController(), - ), - route( - path: $prefix . 'images/bulk/', - PATCH: new ImageBulkPatchController(), - ), - route( - path: $prefix . 'images/list/', - // GET: - ), - route( - path: $prefix . 'user/{username}/assets/avatar/', - DELETE: new UserAssetAvatarDeleteController(), - POST: new UserAssetAvatarPostController() - ), - route( - path: $prefix . 'user/{username}/assets/background/', - DELETE: new UserAssetBackgroundDeleteController(), - POST: new UserAssetBackgroundPostController() - ), - route( - path: $prefix . 'users/{username}/follow/', - DELETE: new UserFollowDeleteController(), - POST: new UserFollowPostController(), - ), - route( - path: $prefix . 'users/list/', - // GET:, - ), -); diff --git a/app/routing/user.web.php b/app/routing/user.web.php deleted file mode 100644 index 46dce2e..0000000 --- a/app/routing/user.web.php +++ /dev/null @@ -1,127 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -use function App\Controllers\legacyController; -use function Chevere\Router\route; -use function Chevere\Router\routes; - -return routes( - route( - name: 'index', - path: '/', - ), - route( - name: 'account', - path: '/account/', - GET: legacyController('account.php'), - POST: legacyController('account.php'), - ), - route( - name: 'album', - path: '/album/', - GET: legacyController('album.php'), - POST: legacyController('album.php'), - ), - route( - name: 'category', - path: '/category/', - GET: legacyController('category.php'), - ), - route( - name: 'connect', - path: '/connect/', - GET: legacyController('connect.php'), - ), - route( - name: 'explore', - path: '/explore/', - GET: legacyController('explore.php'), - ), - route( - name: 'following', - path: '/following/', - GET: legacyController('following.php'), - ), - route( - name: 'image', - path: '/image/', - GET: legacyController('image.php'), - ), - route( - name: 'login', - path: '/login/', - GET: legacyController('login.php'), - POST: legacyController('login.php'), - ), - route( - name: 'logout', - path: '/logout/', - GET: legacyController('logout.php'), - ), - route( - name: 'moderate', - path: '/moderate/', - GET: legacyController('moderate.php'), - ), - route( - name: 'oembed', - path: '/oembed/', - GET: legacyController('oembed.php'), - ), - route( - name: 'page', - path: '/page/', - GET: legacyController('page.php'), - ), - route( - name: 'plugin', - path: '/plugin/', - GET: legacyController('plugin.php'), - ), - route( - name: 'captcha-verify', - path: '/captcha-verify/', - GET: legacyController('captcha-verify.php'), - ), - route( - name: 'redirect', - path: '/redirect/', - GET: legacyController('redirect.php'), - ), - route( - name: 'search', - path: '/search/', - GET: legacyController('search.php'), - POST: legacyController('search.php'), - ), - route( - name: 'settings', - path: '/settings/', - GET: legacyController('settings.php'), - POST: legacyController('settings.php'), - ), - route( - name: 'signup', - path: '/signup/', - GET: legacyController('signup.php'), - POST: legacyController('signup.php'), - ), - route( - name: 'upload', - path: '/upload/', - GET: legacyController('upload.php'), - ), - route( - name: 'user', - path: '/user/', - GET: legacyController('user.php'), - ), -); diff --git a/app/schemas/mysql-5/albums.sql b/app/schemas/mysql-5/albums.sql index e8c8e53..7518220 100644 --- a/app/schemas/mysql-5/albums.sql +++ b/app/schemas/mysql-5/albums.sql @@ -1,22 +1,22 @@ DROP TABLE IF EXISTS `%table_prefix%albums`; CREATE TABLE `%table_prefix%albums` ( - `album_id` bigint(32) NOT NULL AUTO_INCREMENT, - `album_name` varchar(100) NOT NULL, - `album_user_id` bigint(32) DEFAULT NULL, - `album_date` datetime NOT NULL, - `album_date_gmt` datetime NOT NULL, - `album_creation_ip` varchar(255) NOT NULL, - `album_privacy` enum('public','password','private','private_but_link','custom') DEFAULT 'public', - `album_privacy_extra` text, - `album_password` text, - `album_image_count` bigint(32) NOT NULL DEFAULT '0', - `album_description` text, - `album_likes` bigint(32) NOT NULL DEFAULT '0', - `album_views` bigint(32) NOT NULL DEFAULT '0', - `album_cover_id` bigint(32) DEFAULT NULL, - `album_parent_id` bigint(32) DEFAULT NULL, - `album_cta_enable` tinyint(1) NOT NULL DEFAULT '0', - `album_cta` text, + `album_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `album_name` VARCHAR(100) NOT NULL, + `album_user_id` INT UNSIGNED DEFAULT NULL, + `album_date` DATETIME NOT NULL, + `album_date_gmt` DATETIME NOT NULL, + `album_creation_ip` VARCHAR(255) NOT NULL, + `album_privacy` ENUM('public','password','private','private_but_link','custom') DEFAULT 'public', + `album_privacy_extra` TEXT, + `album_password` TEXT, + `album_image_count` INT UNSIGNED NOT NULL DEFAULT '0', + `album_description` TEXT, + `album_likes` INT UNSIGNED NOT NULL DEFAULT '0', + `album_views` INT UNSIGNED NOT NULL DEFAULT '0', + `album_cover_id` INT UNSIGNED DEFAULT NULL, + `album_parent_id` INT UNSIGNED DEFAULT NULL, + `album_cta_enable` TINYINT UNSIGNED NOT NULL DEFAULT '0', + `album_cta` TEXT, PRIMARY KEY (`album_id`), KEY `album_name` (`album_name`), KEY `album_user_id` (`album_user_id`), @@ -27,5 +27,6 @@ CREATE TABLE `%table_prefix%albums` ( KEY `album_likes` (`album_likes`), KEY `album_views` (`album_views`), KEY `album_parent_id` (`album_parent_id`), + KEY `album_user_id_parent_id_name` (`album_user_id`, `album_parent_id`, `album_name`), FULLTEXT KEY `searchindex` (`album_name`,`album_description`) ) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; diff --git a/app/schemas/mysql-5/api_keys.sql b/app/schemas/mysql-5/api_keys.sql index 70e0478..e7632a6 100644 --- a/app/schemas/mysql-5/api_keys.sql +++ b/app/schemas/mysql-5/api_keys.sql @@ -1,10 +1,10 @@ DROP TABLE IF EXISTS `%table_prefix%api_keys`; CREATE TABLE `%table_prefix%api_keys` ( - `api_key_id` bigint(32) NOT NULL AUTO_INCREMENT, - `api_key_user_id` bigint(32) DEFAULT NULL, - `api_key_name` varchar(100) DEFAULT NULL, - `api_key_date_gmt` datetime NOT NULL, - `api_key_hash` text NOT NULL, + `api_key_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `api_key_user_id` INT UNSIGNED DEFAULT NULL, + `api_key_name` VARCHAR(100) DEFAULT NULL, + `api_key_date_gmt` DATETIME NOT NULL, + `api_key_hash` TEXT NOT NULL, PRIMARY KEY (`api_key_id`), KEY `api_key_user_id` (`api_key_user_id`), KEY `api_key_name` (`api_key_name`), diff --git a/app/schemas/mysql-5/assets.sql b/app/schemas/mysql-5/assets.sql index 79c32bf..b89834d 100644 --- a/app/schemas/mysql-5/assets.sql +++ b/app/schemas/mysql-5/assets.sql @@ -1,10 +1,10 @@ DROP TABLE IF EXISTS `%table_prefix%assets`; CREATE TABLE `%table_prefix%assets` ( - `asset_id` bigint(32) NOT NULL AUTO_INCREMENT, - `asset_key` varchar(255) NOT NULL, - `asset_md5` varchar(32) NOT NULL, - `asset_filename` varchar(255) NOT NULL, - `asset_file_path` varchar(255) NOT NULL, + `asset_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `asset_key` VARCHAR(255) NOT NULL, + `asset_checksum` VARCHAR(32) NOT NULL, + `asset_filename` VARCHAR(255) NOT NULL, + `asset_file_path` VARCHAR(255) NOT NULL, `asset_blob` blob, PRIMARY KEY (`asset_id`), UNIQUE KEY `key` (`asset_key`(191)) USING BTREE, diff --git a/app/schemas/mysql-5/categories.sql b/app/schemas/mysql-5/categories.sql index c25baa0..17c3eef 100644 --- a/app/schemas/mysql-5/categories.sql +++ b/app/schemas/mysql-5/categories.sql @@ -1,9 +1,10 @@ DROP TABLE IF EXISTS `%table_prefix%categories`; CREATE TABLE `%table_prefix%categories` ( - `category_id` bigint(32) NOT NULL AUTO_INCREMENT, - `category_name` varchar(32) NOT NULL, - `category_url_key` varchar(32) COLLATE utf8mb4_bin NOT NULL, - `category_description` text, + `category_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `category_name` VARCHAR(32) NOT NULL, + `category_url_key` VARCHAR(32) COLLATE utf8mb4_bin NOT NULL, + `category_description` TEXT, PRIMARY KEY (`category_id`), + KEY `category_name` (`category_name`), UNIQUE KEY `url_key` (`category_url_key`) USING BTREE ) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; diff --git a/app/schemas/mysql-5/confirmations.sql b/app/schemas/mysql-5/confirmations.sql index 57dd9d1..21570f9 100644 --- a/app/schemas/mysql-5/confirmations.sql +++ b/app/schemas/mysql-5/confirmations.sql @@ -1,13 +1,13 @@ DROP TABLE IF EXISTS `%table_prefix%confirmations`; CREATE TABLE `%table_prefix%confirmations` ( - `confirmation_id` bigint(32) NOT NULL AUTO_INCREMENT, - `confirmation_user_id` bigint(32) NOT NULL, - `confirmation_type` enum('account-activate','account-change-email','account-password-forgot') NOT NULL, - `confirmation_date` datetime NOT NULL, - `confirmation_date_gmt` datetime NOT NULL, - `confirmation_token_hash` varchar(255) NOT NULL, - `confirmation_status` enum('active','valid','invalid') NOT NULL, - `confirmation_extra` mediumtext, + `confirmation_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `confirmation_user_id` INT UNSIGNED NOT NULL, + `confirmation_type` ENUM('account-activate','account-change-email','account-password-forgot') NOT NULL, + `confirmation_date` DATETIME NOT NULL, + `confirmation_date_gmt` DATETIME NOT NULL, + `confirmation_token_hash` VARCHAR(255) NOT NULL, + `confirmation_status` ENUM('active','valid','invalid') NOT NULL, + `confirmation_extra` TEXT, PRIMARY KEY (`confirmation_id`), KEY `confirmation_user` (`confirmation_user_id`), KEY `confirmation_user_type` (`confirmation_user_id`, `confirmation_type`), diff --git a/app/schemas/mysql-5/deletions.sql b/app/schemas/mysql-5/deletions.sql index 1aca667..48ebf54 100644 --- a/app/schemas/mysql-5/deletions.sql +++ b/app/schemas/mysql-5/deletions.sql @@ -1,20 +1,20 @@ DROP TABLE IF EXISTS `%table_prefix%deletions`; CREATE TABLE `%table_prefix%deletions` ( - `deleted_id` bigint(32) NOT NULL AUTO_INCREMENT, - `deleted_date_gmt` datetime NOT NULL, - `deleted_content_id` bigint(32) NOT NULL, - `deleted_content_date_gmt` datetime NOT NULL, - `deleted_content_user_id` bigint(32) DEFAULT NULL, - `deleted_content_ip` varchar(255) NOT NULL, - `deleted_content_md5` varchar(32) DEFAULT NULL, - `deleted_content_original_filename` varchar(255) DEFAULT NULL, - `deleted_content_views` bigint(32) NOT NULL DEFAULT '0', - `deleted_content_likes` bigint(32) NOT NULL DEFAULT '0', + `deleted_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `deleted_date_gmt` DATETIME NOT NULL, + `deleted_content_id` INT UNSIGNED NOT NULL, + `deleted_content_date_gmt` DATETIME NOT NULL, + `deleted_content_user_id` INT UNSIGNED DEFAULT NULL, + `deleted_content_ip` VARCHAR(255) NOT NULL, + `deleted_content_checksum` VARCHAR(32) DEFAULT NULL, + `deleted_content_original_filename` VARCHAR(255) DEFAULT NULL, + `deleted_content_views` INT UNSIGNED NOT NULL DEFAULT '0', + `deleted_content_likes` INT UNSIGNED NOT NULL DEFAULT '0', PRIMARY KEY (`deleted_id`), KEY `deleted_content_id` (`deleted_content_id`), KEY `deleted_content_user_id` (`deleted_content_user_id`), KEY `deleted_content_ip` (`deleted_content_ip`), - KEY `deleted_content_md5` (`deleted_content_md5`), + KEY `deleted_content_checksum` (`deleted_content_checksum`), KEY `deleted_content_views` (`deleted_content_views`), KEY `deleted_content_likes` (`deleted_content_likes`) ) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; diff --git a/app/schemas/mysql-5/follows.sql b/app/schemas/mysql-5/follows.sql index 5e51ed9..60a6f5e 100644 --- a/app/schemas/mysql-5/follows.sql +++ b/app/schemas/mysql-5/follows.sql @@ -1,11 +1,11 @@ DROP TABLE IF EXISTS `%table_prefix%follows`; CREATE TABLE `%table_prefix%follows` ( - `follow_id` bigint(32) NOT NULL AUTO_INCREMENT, - `follow_date` datetime NOT NULL, - `follow_date_gmt` datetime NOT NULL, - `follow_user_id` bigint(32) NOT NULL, - `follow_followed_user_id` bigint(32) NOT NULL, - `follow_ip` varchar(255) NOT NULL, + `follow_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `follow_date` DATETIME NOT NULL, + `follow_date_gmt` DATETIME NOT NULL, + `follow_user_id` INT UNSIGNED NOT NULL, + `follow_followed_user_id` INT UNSIGNED NOT NULL, + `follow_ip` VARCHAR(255) NOT NULL, PRIMARY KEY (`follow_id`), KEY `follow_user_id` (`follow_user_id`), KEY `follow_followed_user_id` (`follow_followed_user_id`) diff --git a/app/schemas/mysql-5/images.sql b/app/schemas/mysql-5/images.sql index 5e091e9..866c37f 100644 --- a/app/schemas/mysql-5/images.sql +++ b/app/schemas/mysql-5/images.sql @@ -1,39 +1,39 @@ DROP TABLE IF EXISTS `%table_prefix%images`; 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` bigint(11) UNSIGNED NOT NULL, - `image_width` int(11) NOT NULL, - `image_height` int(11) NOT NULL, - `image_date` datetime NOT NULL, - `image_date_gmt` datetime NOT NULL, - `image_title` varchar(100) DEFAULT NULL, - `image_description` text, - `image_nsfw` tinyint(1) NOT NULL DEFAULT '0', - `image_user_id` bigint(32) DEFAULT NULL, - `image_album_id` bigint(32) DEFAULT NULL, - `image_uploader_ip` varchar(255) NOT NULL, - `image_storage_mode` enum('datefolder','direct','old','path') NOT NULL DEFAULT 'datefolder', - `image_path` varchar(4096) DEFAULT NULL, - `image_storage_id` bigint(32) DEFAULT NULL, - `image_md5` varchar(32) NOT NULL, - `image_source_md5` varchar(32) DEFAULT NULL, - `image_original_filename` varchar(255) NOT NULL, - `image_original_exifdata` mediumtext, - `image_views` bigint(32) NOT NULL DEFAULT '0', - `image_category_id` bigint(32) DEFAULT 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 + `image_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `image_name` VARCHAR(255) NOT NULL, + `image_extension` VARCHAR(255) NOT NULL, + `image_size` BIGINT UNSIGNED NOT NULL, + `image_width` INT UNSIGNED NOT NULL, + `image_height` INT UNSIGNED NOT NULL, + `image_date` DATETIME NOT NULL, + `image_date_gmt` DATETIME NOT NULL, + `image_title` VARCHAR(100) DEFAULT NULL, + `image_description` TEXT, + `image_nsfw` TINYINT UNSIGNED NOT NULL DEFAULT '0', + `image_user_id` INT UNSIGNED DEFAULT NULL, + `image_album_id` INT UNSIGNED DEFAULT NULL, + `image_uploader_ip` VARCHAR(255) NOT NULL, + `image_storage_mode` ENUM('datefolder','direct','old','path') NOT NULL DEFAULT 'datefolder', + `image_path` VARCHAR(4096) DEFAULT NULL, + `image_storage_id` INT UNSIGNED DEFAULT NULL, + `image_checksum` VARCHAR(32) NOT NULL, + `image_source_checksum` VARCHAR(32) DEFAULT NULL, + `image_original_filename` VARCHAR(255) NOT NULL, + `image_original_exifdata` MEDIUMTEXT, + `image_views` INT UNSIGNED NOT NULL DEFAULT '0', + `image_category_id` INT UNSIGNED DEFAULT NULL, + `image_chain` TINYINT UNSIGNED NOT NULL, + `image_thumb_size` INT UNSIGNED NOT NULL, + `image_medium_size` INT UNSIGNED NOT NULL DEFAULT '0', + `image_frame_size` INT UNSIGNED NOT NULL DEFAULT '0', + `image_expiration_date_gmt` DATETIME DEFAULT NULL, + `image_likes` INT UNSIGNED NOT NULL DEFAULT '0', + `image_is_animated` TINYINT UNSIGNED NOT NULL DEFAULT '0', + `image_is_approved` TINYINT UNSIGNED NOT NULL DEFAULT '1', + `image_is_360` TINYINT UNSIGNED NOT NULL DEFAULT '0', + `image_duration` INT UNSIGNED NOT NULL DEFAULT '0', + `image_type` TINYINT 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','mov') then 2 @@ -53,8 +53,8 @@ CREATE TABLE `%table_prefix%images` ( KEY `image_storage_mode` (`image_storage_mode`), KEY `image_path` (`image_path`(255)), KEY `image_storage_id` (`image_storage_id`), - KEY `image_md5` (`image_md5`), - KEY `image_source_md5` (`image_source_md5`), + KEY `image_checksum` (`image_checksum`), + KEY `image_source_checksum` (`image_source_checksum`), KEY `image_views` (`image_views`), KEY `image_category_id` (`image_category_id`), KEY `image_chain` (`image_chain`), @@ -66,5 +66,6 @@ CREATE TABLE `%table_prefix%images` ( 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`) + FULLTEXT KEY `searchindex` (`image_name`,`image_title`,`image_description`,`image_original_filename`), + KEY `image_uploader_ip_date_gmt_checksum_source_checksum` (`image_uploader_ip`, `image_date_gmt`, `image_checksum`, `image_source_checksum`) ) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; diff --git a/app/schemas/mysql-5/images_hash.sql b/app/schemas/mysql-5/images_hash.sql index d5e819e..76f0d77 100644 --- a/app/schemas/mysql-5/images_hash.sql +++ b/app/schemas/mysql-5/images_hash.sql @@ -1,6 +1,6 @@ DROP TABLE IF EXISTS `%table_prefix%images_hash`; CREATE TABLE `%table_prefix%images_hash` ( - `image_hash_image_id` bigint(32) NOT NULL, - `image_hash_hash` mediumtext NOT NULL, + `image_hash_image_id` INT UNSIGNED NOT NULL, + `image_hash_hash` TEXT NOT NULL, PRIMARY KEY (`image_hash_image_id`) ) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; diff --git a/app/schemas/mysql-5/importing.sql b/app/schemas/mysql-5/importing.sql index dee032b..c896189 100644 --- a/app/schemas/mysql-5/importing.sql +++ b/app/schemas/mysql-5/importing.sql @@ -1,10 +1,10 @@ DROP TABLE IF EXISTS `%table_prefix%importing`; CREATE TABLE `%table_prefix%importing` ( - `importing_id` bigint(32) NOT NULL AUTO_INCREMENT, - `importing_import_id` bigint(32) NOT NULL, - `importing_path` varchar(4096) NOT NULL, - `importing_content_type` enum('user','album','image') NOT NULL, - `importing_content_id` bigint(32) DEFAULT NULL, + `importing_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `importing_import_id` INT UNSIGNED NOT NULL, + `importing_path` VARCHAR(4096) NOT NULL, + `importing_content_type` ENUM('user','album','image') NOT NULL, + `importing_content_id` INT UNSIGNED DEFAULT NULL, PRIMARY KEY (`importing_id`), UNIQUE KEY `importing_path` (`importing_path`(191)) ) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; diff --git a/app/schemas/mysql-5/imports.sql b/app/schemas/mysql-5/imports.sql index af44023..860f358 100644 --- a/app/schemas/mysql-5/imports.sql +++ b/app/schemas/mysql-5/imports.sql @@ -1,17 +1,17 @@ DROP TABLE IF EXISTS `%table_prefix%imports`; CREATE TABLE `%table_prefix%imports` ( - `import_id` bigint(32) NOT NULL AUTO_INCREMENT, - `import_path` varchar(4096) NOT NULL, - `import_options` varchar(255) DEFAULT NULL, - `import_status` enum('queued','working','paused','canceled','completed') NOT NULL, - `import_users` bigint(32) NOT NULL DEFAULT '0', - `import_images` bigint(32) NOT NULL DEFAULT '0', - `import_albums` bigint(32) NOT NULL DEFAULT '0', - `import_time_created` datetime DEFAULT NULL, - `import_time_updated` datetime DEFAULT NULL, - `import_errors` tinyint(1) NOT NULL DEFAULT '0', - `import_started` tinyint(1) NOT NULL DEFAULT '0', - `import_continuous` tinyint(1) NOT NULL DEFAULT '0', + `import_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `import_path` VARCHAR(4096) NOT NULL, + `import_options` VARCHAR(255) DEFAULT NULL, + `import_status` ENUM('queued','working','paused','canceled','completed') NOT NULL, + `import_users` INT UNSIGNED NOT NULL DEFAULT '0', + `import_images` INT UNSIGNED NOT NULL DEFAULT '0', + `import_albums` INT UNSIGNED NOT NULL DEFAULT '0', + `import_time_created` DATETIME DEFAULT NULL, + `import_time_updated` DATETIME DEFAULT NULL, + `import_errors` TINYINT UNSIGNED NOT NULL DEFAULT '0', + `import_started` TINYINT UNSIGNED NOT NULL DEFAULT '0', + `import_continuous` TINYINT UNSIGNED NOT NULL DEFAULT '0', PRIMARY KEY (`import_id`), KEY `import_path` (`import_path`(191)) USING BTREE ) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; diff --git a/app/schemas/mysql-5/ip_bans.sql b/app/schemas/mysql-5/ip_bans.sql index 72f2c1c..7ca6d17 100644 --- a/app/schemas/mysql-5/ip_bans.sql +++ b/app/schemas/mysql-5/ip_bans.sql @@ -1,13 +1,13 @@ DROP TABLE IF EXISTS `%table_prefix%ip_bans`; CREATE TABLE `%table_prefix%ip_bans` ( - `ip_ban_id` bigint(20) NOT NULL AUTO_INCREMENT, - `ip_ban_date` datetime NOT NULL, - `ip_ban_date_gmt` datetime NOT NULL, - `ip_ban_expires` datetime DEFAULT NULL, - `ip_ban_expires_gmt` datetime DEFAULT NULL, - `ip_ban_ip` varchar(255) NOT NULL, - `ip_ban_message` text, + `ip_ban_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `ip_ban_date` DATETIME NOT NULL, + `ip_ban_date_gmt` DATETIME NOT NULL, + `ip_ban_expires` DATETIME DEFAULT NULL, + `ip_ban_expires_gmt` DATETIME DEFAULT NULL, + `ip_ban_ip` VARCHAR(255) NOT NULL, + `ip_ban_message` TEXT, PRIMARY KEY (`ip_ban_id`), - KEY `ip_ban_date_gmt` (`ip_ban_date_gmt`), - UNIQUE KEY `ip_ban_ip` (`ip_ban_ip`(191)) USING BTREE + UNIQUE KEY `ip_ban_ip` (`ip_ban_ip`(191)) USING BTREE, + KEY `ip_ban_ip_expires_gmt_id` (`ip_ban_ip`, `ip_ban_expires_gmt`, `ip_ban_id` DESC) ) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; diff --git a/app/schemas/mysql-5/likes.sql b/app/schemas/mysql-5/likes.sql index 4e92ad0..629b2c6 100644 --- a/app/schemas/mysql-5/likes.sql +++ b/app/schemas/mysql-5/likes.sql @@ -1,13 +1,13 @@ DROP TABLE IF EXISTS `%table_prefix%likes`; CREATE TABLE `%table_prefix%likes` ( - `like_id` bigint(32) NOT NULL AUTO_INCREMENT, - `like_date` datetime NOT NULL, - `like_date_gmt` datetime NOT NULL, - `like_user_id` bigint(32) DEFAULT NULL, - `like_content_type` enum('image','album') DEFAULT NULL, - `like_content_id` bigint(32) NOT NULL, - `like_content_user_id` bigint(32) DEFAULT NULL, - `like_ip` varchar(255) NOT NULL, + `like_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `like_date` DATETIME NOT NULL, + `like_date_gmt` DATETIME NOT NULL, + `like_user_id` INT UNSIGNED DEFAULT NULL, + `like_content_type` ENUM('image','album') DEFAULT NULL, + `like_content_id` INT UNSIGNED NOT NULL, + `like_content_user_id` INT UNSIGNED DEFAULT NULL, + `like_ip` VARCHAR(255) NOT NULL, PRIMARY KEY (`like_id`), KEY `like_date_gmt` (`like_date_gmt`), KEY `like_user_id` (`like_user_id`), diff --git a/app/schemas/mysql-5/locks.sql b/app/schemas/mysql-5/locks.sql index db3b44a..6c66763 100644 --- a/app/schemas/mysql-5/locks.sql +++ b/app/schemas/mysql-5/locks.sql @@ -1,9 +1,9 @@ DROP TABLE IF EXISTS `%table_prefix%locks`; CREATE TABLE `%table_prefix%locks` ( - `lock_id` bigint(20) NOT NULL AUTO_INCREMENT, - `lock_name` varchar(255) NOT NULL, - `lock_date_gmt` datetime NOT NULL, - `lock_expires_gmt` datetime DEFAULT NULL, + `lock_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `lock_name` VARCHAR(255) NOT NULL, + `lock_date_gmt` DATETIME NOT NULL, + `lock_expires_gmt` DATETIME DEFAULT NULL, PRIMARY KEY (`lock_id`), KEY `lock_date_gmt` (`lock_date_gmt`), KEY `lock_expires_gmt` (`lock_expires_gmt`), diff --git a/app/schemas/mysql-5/login_connections.sql b/app/schemas/mysql-5/login_connections.sql index ff13bb5..4bf5617 100644 --- a/app/schemas/mysql-5/login_connections.sql +++ b/app/schemas/mysql-5/login_connections.sql @@ -1,12 +1,12 @@ DROP TABLE IF EXISTS `%table_prefix%login_connections`; CREATE TABLE `%table_prefix%login_connections` ( - `login_connection_id` bigint(32) NOT NULL AUTO_INCREMENT, - `login_connection_user_id` bigint(32) NOT NULL, - `login_connection_provider_id` bigint(32) NOT NULL, - `login_connection_date_gmt` datetime NOT NULL, - `login_connection_resource_id` varchar(255) NOT NULL, - `login_connection_resource_name` text, - `login_connection_token` text NOT NULL COMMENT 'Ciphertext', + `login_connection_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `login_connection_user_id` INT UNSIGNED NOT NULL, + `login_connection_provider_id` INT UNSIGNED NOT NULL, + `login_connection_date_gmt` DATETIME NOT NULL, + `login_connection_resource_id` VARCHAR(255) NOT NULL, + `login_connection_resource_name` TEXT, + `login_connection_token` TEXT NOT NULL COMMENT 'Ciphertext', PRIMARY KEY (`login_connection_id`), UNIQUE KEY `login_connection_unique` (`login_connection_user_id`,`login_connection_provider_id`), KEY `login_connection_user_id` (`login_connection_user_id`), diff --git a/app/schemas/mysql-5/login_cookies.sql b/app/schemas/mysql-5/login_cookies.sql index d32453c..6a99dd4 100644 --- a/app/schemas/mysql-5/login_cookies.sql +++ b/app/schemas/mysql-5/login_cookies.sql @@ -1,16 +1,17 @@ DROP TABLE IF EXISTS `%table_prefix%login_cookies`; CREATE TABLE `%table_prefix%login_cookies` ( - `login_cookie_id` bigint(32) NOT NULL AUTO_INCREMENT, - `login_cookie_user_id` bigint(32) NOT NULL, - `login_cookie_connection_id` bigint(32) DEFAULT 0, - `login_cookie_date_gmt` datetime NOT NULL, - `login_cookie_ip` varchar(255) DEFAULT NULL, - `login_cookie_user_agent` text NOT NULL, - `login_cookie_hash` text NOT NULL, + `login_cookie_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `login_cookie_user_id` INT UNSIGNED NOT NULL, + `login_cookie_connection_id` INT UNSIGNED DEFAULT 0, + `login_cookie_date_gmt` DATETIME NOT NULL, + `login_cookie_ip` VARCHAR(255) DEFAULT NULL, + `login_cookie_user_agent` TEXT NOT NULL, + `login_cookie_hash` TEXT NOT NULL, PRIMARY KEY (`login_cookie_id`), UNIQUE KEY `login_cookie_unique` (`login_cookie_user_id`,`login_cookie_connection_id`,`login_cookie_date_gmt`), KEY `login_cookie_user_id_date_gmt` (`login_cookie_user_id`, `login_cookie_date_gmt`), KEY `login_cookie_user_id` (`login_cookie_user_id`), KEY `login_cookie_ip` (`login_cookie_ip`), - KEY `login_cookie_connection_id` (`login_cookie_connection_id`) + KEY `login_cookie_connection_id` (`login_cookie_connection_id`), + KEY `login_cookie_user_id_date_gmt_connection_id` (`login_cookie_user_id`, `login_cookie_date_gmt`, `login_cookie_connection_id`) ) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; diff --git a/app/schemas/mysql-5/login_passwords.sql b/app/schemas/mysql-5/login_passwords.sql index b1f7385..6202a05 100644 --- a/app/schemas/mysql-5/login_passwords.sql +++ b/app/schemas/mysql-5/login_passwords.sql @@ -1,9 +1,9 @@ DROP TABLE IF EXISTS `%table_prefix%login_passwords`; CREATE TABLE `%table_prefix%login_passwords` ( - `login_password_id` bigint(32) NOT NULL AUTO_INCREMENT, - `login_password_user_id` bigint(32) NOT NULL, - `login_password_date_gmt` datetime NOT NULL, - `login_password_hash` text NOT NULL, + `login_password_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `login_password_user_id` INT UNSIGNED NOT NULL, + `login_password_date_gmt` DATETIME NOT NULL, + `login_password_hash` TEXT NOT NULL, PRIMARY KEY (`login_password_id`), UNIQUE KEY `login_password_user_id` (`login_password_user_id`) ) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; diff --git a/app/schemas/mysql-5/login_providers.sql b/app/schemas/mysql-5/login_providers.sql index 595bae2..1a004d1 100644 --- a/app/schemas/mysql-5/login_providers.sql +++ b/app/schemas/mysql-5/login_providers.sql @@ -1,14 +1,15 @@ DROP TABLE IF EXISTS `%table_prefix%login_providers`; CREATE TABLE `%table_prefix%login_providers` ( - `login_provider_id` bigint(32) NOT NULL AUTO_INCREMENT, - `login_provider_name` varchar(255) DEFAULT NULL, - `login_provider_label` varchar(255) DEFAULT NULL, - `login_provider_key_id` text DEFAULT NULL, - `login_provider_key_secret` text DEFAULT NULL, - `login_provider_is_enabled` tinyint(1) NOT NULL DEFAULT '1', + `login_provider_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `login_provider_name` VARCHAR(255) DEFAULT NULL, + `login_provider_label` VARCHAR(255) DEFAULT NULL, + `login_provider_key_id` TEXT DEFAULT NULL, + `login_provider_key_secret` TEXT DEFAULT NULL, + `login_provider_is_enabled` TINYINT UNSIGNED NOT NULL DEFAULT '1', PRIMARY KEY (`login_provider_id`), UNIQUE KEY `login_provider_name` (`login_provider_name`(191)), - KEY `login_provider_is_enabled` (`login_provider_is_enabled`) + KEY `login_provider_is_enabled` (`login_provider_is_enabled`), + KEY `login_provider_id_is_enabled_name` (`login_provider_id`, `login_provider_is_enabled`, `login_provider_name` DESC) ) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; INSERT INTO `%table_prefix%login_providers` VALUES ('1', 'facebook', 'Facebook', null, null, '0'); INSERT INTO `%table_prefix%login_providers` VALUES ('2', 'twitter', 'Twitter', null, null, '0'); diff --git a/app/schemas/mysql-5/logins.sql b/app/schemas/mysql-5/logins.sql index 9ebe4f6..1af57eb 100644 --- a/app/schemas/mysql-5/logins.sql +++ b/app/schemas/mysql-5/logins.sql @@ -1,18 +1,18 @@ DROP TABLE IF EXISTS `%table_prefix%logins`; CREATE TABLE `%table_prefix%logins` ( - `login_id` bigint(32) NOT NULL AUTO_INCREMENT, - `login_user_id` bigint(32) NOT NULL, - `login_type` enum('password','session','cookie','facebook','twitter','google','vk','cookie_facebook','cookie_twitter','cookie_google','cookie_vk') NOT NULL, - `login_ip` varchar(255) DEFAULT NULL, - `login_hostname` text, - `login_date` datetime NOT NULL, - `login_date_gmt` datetime NOT NULL, - `login_resource_id` varchar(255) DEFAULT NULL, - `login_resource_name` text, - `login_resource_avatar` text, - `login_resource_url` text, - `login_secret` text DEFAULT NULL COMMENT 'The secret part', - `login_token_hash` text COMMENT 'Hashed complement to secret if needed', + `login_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `login_user_id` INT UNSIGNED NOT NULL, + `login_type` ENUM('password','session','cookie','facebook','twitter','google','vk','cookie_facebook','cookie_twitter','cookie_google','cookie_vk') NOT NULL, + `login_ip` VARCHAR(255) DEFAULT NULL, + `login_hostname` TEXT, + `login_date` DATETIME NOT NULL, + `login_date_gmt` DATETIME NOT NULL, + `login_resource_id` VARCHAR(255) DEFAULT NULL, + `login_resource_name` TEXT, + `login_resource_avatar` TEXT, + `login_resource_url` TEXT, + `login_secret` TEXT DEFAULT NULL COMMENT 'The secret part', + `login_token_hash` TEXT COMMENT 'Hashed complement to secret if needed', PRIMARY KEY (`login_id`), KEY `login_user_id` (`login_user_id`) ) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; diff --git a/app/schemas/mysql-5/notifications.sql b/app/schemas/mysql-5/notifications.sql index 51044c3..9408cba 100644 --- a/app/schemas/mysql-5/notifications.sql +++ b/app/schemas/mysql-5/notifications.sql @@ -1,13 +1,13 @@ DROP TABLE IF EXISTS `%table_prefix%notifications`; CREATE TABLE `%table_prefix%notifications` ( - `notification_id` bigint(32) NOT NULL AUTO_INCREMENT, - `notification_date_gmt` datetime NOT NULL, - `notification_user_id` bigint(32) NOT NULL, - `notification_trigger_user_id` bigint(32) DEFAULT NULL, - `notification_type` enum('follow','like') NOT NULL, - `notification_content_type` enum('user','image','album') NOT NULL, - `notification_type_id` bigint(32) NOT NULL COMMENT 'type_id based on action (type) table', - `notification_is_read` tinyint(1) NOT NULL DEFAULT '0', + `notification_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `notification_date_gmt` DATETIME NOT NULL, + `notification_user_id` INT UNSIGNED NOT NULL, + `notification_trigger_user_id` INT UNSIGNED DEFAULT NULL, + `notification_type` ENUM('follow','like') NOT NULL, + `notification_content_type` ENUM('user','image','album') NOT NULL, + `notification_type_id` INT UNSIGNED NOT NULL COMMENT 'type_id based on action (type) table', + `notification_is_read` TINYINT UNSIGNED NOT NULL DEFAULT '0', PRIMARY KEY (`notification_id`), KEY `notification_date_gmt` (`notification_date_gmt`), KEY `notification_user_id` (`notification_user_id`), diff --git a/app/schemas/mysql-5/pages.sql b/app/schemas/mysql-5/pages.sql index 3ef4aa9..7ec4e28 100644 --- a/app/schemas/mysql-5/pages.sql +++ b/app/schemas/mysql-5/pages.sql @@ -1,21 +1,21 @@ DROP TABLE IF EXISTS `%table_prefix%pages`; CREATE TABLE `%table_prefix%pages` ( - `page_id` bigint(32) NOT NULL AUTO_INCREMENT, - `page_url_key` varchar(32) DEFAULT NULL, - `page_type` enum('internal','link') NOT NULL DEFAULT 'internal', - `page_file_path` varchar(255) DEFAULT NULL, - `page_link_url` text, - `page_icon` varchar(255) DEFAULT NULL, - `page_title` varchar(255) NOT NULL, - `page_description` text, - `page_keywords` text, - `page_is_active` tinyint(1) NOT NULL DEFAULT '1', - `page_is_link_visible` tinyint(1) NOT NULL DEFAULT '1', - `page_attr_target` enum('_self','_blank') DEFAULT '_self', - `page_attr_rel` varchar(255) DEFAULT NULL, - `page_sort_display` int(11) DEFAULT NULL, - `page_internal` varchar(255) DEFAULT NULL, - `page_code` text, + `page_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `page_url_key` VARCHAR(32) DEFAULT NULL, + `page_type` ENUM('internal','link') NOT NULL DEFAULT 'internal', + `page_file_path` VARCHAR(255) DEFAULT NULL, + `page_link_url` TEXT, + `page_icon` VARCHAR(255) DEFAULT NULL, + `page_title` VARCHAR(255) NOT NULL, + `page_description` TEXT, + `page_keywords` TEXT, + `page_is_active` TINYINT UNSIGNED NOT NULL DEFAULT '1', + `page_is_link_visible` TINYINT UNSIGNED NOT NULL DEFAULT '1', + `page_attr_target` ENUM('_self','_blank') DEFAULT '_self', + `page_attr_rel` VARCHAR(255) DEFAULT NULL, + `page_sort_display` INT UNSIGNED DEFAULT NULL, + `page_internal` VARCHAR(255) DEFAULT NULL, + `page_code` TEXT, PRIMARY KEY (`page_id`), UNIQUE KEY `page_internal` (`page_internal`(191)), KEY `page_url_key` (`page_url_key`), diff --git a/app/schemas/mysql-5/queues.sql b/app/schemas/mysql-5/queues.sql index c263a09..b47c439 100644 --- a/app/schemas/mysql-5/queues.sql +++ b/app/schemas/mysql-5/queues.sql @@ -1,11 +1,11 @@ DROP TABLE IF EXISTS `%table_prefix%queues`; CREATE TABLE `%table_prefix%queues` ( - `queue_id` bigint(32) NOT NULL AUTO_INCREMENT, - `queue_type` enum('storage-delete') NOT NULL, - `queue_date_gmt` datetime NOT NULL, - `queue_args` text NOT NULL, - `queue_join` bigint(32) NOT NULL, - `queue_attempts` varchar(255) DEFAULT '0', - `queue_status` enum('pending','failed') NOT NULL DEFAULT 'pending', + `queue_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `queue_type` ENUM('storage-delete') NOT NULL, + `queue_date_gmt` DATETIME NOT NULL, + `queue_args` TEXT NOT NULL, + `queue_join` INT UNSIGNED NOT NULL, + `queue_attempts` VARCHAR(255) DEFAULT '0', + `queue_status` ENUM('pending','failed') NOT NULL DEFAULT 'pending', PRIMARY KEY (`queue_id`) ) ENGINE=%table_engine% DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC; diff --git a/app/schemas/mysql-5/requests.sql b/app/schemas/mysql-5/requests.sql index 3814600..83c3579 100644 --- a/app/schemas/mysql-5/requests.sql +++ b/app/schemas/mysql-5/requests.sql @@ -1,18 +1,20 @@ DROP TABLE IF EXISTS `%table_prefix%requests`; CREATE TABLE `%table_prefix%requests` ( - `request_id` bigint(32) NOT NULL AUTO_INCREMENT, - `request_type` enum('upload','signup','account-edit','account-password-forgot','account-password-reset','account-resend-activation','account-email-needed','account-change-email','account-activate','login', 'content-password', 'account-two-factor') NOT NULL, - `request_user_id` bigint(32) DEFAULT NULL, - `request_content_id` bigint(32) DEFAULT NULL, - `request_ip` varchar(255) NOT NULL, - `request_date` datetime NOT NULL, - `request_date_gmt` datetime NOT NULL, - `request_result` enum('success','fail') NOT NULL, + `request_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `request_type` ENUM('upload','signup','account-edit','account-password-forgot','account-password-reset','account-resend-activation','account-email-needed','account-change-email','account-activate','login', 'content-password', 'account-two-factor') NOT NULL, + `request_user_id` INT UNSIGNED DEFAULT NULL, + `request_content_id` INT UNSIGNED DEFAULT NULL, + `request_ip` VARCHAR(255) NOT NULL, + `request_date` DATETIME NOT NULL, + `request_date_gmt` DATETIME NOT NULL, + `request_result` ENUM('success','fail') NOT NULL, PRIMARY KEY (`request_id`), KEY `request_type` (`request_type`), KEY `request_user_id` (`request_user_id`), KEY `request_content_id` (`request_content_id`), KEY `request_ip` (`request_ip`), KEY `request_date_gmt` (`request_date_gmt`), - KEY `request_result` (`request_result`) + KEY `request_result` (`request_result`), + KEY `request_result_ip_type_date_gmt` (`request_result`, `request_type`, `request_ip`,`request_date_gmt`), + KEY `request_user_id_result_type_ip` (`request_user_id`, `request_result`, `request_type`, `request_ip`) ) ENGINE=%table_engine% DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC; diff --git a/app/schemas/mysql-5/settings.sql b/app/schemas/mysql-5/settings.sql index e5ba0bb..17f17f6 100644 --- a/app/schemas/mysql-5/settings.sql +++ b/app/schemas/mysql-5/settings.sql @@ -1,10 +1,10 @@ DROP TABLE IF EXISTS `%table_prefix%settings`; CREATE TABLE `%table_prefix%settings` ( - `setting_id` int(11) NOT NULL AUTO_INCREMENT, - `setting_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL, - `setting_value` text, - `setting_default` text, - `setting_typeset` enum('string','bool') DEFAULT 'string', + `setting_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `setting_name` VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL, + `setting_value` TEXT, + `setting_default` TEXT, + `setting_typeset` ENUM('string','bool') DEFAULT 'string', PRIMARY KEY (`setting_id`), UNIQUE KEY `setting_name` (`setting_name`) USING BTREE ) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; diff --git a/app/schemas/mysql-5/stats.sql b/app/schemas/mysql-5/stats.sql index fca14da..1c14c91 100644 --- a/app/schemas/mysql-5/stats.sql +++ b/app/schemas/mysql-5/stats.sql @@ -1,21 +1,22 @@ DROP TABLE IF EXISTS `%table_prefix%stats`; CREATE TABLE `%table_prefix%stats` ( - `stat_id` bigint(32) NOT NULL AUTO_INCREMENT, - `stat_type` enum('total','date') NOT NULL, + `stat_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `stat_type` ENUM('total','date') NOT NULL, `stat_date_gmt` date DEFAULT NULL, - `stat_users` bigint(32) UNSIGNED NOT NULL DEFAULT '0', - `stat_images` bigint(32) UNSIGNED NOT NULL DEFAULT '0', - `stat_albums` bigint(32) UNSIGNED NOT NULL DEFAULT '0', - `stat_tags` bigint(32) UNSIGNED NOT NULL DEFAULT '0', - `stat_cron_runs` bigint(32) UNSIGNED NOT NULL DEFAULT '0', - `stat_cron_time` bigint(32) UNSIGNED NOT NULL DEFAULT '0', - `stat_image_views` bigint(32) UNSIGNED NOT NULL DEFAULT '0', - `stat_album_views` bigint(32) UNSIGNED NOT NULL DEFAULT '0', - `stat_image_likes` bigint(32) UNSIGNED NOT NULL DEFAULT '0', - `stat_album_likes` bigint(32) UNSIGNED NOT NULL DEFAULT '0', - `stat_disk_used` bigint(32) UNSIGNED NOT NULL DEFAULT '0', + `stat_users` INT UNSIGNED NOT NULL DEFAULT '0', + `stat_images` INT UNSIGNED NOT NULL DEFAULT '0', + `stat_albums` INT UNSIGNED NOT NULL DEFAULT '0', + `stat_tags` INT UNSIGNED NOT NULL DEFAULT '0', + `stat_cron_runs` INT UNSIGNED NOT NULL DEFAULT '0', + `stat_cron_time` INT UNSIGNED NOT NULL DEFAULT '0', + `stat_image_views` INT UNSIGNED NOT NULL DEFAULT '0', + `stat_album_views` INT UNSIGNED NOT NULL DEFAULT '0', + `stat_image_likes` INT UNSIGNED NOT NULL DEFAULT '0', + `stat_album_likes` INT UNSIGNED NOT NULL DEFAULT '0', + `stat_disk_used` BIGINT UNSIGNED NOT NULL DEFAULT '0', PRIMARY KEY (`stat_id`), UNIQUE KEY `stat_date_gmt` (`stat_date_gmt`) USING BTREE, - KEY `stat_type` (`stat_type`) + KEY `stat_type` (`stat_type`), + KEY `stat_type_date_gmt` (`stat_type`, `stat_date_gmt` DESC) ) ENGINE=%table_engine% DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC; INSERT INTO `%table_prefix%stats` (stat_id, stat_type) VALUES (1, 'total'); diff --git a/app/schemas/mysql-5/storage_apis.sql b/app/schemas/mysql-5/storage_apis.sql index b917bd4..7541310 100644 --- a/app/schemas/mysql-5/storage_apis.sql +++ b/app/schemas/mysql-5/storage_apis.sql @@ -1,8 +1,8 @@ DROP TABLE IF EXISTS `%table_prefix%storage_apis`; CREATE TABLE `%table_prefix%storage_apis` ( - `storage_api_id` bigint(32) NOT NULL AUTO_INCREMENT, - `storage_api_name` varchar(255) NOT NULL, - `storage_api_type` varchar(255) NOT NULL, + `storage_api_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `storage_api_name` VARCHAR(255) NOT NULL, + `storage_api_type` VARCHAR(255) NOT NULL, PRIMARY KEY (`storage_api_id`) ) ENGINE=%table_engine% DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC; INSERT INTO `%table_prefix%storage_apis` VALUES ('1', 'Amazon S3', 's3'); diff --git a/app/schemas/mysql-5/storages.sql b/app/schemas/mysql-5/storages.sql index 5e0c32a..ed22227 100644 --- a/app/schemas/mysql-5/storages.sql +++ b/app/schemas/mysql-5/storages.sql @@ -1,23 +1,23 @@ DROP TABLE IF EXISTS `%table_prefix%storages`; CREATE TABLE `%table_prefix%storages` ( - `storage_id` bigint(32) NOT NULL AUTO_INCREMENT, - `storage_api_id` bigint(32) NOT NULL, - `storage_name` varchar(255) NOT NULL, - `storage_service` varchar(255) DEFAULT NULL, - `storage_url` varchar(255) NOT NULL, - `storage_bucket` varchar(255) DEFAULT NULL, - `storage_region` varchar(255) DEFAULT NULL, - `storage_server` varchar(255) DEFAULT NULL, - `storage_account_id` varchar(255) DEFAULT NULL, - `storage_account_name` varchar(255) DEFAULT NULL, - `storage_key` text, - `storage_secret` text, - `storage_is_https` tinyint(1) NOT NULL DEFAULT '0', - `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', - `storage_use_path_style_endpoint` tinyint(1) NOT NULL DEFAULT '0', + `storage_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `storage_api_id` INT UNSIGNED NOT NULL, + `storage_name` VARCHAR(255) NOT NULL, + `storage_service` VARCHAR(255) DEFAULT NULL, + `storage_url` VARCHAR(255) NOT NULL, + `storage_bucket` VARCHAR(255) DEFAULT NULL, + `storage_region` VARCHAR(255) DEFAULT NULL, + `storage_server` VARCHAR(255) DEFAULT NULL, + `storage_account_id` VARCHAR(255) DEFAULT NULL, + `storage_account_name` VARCHAR(255) DEFAULT NULL, + `storage_key` TEXT, + `storage_secret` TEXT, + `storage_is_https` TINYINT UNSIGNED NOT NULL DEFAULT '0', + `storage_is_active` TINYINT UNSIGNED NOT NULL DEFAULT '0', + `storage_capacity` BIGINT UNSIGNED DEFAULT NULL, + `storage_space_used` BIGINT UNSIGNED DEFAULT '0', + `storage_type_chain` TINYINT UNSIGNED NOT NULL DEFAULT '1', + `storage_use_path_style_endpoint` TINYINT UNSIGNED NOT NULL DEFAULT '0', `storage_deleted_at` DATETIME NULL DEFAULT NULL, PRIMARY KEY (`storage_id`), KEY `storage_api_id` (`storage_api_id`), diff --git a/app/schemas/mysql-5/tags.sql b/app/schemas/mysql-5/tags.sql index de2c74f..72ed46a 100644 --- a/app/schemas/mysql-5/tags.sql +++ b/app/schemas/mysql-5/tags.sql @@ -1,12 +1,12 @@ DROP TABLE IF EXISTS `%table_prefix%tags`; CREATE TABLE `%table_prefix%tags` ( - `tag_id` bigint(32) NOT NULL AUTO_INCREMENT, - `tag_name` varchar(32) COLLATE utf8mb4_bin NOT NULL, - `tag_description` text, - `tag_user_id` bigint(32) NOT NULL, - `tag_date_gmt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, - `tag_files` bigint(32) NOT NULL DEFAULT 0, - `tag_views` bigint(32) NOT NULL DEFAULT 0, + `tag_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `tag_name` VARCHAR(32) COLLATE utf8mb4_bin NOT NULL, + `tag_description` TEXT, + `tag_user_id` INT UNSIGNED NOT NULL, + `tag_date_gmt` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `tag_files` INT UNSIGNED NOT NULL DEFAULT 0, + `tag_views` INT UNSIGNED NOT NULL DEFAULT 0, PRIMARY KEY (`tag_id`), UNIQUE KEY `tag_name` (`tag_name`) USING BTREE, KEY `tag_user_id` (`tag_user_id`), @@ -15,5 +15,6 @@ CREATE TABLE `%table_prefix%tags` ( KEY `tag_views` (`tag_views`), KEY `tag_user_id_date_gmt` (`tag_user_id`,`tag_date_gmt`), KEY `tag_user_id_files` (`tag_user_id`,`tag_files`), - KEY `tag_user_id_views` (`tag_user_id`,`tag_views`) + KEY `tag_user_id_views` (`tag_user_id`,`tag_views`), + KEY `tag_files_name` (`tag_files` DESC, `tag_name` ASC) ) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; diff --git a/app/schemas/mysql-5/tags_albums.sql b/app/schemas/mysql-5/tags_albums.sql index 4e88991..66222ea 100644 --- a/app/schemas/mysql-5/tags_albums.sql +++ b/app/schemas/mysql-5/tags_albums.sql @@ -1,10 +1,10 @@ DROP TABLE IF EXISTS `%table_prefix%tags_albums`; CREATE TABLE `%table_prefix%tags_albums` ( - `tag_album_tag_id` bigint(32) NOT NULL, - `tag_album_album_id` bigint(32) NOT NULL, - `tag_album_user_id` bigint(32) NOT NULL, - `tag_album_count` int(11) NOT NULL DEFAULT 0, - `tag_album_last_used_datetime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `tag_album_tag_id` INT UNSIGNED NOT NULL, + `tag_album_album_id` INT UNSIGNED NOT NULL, + `tag_album_user_id` INT UNSIGNED NOT NULL, + `tag_album_count` INT UNSIGNED NOT NULL DEFAULT 0, + `tag_album_last_used_datetime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (tag_album_tag_id) REFERENCES `%table_prefix%tags` (tag_id) ON DELETE CASCADE, FOREIGN KEY (tag_album_album_id) REFERENCES `%table_prefix%albums` (album_id) ON DELETE CASCADE, FOREIGN KEY (tag_album_user_id) REFERENCES `%table_prefix%users` (user_id) ON DELETE CASCADE, diff --git a/app/schemas/mysql-5/tags_files.sql b/app/schemas/mysql-5/tags_files.sql index aff4f11..d3755ae 100644 --- a/app/schemas/mysql-5/tags_files.sql +++ b/app/schemas/mysql-5/tags_files.sql @@ -1,7 +1,7 @@ DROP TABLE IF EXISTS `%table_prefix%tags_files`; CREATE TABLE `%table_prefix%tags_files` ( - `tag_file_tag_id` bigint(32) NOT NULL, - `tag_file_file_id` bigint(32) NOT NULL, + `tag_file_tag_id` INT UNSIGNED NOT NULL, + `tag_file_file_id` INT UNSIGNED NOT NULL, FOREIGN KEY (tag_file_tag_id) REFERENCES `%table_prefix%tags` (tag_id) ON DELETE CASCADE, FOREIGN KEY (tag_file_file_id) REFERENCES `%table_prefix%images` (image_id) ON DELETE CASCADE, UNIQUE INDEX `tag_file_UNIQUE` (`tag_file_tag_id` ASC, `tag_file_file_id` ASC) diff --git a/app/schemas/mysql-5/tags_users.sql b/app/schemas/mysql-5/tags_users.sql index 3e431a4..db0bb84 100644 --- a/app/schemas/mysql-5/tags_users.sql +++ b/app/schemas/mysql-5/tags_users.sql @@ -1,9 +1,9 @@ DROP TABLE IF EXISTS `%table_prefix%tags_users`; CREATE TABLE `%table_prefix%tags_users` ( - `tag_user_tag_id` bigint(32) NOT NULL, - `tag_user_user_id` bigint(32) NOT NULL, - `tag_user_count` int(11) NOT NULL DEFAULT 0, - `tag_user_last_used_datetime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `tag_user_tag_id` INT UNSIGNED NOT NULL, + `tag_user_user_id` INT UNSIGNED NOT NULL, + `tag_user_count` INT UNSIGNED NOT NULL DEFAULT 0, + `tag_user_last_used_datetime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (tag_user_tag_id) REFERENCES `%table_prefix%tags` (tag_id) ON DELETE CASCADE, FOREIGN KEY (tag_user_user_id) REFERENCES `%table_prefix%users` (user_id) ON DELETE CASCADE, UNIQUE INDEX `tag_user_UNIQUE` (`tag_user_tag_id` ASC, `tag_user_user_id` ASC), diff --git a/app/schemas/mysql-5/two_factors.sql b/app/schemas/mysql-5/two_factors.sql index 4fad5f5..2f98a88 100644 --- a/app/schemas/mysql-5/two_factors.sql +++ b/app/schemas/mysql-5/two_factors.sql @@ -1,9 +1,9 @@ DROP TABLE IF EXISTS `%table_prefix%two_factors`; CREATE TABLE `%table_prefix%two_factors` ( - `two_factor_id` bigint(32) NOT NULL AUTO_INCREMENT, - `two_factor_user_id` bigint(32) DEFAULT NULL, - `two_factor_date_gmt` datetime NOT NULL, - `two_factor_secret` text NOT NULL, + `two_factor_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `two_factor_user_id` INT UNSIGNED DEFAULT NULL, + `two_factor_date_gmt` DATETIME NOT NULL, + `two_factor_secret` TEXT NOT NULL, PRIMARY KEY (`two_factor_id`), KEY `two_factor_user_id` (`two_factor_user_id`), KEY `two_factor_date_gmt` (`two_factor_date_gmt`) diff --git a/app/schemas/mysql-5/uploads.sql b/app/schemas/mysql-5/uploads.sql new file mode 100644 index 0000000..f307cfc --- /dev/null +++ b/app/schemas/mysql-5/uploads.sql @@ -0,0 +1,17 @@ +DROP TABLE IF EXISTS `%table_prefix%uploads`; +CREATE TABLE `%table_prefix%uploads` ( + `upload_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `upload_user_id` INT UNSIGNED DEFAULT NULL, + `upload_uploader_ip` VARCHAR(255) NOT NULL, + `upload_token` VARCHAR(64) NOT NULL, + `upload_checksum` VARCHAR(32) NOT NULL, + `upload_params` JSON NOT NULL, + `upload_chunks` INT UNSIGNED NOT NULL, + `upload_date_gmt` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `upload_completed` TINYINT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`upload_id`), + KEY `upload_id_token` (`upload_id`, `upload_token`), + KEY `upload_id_token_user_id` (`upload_id`, `upload_token`, `upload_user_id`), + KEY `upload_date_gmt` (`upload_date_gmt`), + KEY `upload_uploader_ip_date_gmt_checksum` (`upload_uploader_ip`, `upload_date_gmt`, `upload_checksum`) +) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; diff --git a/app/schemas/mysql-5/uploads_chunks.sql b/app/schemas/mysql-5/uploads_chunks.sql new file mode 100644 index 0000000..77e7f37 --- /dev/null +++ b/app/schemas/mysql-5/uploads_chunks.sql @@ -0,0 +1,10 @@ +DROP TABLE IF EXISTS `%table_prefix%uploads_chunks`; +CREATE TABLE `%table_prefix%uploads_chunks` ( + `upload_chunk_upload_id` INT UNSIGNED NOT NULL, + `upload_chunk_index` INT UNSIGNED NOT NULL, + `upload_chunk_path` VARCHAR(4096) DEFAULT NULL, + `upload_chunk_date_gmt` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`upload_chunk_upload_id`, `upload_chunk_index`), + FOREIGN KEY (upload_chunk_upload_id) REFERENCES `%table_prefix%uploads` (upload_id) ON DELETE CASCADE, + KEY `upload_chunk_date_gmt` (`upload_chunk_date_gmt`) +) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; diff --git a/app/schemas/mysql-5/users.sql b/app/schemas/mysql-5/users.sql index 4709894..75ac6ae 100644 --- a/app/schemas/mysql-5/users.sql +++ b/app/schemas/mysql-5/users.sql @@ -1,38 +1,38 @@ DROP TABLE IF EXISTS `%table_prefix%users`; CREATE TABLE `%table_prefix%users` ( - `user_id` bigint(32) NOT NULL AUTO_INCREMENT, - `user_name` varchar(255) DEFAULT NULL, - `user_username` varchar(255) NOT NULL, - `user_date` datetime NOT NULL, - `user_date_gmt` datetime NOT NULL, - `user_email` varchar(255) DEFAULT NULL, - `user_avatar_filename` varchar(255) DEFAULT NULL, - `user_facebook_username` varchar(255) DEFAULT NULL, - `user_twitter_username` varchar(255) DEFAULT NULL, - `user_website` varchar(255) DEFAULT NULL, - `user_background_filename` varchar(255) DEFAULT NULL, - `user_bio` varchar(255) DEFAULT NULL, - `user_timezone` varchar(255) NOT NULL, - `user_language` varchar(255) DEFAULT NULL, - `user_status` enum('valid','awaiting-confirmation','awaiting-email','banned') NOT NULL, - `user_is_admin` tinyint(1) NOT NULL DEFAULT '0', - `user_is_manager` tinyint(1) NOT NULL DEFAULT '0', - `user_is_private` tinyint(1) NOT NULL DEFAULT '0', - `user_palette_id` int(11) NOT NULL DEFAULT '0', - `user_newsletter_subscribe` tinyint(1) NOT NULL DEFAULT '1', - `user_show_nsfw_listings` tinyint(1) NOT NULL DEFAULT '0', - `user_image_count` bigint(32) NOT NULL DEFAULT '0', - `user_album_count` bigint(32) NOT NULL DEFAULT '0', - `user_image_keep_exif` tinyint(1) NOT NULL DEFAULT '1', - `user_file_meta_tag_camera_model` tinyint(1) NOT NULL DEFAULT '1', - `user_image_expiration` varchar(255) DEFAULT NULL, - `user_registration_ip` varchar(255) NOT NULL, - `user_likes` bigint(32) NOT NULL DEFAULT '0' COMMENT 'Likes made to content owned by this user', - `user_liked` bigint(32) NOT NULL DEFAULT '0' COMMENT 'Likes made by this user', - `user_following` bigint(32) NOT NULL DEFAULT '0', - `user_followers` bigint(32) NOT NULL DEFAULT '0', - `user_content_views` bigint(32) NOT NULL DEFAULT '0', - `user_notifications_unread` bigint(32) NOT NULL DEFAULT '0', + `user_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `user_name` VARCHAR(255) DEFAULT NULL, + `user_username` VARCHAR(255) NOT NULL, + `user_date` DATETIME NOT NULL, + `user_date_gmt` DATETIME NOT NULL, + `user_email` VARCHAR(255) DEFAULT NULL, + `user_avatar_filename` VARCHAR(255) DEFAULT NULL, + `user_facebook_username` VARCHAR(255) DEFAULT NULL, + `user_twitter_username` VARCHAR(255) DEFAULT NULL, + `user_website` VARCHAR(255) DEFAULT NULL, + `user_background_filename` VARCHAR(255) DEFAULT NULL, + `user_bio` VARCHAR(255) DEFAULT NULL, + `user_timezone` VARCHAR(255) NOT NULL, + `user_language` VARCHAR(255) DEFAULT NULL, + `user_status` ENUM('valid','awaiting-confirmation','awaiting-email','banned') NOT NULL, + `user_is_admin` TINYINT UNSIGNED NOT NULL DEFAULT '0', + `user_is_manager` TINYINT UNSIGNED NOT NULL DEFAULT '0', + `user_is_private` TINYINT UNSIGNED NOT NULL DEFAULT '0', + `user_palette_id` INT UNSIGNED NOT NULL DEFAULT '0', + `user_newsletter_subscribe` TINYINT UNSIGNED NOT NULL DEFAULT '1', + `user_show_nsfw_listings` TINYINT UNSIGNED NOT NULL DEFAULT '0', + `user_image_count` INT UNSIGNED NOT NULL DEFAULT '0', + `user_album_count` INT UNSIGNED NOT NULL DEFAULT '0', + `user_image_keep_exif` TINYINT UNSIGNED NOT NULL DEFAULT '1', + `user_file_meta_tag_camera_model` TINYINT UNSIGNED NOT NULL DEFAULT '1', + `user_image_expiration` VARCHAR(255) DEFAULT NULL, + `user_registration_ip` VARCHAR(255) NOT NULL, + `user_likes` INT UNSIGNED NOT NULL DEFAULT '0' COMMENT 'Likes made to content owned by this user', + `user_liked` INT UNSIGNED NOT NULL DEFAULT '0' COMMENT 'Likes made by this user', + `user_following` INT UNSIGNED NOT NULL DEFAULT '0', + `user_followers` INT UNSIGNED NOT NULL DEFAULT '0', + `user_content_views` INT UNSIGNED NOT NULL DEFAULT '0', + `user_notifications_unread` INT UNSIGNED NOT NULL DEFAULT '0', PRIMARY KEY (`user_id`), UNIQUE KEY `username` (`user_username`(191)) USING BTREE, UNIQUE KEY `email` (`user_email`(191)) USING BTREE, diff --git a/app/schemas/mysql-5/variables.sql b/app/schemas/mysql-5/variables.sql index c150d39..4c68a06 100644 --- a/app/schemas/mysql-5/variables.sql +++ b/app/schemas/mysql-5/variables.sql @@ -1,10 +1,10 @@ DROP TABLE IF EXISTS `%table_prefix%variables`; CREATE TABLE `%table_prefix%variables` ( - `variable_id` int(11) NOT NULL AUTO_INCREMENT, - `variable_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL, - `variable_datetime_utc` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `variable_value` text, - `variable_type` enum('string','bool','int','float','array','object') DEFAULT 'string', + `variable_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `variable_name` VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL, + `variable_datetime_utc` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `variable_value` TEXT, + `variable_type` ENUM('string','bool','int','float','array','object') DEFAULT 'string', PRIMARY KEY (`variable_id`), UNIQUE KEY `variable_name` (`variable_name`) USING BTREE ) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; diff --git a/app/schemas/mysql-8/albums.sql b/app/schemas/mysql-8/albums.sql index d6e9f5c..fc9acd4 100644 --- a/app/schemas/mysql-8/albums.sql +++ b/app/schemas/mysql-8/albums.sql @@ -1,22 +1,22 @@ DROP TABLE IF EXISTS `%table_prefix%albums`; CREATE TABLE `%table_prefix%albums` ( - `album_id` bigint(32) NOT NULL AUTO_INCREMENT, - `album_name` varchar(100) NOT NULL, - `album_user_id` bigint(32) DEFAULT NULL, - `album_date` datetime NOT NULL, - `album_date_gmt` datetime NOT NULL, - `album_creation_ip` varchar(255) NOT NULL, - `album_privacy` enum('public','password','private','private_but_link','custom') DEFAULT 'public', - `album_privacy_extra` text, - `album_password` text, - `album_image_count` bigint(32) NOT NULL DEFAULT '0', - `album_description` text, - `album_likes` bigint(32) NOT NULL DEFAULT '0', - `album_views` bigint(32) NOT NULL DEFAULT '0', - `album_cover_id` bigint(32) DEFAULT NULL, - `album_parent_id` bigint(32) DEFAULT NULL, - `album_cta_enable` tinyint(1) NOT NULL DEFAULT '0', - `album_cta` text, + `album_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `album_name` VARCHAR(100) NOT NULL, + `album_user_id` INT UNSIGNED DEFAULT NULL, + `album_date` DATETIME NOT NULL, + `album_date_gmt` DATETIME NOT NULL, + `album_creation_ip` VARCHAR(255) NOT NULL, + `album_privacy` ENUM('public','password','private','private_but_link','custom') DEFAULT 'public', + `album_privacy_extra` TEXT, + `album_password` TEXT, + `album_image_count` INT UNSIGNED NOT NULL DEFAULT '0', + `album_description` TEXT, + `album_likes` INT UNSIGNED NOT NULL DEFAULT '0', + `album_views` INT UNSIGNED NOT NULL DEFAULT '0', + `album_cover_id` INT UNSIGNED DEFAULT NULL, + `album_parent_id` INT UNSIGNED DEFAULT NULL, + `album_cta_enable` TINYINT UNSIGNED NOT NULL DEFAULT '0', + `album_cta` TEXT, PRIMARY KEY (`album_id`), KEY `album_name` (`album_name`), KEY `album_user_id` (`album_user_id`), @@ -27,5 +27,6 @@ CREATE TABLE `%table_prefix%albums` ( KEY `album_likes` (`album_likes`), KEY `album_views` (`album_views`), KEY `album_parent_id` (`album_parent_id`), + KEY `album_user_id_parent_id_name` (`album_user_id`, `album_parent_id`, `album_name`), FULLTEXT KEY `searchindex` (`album_name`,`album_description`) ) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4; diff --git a/app/schemas/mysql-8/api_keys.sql b/app/schemas/mysql-8/api_keys.sql index 717a879..638671f 100644 --- a/app/schemas/mysql-8/api_keys.sql +++ b/app/schemas/mysql-8/api_keys.sql @@ -1,10 +1,10 @@ DROP TABLE IF EXISTS `%table_prefix%api_keys`; CREATE TABLE `%table_prefix%api_keys` ( - `api_key_id` bigint(32) NOT NULL AUTO_INCREMENT, - `api_key_user_id` bigint(32) DEFAULT NULL, - `api_key_name` varchar(100) DEFAULT NULL, - `api_key_date_gmt` datetime NOT NULL, - `api_key_hash` text NOT NULL, + `api_key_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `api_key_user_id` INT UNSIGNED DEFAULT NULL, + `api_key_name` VARCHAR(100) DEFAULT NULL, + `api_key_date_gmt` DATETIME NOT NULL, + `api_key_hash` TEXT NOT NULL, PRIMARY KEY (`api_key_id`), KEY `api_key_user_id` (`api_key_user_id`), KEY `api_key_name` (`api_key_name`), diff --git a/app/schemas/mysql-8/assets.sql b/app/schemas/mysql-8/assets.sql index 0072565..c3e8348 100644 --- a/app/schemas/mysql-8/assets.sql +++ b/app/schemas/mysql-8/assets.sql @@ -1,10 +1,10 @@ DROP TABLE IF EXISTS `%table_prefix%assets`; CREATE TABLE `%table_prefix%assets` ( - `asset_id` bigint(32) NOT NULL AUTO_INCREMENT, - `asset_key` varchar(255) NOT NULL, - `asset_md5` varchar(32) NOT NULL, - `asset_filename` varchar(255) NOT NULL, - `asset_file_path` varchar(255) NOT NULL, + `asset_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `asset_key` VARCHAR(255) NOT NULL, + `asset_checksum` VARCHAR(32) NOT NULL, + `asset_filename` VARCHAR(255) NOT NULL, + `asset_file_path` VARCHAR(255) NOT NULL, `asset_blob` blob, PRIMARY KEY (`asset_id`), UNIQUE KEY `key` (`asset_key`) USING BTREE, diff --git a/app/schemas/mysql-8/categories.sql b/app/schemas/mysql-8/categories.sql index 323b3ad..320d431 100644 --- a/app/schemas/mysql-8/categories.sql +++ b/app/schemas/mysql-8/categories.sql @@ -1,9 +1,10 @@ DROP TABLE IF EXISTS `%table_prefix%categories`; CREATE TABLE `%table_prefix%categories` ( - `category_id` bigint(32) NOT NULL AUTO_INCREMENT, - `category_name` varchar(32) NOT NULL, - `category_url_key` varchar(32) COLLATE utf8mb4_bin NOT NULL, - `category_description` text, + `category_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `category_name` VARCHAR(32) NOT NULL, + `category_url_key` VARCHAR(32) COLLATE utf8mb4_bin NOT NULL, + `category_description` TEXT, PRIMARY KEY (`category_id`), + KEY `category_name` (`category_name`), UNIQUE KEY `url_key` (`category_url_key`) USING BTREE ) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4; diff --git a/app/schemas/mysql-8/confirmations.sql b/app/schemas/mysql-8/confirmations.sql index 10d481c..ded8766 100644 --- a/app/schemas/mysql-8/confirmations.sql +++ b/app/schemas/mysql-8/confirmations.sql @@ -1,13 +1,13 @@ DROP TABLE IF EXISTS `%table_prefix%confirmations`; CREATE TABLE `%table_prefix%confirmations` ( - `confirmation_id` bigint(32) NOT NULL AUTO_INCREMENT, - `confirmation_user_id` bigint(32) NOT NULL, - `confirmation_type` enum('account-activate','account-change-email','account-password-forgot') NOT NULL, - `confirmation_date` datetime NOT NULL, - `confirmation_date_gmt` datetime NOT NULL, - `confirmation_token_hash` varchar(255) NOT NULL, - `confirmation_status` enum('active','valid','invalid') NOT NULL, - `confirmation_extra` text, + `confirmation_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `confirmation_user_id` INT UNSIGNED NOT NULL, + `confirmation_type` ENUM('account-activate','account-change-email','account-password-forgot') NOT NULL, + `confirmation_date` DATETIME NOT NULL, + `confirmation_date_gmt` DATETIME NOT NULL, + `confirmation_token_hash` VARCHAR(255) NOT NULL, + `confirmation_status` ENUM('active','valid','invalid') NOT NULL, + `confirmation_extra` TEXT, PRIMARY KEY (`confirmation_id`), KEY `confirmation_user` (`confirmation_user_id`), KEY `confirmation_user_type` (`confirmation_user_id`, `confirmation_type`), diff --git a/app/schemas/mysql-8/deletions.sql b/app/schemas/mysql-8/deletions.sql index b5b732a..e74f09b 100644 --- a/app/schemas/mysql-8/deletions.sql +++ b/app/schemas/mysql-8/deletions.sql @@ -1,20 +1,20 @@ DROP TABLE IF EXISTS `%table_prefix%deletions`; CREATE TABLE `%table_prefix%deletions` ( - `deleted_id` bigint(32) NOT NULL AUTO_INCREMENT, - `deleted_date_gmt` datetime NOT NULL, - `deleted_content_id` bigint(32) NOT NULL, - `deleted_content_date_gmt` datetime NOT NULL, - `deleted_content_user_id` bigint(32) DEFAULT NULL, - `deleted_content_ip` varchar(255) NOT NULL, - `deleted_content_md5` varchar(32) DEFAULT NULL, - `deleted_content_original_filename` varchar(255) DEFAULT NULL, - `deleted_content_views` bigint(32) NOT NULL DEFAULT '0', - `deleted_content_likes` bigint(32) NOT NULL DEFAULT '0', + `deleted_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `deleted_date_gmt` DATETIME NOT NULL, + `deleted_content_id` INT UNSIGNED NOT NULL, + `deleted_content_date_gmt` DATETIME NOT NULL, + `deleted_content_user_id` INT UNSIGNED DEFAULT NULL, + `deleted_content_ip` VARCHAR(255) NOT NULL, + `deleted_content_checksum` VARCHAR(32) DEFAULT NULL, + `deleted_content_original_filename` VARCHAR(255) DEFAULT NULL, + `deleted_content_views` INT UNSIGNED NOT NULL DEFAULT '0', + `deleted_content_likes` INT UNSIGNED NOT NULL DEFAULT '0', PRIMARY KEY (`deleted_id`), KEY `deleted_content_id` (`deleted_content_id`), KEY `deleted_content_user_id` (`deleted_content_user_id`), KEY `deleted_content_ip` (`deleted_content_ip`), - KEY `deleted_content_md5` (`deleted_content_md5`), + KEY `deleted_content_checksum` (`deleted_content_checksum`), KEY `deleted_content_views` (`deleted_content_views`), KEY `deleted_content_likes` (`deleted_content_likes`) ) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4; diff --git a/app/schemas/mysql-8/follows.sql b/app/schemas/mysql-8/follows.sql index 211af22..bfa7172 100644 --- a/app/schemas/mysql-8/follows.sql +++ b/app/schemas/mysql-8/follows.sql @@ -1,12 +1,12 @@ DROP TABLE IF EXISTS `%table_prefix%follows`; CREATE TABLE `%table_prefix%follows` ( - `follow_id` bigint(32) NOT NULL AUTO_INCREMENT, - `follow_date` datetime NOT NULL, - `follow_date_gmt` datetime NOT NULL, - `follow_user_id` bigint(32) NOT NULL, - `follow_followed_user_id` bigint(32) NOT NULL, - `follow_ip` varchar(255) NOT NULL, + `follow_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `follow_date` DATETIME NOT NULL, + `follow_date_gmt` DATETIME NOT NULL, + `follow_user_id` INT UNSIGNED NOT NULL, + `follow_followed_user_id` INT UNSIGNED NOT NULL, + `follow_ip` VARCHAR(255) NOT NULL, PRIMARY KEY (`follow_id`), KEY `follow_user_id` (`follow_user_id`), KEY `follow_followed_user_id` (`follow_followed_user_id`) -) ENGINE=%table_engine% DEFAULT CHARSET=utf8; \ No newline at end of file +) ENGINE=%table_engine% DEFAULT CHARSET=utf8; diff --git a/app/schemas/mysql-8/images.sql b/app/schemas/mysql-8/images.sql index 40573b4..6edc1a4 100644 --- a/app/schemas/mysql-8/images.sql +++ b/app/schemas/mysql-8/images.sql @@ -1,39 +1,39 @@ DROP TABLE IF EXISTS `%table_prefix%images`; 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` bigint(11) UNSIGNED NOT NULL, - `image_width` int(11) NOT NULL, - `image_height` int(11) NOT NULL, - `image_date` datetime NOT NULL, - `image_date_gmt` datetime NOT NULL, - `image_title` varchar(100) DEFAULT NULL, - `image_description` text, - `image_nsfw` tinyint(1) NOT NULL DEFAULT '0', - `image_user_id` bigint(32) DEFAULT NULL, - `image_album_id` bigint(32) DEFAULT NULL, - `image_uploader_ip` varchar(255) NOT NULL, - `image_storage_mode` enum('datefolder','direct','old','path') NOT NULL DEFAULT 'datefolder', - `image_path` varchar(4096) DEFAULT NULL, - `image_storage_id` bigint(32) DEFAULT NULL, - `image_md5` varchar(32) NOT NULL, - `image_source_md5` varchar(32) DEFAULT NULL, - `image_original_filename` varchar(255) NOT NULL, - `image_original_exifdata` mediumtext, - `image_views` bigint(32) NOT NULL DEFAULT '0', - `image_category_id` bigint(32) DEFAULT 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 + `image_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `image_name` VARCHAR(255) NOT NULL, + `image_extension` VARCHAR(255) NOT NULL, + `image_size` BIGINT UNSIGNED NOT NULL, + `image_width` INT UNSIGNED NOT NULL, + `image_height` INT UNSIGNED NOT NULL, + `image_date` DATETIME NOT NULL, + `image_date_gmt` DATETIME NOT NULL, + `image_title` VARCHAR(100) DEFAULT NULL, + `image_description` TEXT, + `image_nsfw` TINYINT UNSIGNED NOT NULL DEFAULT '0', + `image_user_id` INT UNSIGNED DEFAULT NULL, + `image_album_id` INT UNSIGNED DEFAULT NULL, + `image_uploader_ip` VARCHAR(255) NOT NULL, + `image_storage_mode` ENUM('datefolder','direct','old','path') NOT NULL DEFAULT 'datefolder', + `image_path` VARCHAR(4096) DEFAULT NULL, + `image_storage_id` INT UNSIGNED DEFAULT NULL, + `image_checksum` VARCHAR(32) NOT NULL, + `image_source_checksum` VARCHAR(32) DEFAULT NULL, + `image_original_filename` VARCHAR(255) NOT NULL, + `image_original_exifdata` MEDIUMTEXT, + `image_views` INT UNSIGNED NOT NULL DEFAULT '0', + `image_category_id` INT UNSIGNED DEFAULT NULL, + `image_chain` TINYINT UNSIGNED NOT NULL, + `image_thumb_size` INT UNSIGNED NOT NULL, + `image_medium_size` INT UNSIGNED NOT NULL DEFAULT '0', + `image_frame_size` INT UNSIGNED NOT NULL DEFAULT '0', + `image_expiration_date_gmt` DATETIME DEFAULT NULL, + `image_likes` INT UNSIGNED NOT NULL DEFAULT '0', + `image_is_animated` TINYINT UNSIGNED NOT NULL DEFAULT '0', + `image_is_approved` TINYINT UNSIGNED NOT NULL DEFAULT '1', + `image_is_360` TINYINT UNSIGNED NOT NULL DEFAULT '0', + `image_duration` INT UNSIGNED NOT NULL DEFAULT '0', + `image_type` TINYINT 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','mov') then 2 @@ -53,8 +53,8 @@ CREATE TABLE `%table_prefix%images` ( KEY `image_storage_mode` (`image_storage_mode`), KEY `image_path` (`image_path`(255)), KEY `image_storage_id` (`image_storage_id`), - KEY `image_md5` (`image_md5`), - KEY `image_source_md5` (`image_source_md5`), + KEY `image_checksum` (`image_checksum`), + KEY `image_source_checksum` (`image_source_checksum`), KEY `image_views` (`image_views`), KEY `image_category_id` (`image_category_id`), KEY `image_chain` (`image_chain`), @@ -66,5 +66,6 @@ CREATE TABLE `%table_prefix%images` ( 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`) + FULLTEXT KEY `searchindex` (`image_name`,`image_title`,`image_description`,`image_original_filename`), + KEY `image_uploader_ip_date_gmt_checksum_source_checksum` (`image_uploader_ip`, `image_date_gmt`, `image_checksum`, `image_source_checksum`) ) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4; diff --git a/app/schemas/mysql-8/images_hash.sql b/app/schemas/mysql-8/images_hash.sql index 19e1d89..e4e00ca 100644 --- a/app/schemas/mysql-8/images_hash.sql +++ b/app/schemas/mysql-8/images_hash.sql @@ -1,6 +1,6 @@ DROP TABLE IF EXISTS `%table_prefix%images_hash`; CREATE TABLE `%table_prefix%images_hash` ( - `image_hash_image_id` bigint(32) NOT NULL, - `image_hash_hash` text NOT NULL, + `image_hash_image_id` INT UNSIGNED NOT NULL, + `image_hash_hash` TEXT NOT NULL, PRIMARY KEY (`image_hash_image_id`) ) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4; diff --git a/app/schemas/mysql-8/importing.sql b/app/schemas/mysql-8/importing.sql index 7b901bc..68a82a5 100644 --- a/app/schemas/mysql-8/importing.sql +++ b/app/schemas/mysql-8/importing.sql @@ -1,10 +1,10 @@ DROP TABLE IF EXISTS `%table_prefix%importing`; CREATE TABLE `%table_prefix%importing` ( - `importing_id` bigint(32) NOT NULL AUTO_INCREMENT, - `importing_import_id` bigint(32) NOT NULL, - `importing_path` varchar(4096) NOT NULL, - `importing_content_type` enum('user','album','image') NOT NULL, - `importing_content_id` bigint(32) DEFAULT NULL, + `importing_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `importing_import_id` INT UNSIGNED NOT NULL, + `importing_path` VARCHAR(4096) NOT NULL, + `importing_content_type` ENUM('user','album','image') NOT NULL, + `importing_content_id` INT UNSIGNED DEFAULT NULL, PRIMARY KEY (`importing_id`), UNIQUE KEY `importing_path` (`importing_path`(767)) ) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4; diff --git a/app/schemas/mysql-8/imports.sql b/app/schemas/mysql-8/imports.sql index 5745158..c6f3666 100644 --- a/app/schemas/mysql-8/imports.sql +++ b/app/schemas/mysql-8/imports.sql @@ -1,21 +1,21 @@ DROP TABLE IF EXISTS `%table_prefix%imports`; CREATE TABLE `%table_prefix%imports` ( - `import_id` bigint(32) NOT NULL AUTO_INCREMENT, - `import_path` varchar(4096) NOT NULL, - `import_options` varchar(255) DEFAULT NULL, - `import_status` enum('queued','working','paused','canceled','completed') NOT NULL, - `import_users` bigint(32) NOT NULL DEFAULT '0', - `import_images` bigint(32) NOT NULL DEFAULT '0', - `import_albums` bigint(32) NOT NULL DEFAULT '0', - `import_time_created` datetime DEFAULT NULL, - `import_time_updated` datetime DEFAULT NULL, - `import_errors` tinyint(1) NOT NULL DEFAULT '0', - `import_started` tinyint(1) NOT NULL DEFAULT '0', - `import_continuous` tinyint(1) NOT NULL DEFAULT '0', + `import_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `import_path` VARCHAR(4096) NOT NULL, + `import_options` VARCHAR(255) DEFAULT NULL, + `import_status` ENUM('queued','working','paused','canceled','completed') NOT NULL, + `import_users` INT UNSIGNED NOT NULL DEFAULT '0', + `import_images` INT UNSIGNED NOT NULL DEFAULT '0', + `import_albums` INT UNSIGNED NOT NULL DEFAULT '0', + `import_time_created` DATETIME DEFAULT NULL, + `import_time_updated` DATETIME DEFAULT NULL, + `import_errors` TINYINT UNSIGNED NOT NULL DEFAULT '0', + `import_started` TINYINT UNSIGNED NOT NULL DEFAULT '0', + `import_continuous` TINYINT UNSIGNED NOT NULL DEFAULT '0', PRIMARY KEY (`import_id`), KEY `import_path` (`import_path`(767)) USING BTREE ) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4; INSERT INTO `%table_prefix%imports` VALUES ('1', '%rootPath%importing/no-parse', 'a:1:{s:4:"root";s:5:"plain";}', 'working', '0', '0', '0', NOW(), NOW(), '0', '1', '1'); INSERT INTO `%table_prefix%imports` VALUES ('2', '%rootPath%importing/parse-users', 'a:1:{s:4:"root";s:5:"users";}', 'working', '0', '0', '0', NOW(), NOW(), '0', '1', '1'); -INSERT INTO `%table_prefix%imports` VALUES ('3', '%rootPath%importing/parse-albums', 'a:1:{s:4:"root";s:6:"albums";}', 'working', '0', '0', '0', NOW(), NOW(), '0', '1', '1'); \ No newline at end of file +INSERT INTO `%table_prefix%imports` VALUES ('3', '%rootPath%importing/parse-albums', 'a:1:{s:4:"root";s:6:"albums";}', 'working', '0', '0', '0', NOW(), NOW(), '0', '1', '1'); diff --git a/app/schemas/mysql-8/ip_bans.sql b/app/schemas/mysql-8/ip_bans.sql index dd5bf2b..f9d94c9 100644 --- a/app/schemas/mysql-8/ip_bans.sql +++ b/app/schemas/mysql-8/ip_bans.sql @@ -1,13 +1,13 @@ DROP TABLE IF EXISTS `%table_prefix%ip_bans`; CREATE TABLE `%table_prefix%ip_bans` ( - `ip_ban_id` bigint(20) NOT NULL AUTO_INCREMENT, - `ip_ban_date` datetime NOT NULL, - `ip_ban_date_gmt` datetime NOT NULL, - `ip_ban_expires` datetime DEFAULT NULL, - `ip_ban_expires_gmt` datetime DEFAULT NULL, - `ip_ban_ip` varchar(255) NOT NULL, - `ip_ban_message` text, + `ip_ban_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `ip_ban_date` DATETIME NOT NULL, + `ip_ban_date_gmt` DATETIME NOT NULL, + `ip_ban_expires` DATETIME DEFAULT NULL, + `ip_ban_expires_gmt` DATETIME DEFAULT NULL, + `ip_ban_ip` VARCHAR(255) NOT NULL, + `ip_ban_message` TEXT, PRIMARY KEY (`ip_ban_id`), - KEY `ip_ban_date_gmt` (`ip_ban_date_gmt`), - UNIQUE KEY `ip_ban_ip` (`ip_ban_ip`) USING BTREE -) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4; \ No newline at end of file + UNIQUE KEY `ip_ban_ip` (`ip_ban_ip`) USING BTREE, + KEY `ip_ban_ip_expires_gmt_id` (`ip_ban_ip`, `ip_ban_expires_gmt`, `ip_ban_id` DESC) +) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4; diff --git a/app/schemas/mysql-8/likes.sql b/app/schemas/mysql-8/likes.sql index 8d9d4b1..651bbeb 100644 --- a/app/schemas/mysql-8/likes.sql +++ b/app/schemas/mysql-8/likes.sql @@ -1,13 +1,13 @@ DROP TABLE IF EXISTS `%table_prefix%likes`; CREATE TABLE `%table_prefix%likes` ( - `like_id` bigint(32) NOT NULL AUTO_INCREMENT, - `like_date` datetime NOT NULL, - `like_date_gmt` datetime NOT NULL, - `like_user_id` bigint(32) DEFAULT NULL, - `like_content_type` enum('image','album') DEFAULT NULL, - `like_content_id` bigint(32) NOT NULL, - `like_content_user_id` bigint(32) DEFAULT NULL, - `like_ip` varchar(255) NOT NULL, + `like_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `like_date` DATETIME NOT NULL, + `like_date_gmt` DATETIME NOT NULL, + `like_user_id` INT UNSIGNED DEFAULT NULL, + `like_content_type` ENUM('image','album') DEFAULT NULL, + `like_content_id` INT UNSIGNED NOT NULL, + `like_content_user_id` INT UNSIGNED DEFAULT NULL, + `like_ip` VARCHAR(255) NOT NULL, PRIMARY KEY (`like_id`), KEY `like_date_gmt` (`like_date_gmt`), KEY `like_user_id` (`like_user_id`), diff --git a/app/schemas/mysql-8/locks.sql b/app/schemas/mysql-8/locks.sql index d9ba8e2..3fe6b70 100644 --- a/app/schemas/mysql-8/locks.sql +++ b/app/schemas/mysql-8/locks.sql @@ -1,11 +1,11 @@ DROP TABLE IF EXISTS `%table_prefix%locks`; CREATE TABLE `%table_prefix%locks` ( - `lock_id` bigint(20) NOT NULL AUTO_INCREMENT, - `lock_name` varchar(255) NOT NULL, - `lock_date_gmt` datetime NOT NULL, - `lock_expires_gmt` datetime DEFAULT NULL, + `lock_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `lock_name` VARCHAR(255) NOT NULL, + `lock_date_gmt` DATETIME NOT NULL, + `lock_expires_gmt` DATETIME DEFAULT NULL, PRIMARY KEY (`lock_id`), KEY `lock_date_gmt` (`lock_date_gmt`), KEY `lock_expires_gmt` (`lock_expires_gmt`), UNIQUE KEY `lock_name` (`lock_name`) USING BTREE -) 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/login_connections.sql b/app/schemas/mysql-8/login_connections.sql index 09e2e78..755ebff 100644 --- a/app/schemas/mysql-8/login_connections.sql +++ b/app/schemas/mysql-8/login_connections.sql @@ -1,12 +1,12 @@ DROP TABLE IF EXISTS `%table_prefix%login_connections`; CREATE TABLE `%table_prefix%login_connections` ( - `login_connection_id` bigint(32) NOT NULL AUTO_INCREMENT, - `login_connection_user_id` bigint(32) NOT NULL, - `login_connection_provider_id` bigint(32) NOT NULL, - `login_connection_date_gmt` datetime NOT NULL, - `login_connection_resource_id` varchar(255) NOT NULL, - `login_connection_resource_name` text, - `login_connection_token` text NOT NULL COMMENT 'Ciphertext', + `login_connection_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `login_connection_user_id` INT UNSIGNED NOT NULL, + `login_connection_provider_id` INT UNSIGNED NOT NULL, + `login_connection_date_gmt` DATETIME NOT NULL, + `login_connection_resource_id` VARCHAR(255) NOT NULL, + `login_connection_resource_name` TEXT, + `login_connection_token` TEXT NOT NULL COMMENT 'Ciphertext', PRIMARY KEY (`login_connection_id`), UNIQUE KEY `login_connection_unique` (`login_connection_user_id`,`login_connection_provider_id`), KEY `login_connection_user_id` (`login_connection_user_id`), diff --git a/app/schemas/mysql-8/login_cookies.sql b/app/schemas/mysql-8/login_cookies.sql index 10371c4..45fbc17 100644 --- a/app/schemas/mysql-8/login_cookies.sql +++ b/app/schemas/mysql-8/login_cookies.sql @@ -1,16 +1,17 @@ DROP TABLE IF EXISTS `%table_prefix%login_cookies`; CREATE TABLE `%table_prefix%login_cookies` ( - `login_cookie_id` bigint(32) NOT NULL AUTO_INCREMENT, - `login_cookie_user_id` bigint(32) NOT NULL, - `login_cookie_connection_id` bigint(32) DEFAULT 0, - `login_cookie_date_gmt` datetime NOT NULL, - `login_cookie_ip` varchar(255) DEFAULT NULL, - `login_cookie_user_agent` text NOT NULL, - `login_cookie_hash` text NOT NULL, + `login_cookie_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `login_cookie_user_id` INT UNSIGNED NOT NULL, + `login_cookie_connection_id` INT UNSIGNED DEFAULT 0, + `login_cookie_date_gmt` DATETIME NOT NULL, + `login_cookie_ip` VARCHAR(255) DEFAULT NULL, + `login_cookie_user_agent` TEXT NOT NULL, + `login_cookie_hash` TEXT NOT NULL, PRIMARY KEY (`login_cookie_id`), UNIQUE KEY `login_cookie_unique` (`login_cookie_user_id`,`login_cookie_connection_id`,`login_cookie_date_gmt`), KEY `login_cookie_user_id_date_gmt` (`login_cookie_user_id`, `login_cookie_date_gmt`), KEY `login_cookie_user_id` (`login_cookie_user_id`), KEY `login_cookie_ip` (`login_cookie_ip`), - KEY `login_cookie_connection_id` (`login_cookie_connection_id`) + KEY `login_cookie_connection_id` (`login_cookie_connection_id`), + KEY `login_cookie_user_id_date_gmt_connection_id` (`login_cookie_user_id`, `login_cookie_date_gmt`, `login_cookie_connection_id`) ) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4; diff --git a/app/schemas/mysql-8/login_passwords.sql b/app/schemas/mysql-8/login_passwords.sql index a353c3f..82a1ad2 100644 --- a/app/schemas/mysql-8/login_passwords.sql +++ b/app/schemas/mysql-8/login_passwords.sql @@ -1,9 +1,9 @@ DROP TABLE IF EXISTS `%table_prefix%login_passwords`; CREATE TABLE `%table_prefix%login_passwords` ( - `login_password_id` bigint(32) NOT NULL AUTO_INCREMENT, - `login_password_user_id` bigint(32) NOT NULL, - `login_password_date_gmt` datetime NOT NULL, - `login_password_hash` text NOT NULL, + `login_password_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `login_password_user_id` INT UNSIGNED NOT NULL, + `login_password_date_gmt` DATETIME NOT NULL, + `login_password_hash` TEXT NOT NULL, PRIMARY KEY (`login_password_id`), UNIQUE KEY `login_password_user_id` (`login_password_user_id`) ) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4; diff --git a/app/schemas/mysql-8/login_providers.sql b/app/schemas/mysql-8/login_providers.sql index 965c2c0..44b8b35 100644 --- a/app/schemas/mysql-8/login_providers.sql +++ b/app/schemas/mysql-8/login_providers.sql @@ -1,14 +1,15 @@ DROP TABLE IF EXISTS `%table_prefix%login_providers`; CREATE TABLE `%table_prefix%login_providers` ( - `login_provider_id` bigint(32) NOT NULL AUTO_INCREMENT, - `login_provider_name` varchar(255) DEFAULT NULL, - `login_provider_label` varchar(255) DEFAULT NULL, - `login_provider_key_id` text DEFAULT NULL, - `login_provider_key_secret` text DEFAULT NULL, - `login_provider_is_enabled` tinyint(1) NOT NULL DEFAULT '1', + `login_provider_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `login_provider_name` VARCHAR(255) DEFAULT NULL, + `login_provider_label` VARCHAR(255) DEFAULT NULL, + `login_provider_key_id` TEXT DEFAULT NULL, + `login_provider_key_secret` TEXT DEFAULT NULL, + `login_provider_is_enabled` TINYINT UNSIGNED NOT NULL DEFAULT '1', PRIMARY KEY (`login_provider_id`), UNIQUE KEY `login_provider_name` (`login_provider_name`), - KEY `login_provider_is_enabled` (`login_provider_is_enabled`) + KEY `login_provider_is_enabled` (`login_provider_is_enabled`), + KEY `login_provider_id_is_enabled_name` (`login_provider_id`, `login_provider_is_enabled`, `login_provider_name` DESC) ) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4; INSERT INTO `%table_prefix%login_providers` VALUES ('1', 'facebook', 'Facebook', null, null, '0'); INSERT INTO `%table_prefix%login_providers` VALUES ('2', 'twitter', 'X', null, null, '0'); diff --git a/app/schemas/mysql-8/logins.sql b/app/schemas/mysql-8/logins.sql index b03ce7d..b7cf588 100644 --- a/app/schemas/mysql-8/logins.sql +++ b/app/schemas/mysql-8/logins.sql @@ -1,18 +1,18 @@ DROP TABLE IF EXISTS `%table_prefix%logins`; CREATE TABLE `%table_prefix%logins` ( - `login_id` bigint(32) NOT NULL AUTO_INCREMENT, - `login_user_id` bigint(32) NOT NULL, - `login_type` enum('password','session','cookie','facebook','twitter','google','vk','cookie_facebook','cookie_twitter','cookie_google','cookie_vk') NOT NULL, - `login_ip` varchar(255) DEFAULT NULL, - `login_hostname` text, - `login_date` datetime NOT NULL, - `login_date_gmt` datetime NOT NULL, - `login_resource_id` varchar(255) DEFAULT NULL, - `login_resource_name` text, - `login_resource_avatar` text, - `login_resource_url` text, - `login_secret` text DEFAULT NULL COMMENT 'The secret part', - `login_token_hash` text COMMENT 'Hashed complement to secret if needed', + `login_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `login_user_id` INT UNSIGNED NOT NULL, + `login_type` ENUM('password','session','cookie','facebook','twitter','google','vk','cookie_facebook','cookie_twitter','cookie_google','cookie_vk') NOT NULL, + `login_ip` VARCHAR(255) DEFAULT NULL, + `login_hostname` TEXT, + `login_date` DATETIME NOT NULL, + `login_date_gmt` DATETIME NOT NULL, + `login_resource_id` VARCHAR(255) DEFAULT NULL, + `login_resource_name` TEXT, + `login_resource_avatar` TEXT, + `login_resource_url` TEXT, + `login_secret` TEXT DEFAULT NULL COMMENT 'The secret part', + `login_token_hash` TEXT COMMENT 'Hashed complement to secret if needed', PRIMARY KEY (`login_id`), KEY `login_user_id` (`login_user_id`) ) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4; diff --git a/app/schemas/mysql-8/notifications.sql b/app/schemas/mysql-8/notifications.sql index a5880c9..c035d8b 100644 --- a/app/schemas/mysql-8/notifications.sql +++ b/app/schemas/mysql-8/notifications.sql @@ -1,13 +1,13 @@ DROP TABLE IF EXISTS `%table_prefix%notifications`; CREATE TABLE `%table_prefix%notifications` ( - `notification_id` bigint(32) NOT NULL AUTO_INCREMENT, - `notification_date_gmt` datetime NOT NULL, - `notification_user_id` bigint(32) NOT NULL, - `notification_trigger_user_id` bigint(32) DEFAULT NULL, - `notification_type` enum('follow','like') NOT NULL, - `notification_content_type` enum('user','image','album') NOT NULL, - `notification_type_id` bigint(32) NOT NULL COMMENT 'type_id based on action (type) table', - `notification_is_read` tinyint(1) NOT NULL DEFAULT '0', + `notification_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `notification_date_gmt` DATETIME NOT NULL, + `notification_user_id` INT UNSIGNED NOT NULL, + `notification_trigger_user_id` INT UNSIGNED DEFAULT NULL, + `notification_type` ENUM('follow','like') NOT NULL, + `notification_content_type` ENUM('user','image','album') NOT NULL, + `notification_type_id` INT UNSIGNED NOT NULL COMMENT 'type_id based on action (type) table', + `notification_is_read` TINYINT UNSIGNED NOT NULL DEFAULT '0', PRIMARY KEY (`notification_id`), KEY `notification_date_gmt` (`notification_date_gmt`), KEY `notification_user_id` (`notification_user_id`), @@ -16,4 +16,4 @@ CREATE TABLE `%table_prefix%notifications` ( KEY `notification_content_type` (`notification_content_type`), KEY `notification_type_id` (`notification_type_id`), KEY `notification_is_read` (`notification_is_read`) -) ENGINE=%table_engine% DEFAULT CHARSET=utf8; \ No newline at end of file +) ENGINE=%table_engine% DEFAULT CHARSET=utf8; diff --git a/app/schemas/mysql-8/pages.sql b/app/schemas/mysql-8/pages.sql index 178a54a..124cdbd 100644 --- a/app/schemas/mysql-8/pages.sql +++ b/app/schemas/mysql-8/pages.sql @@ -1,21 +1,21 @@ DROP TABLE IF EXISTS `%table_prefix%pages`; CREATE TABLE `%table_prefix%pages` ( - `page_id` bigint(32) NOT NULL AUTO_INCREMENT, - `page_url_key` varchar(32) DEFAULT NULL, - `page_type` enum('internal','link') NOT NULL DEFAULT 'internal', - `page_file_path` varchar(255) DEFAULT NULL, - `page_link_url` text, - `page_icon` varchar(255) DEFAULT NULL, - `page_title` varchar(255) NOT NULL, - `page_description` text, - `page_keywords` text, - `page_is_active` tinyint(1) NOT NULL DEFAULT '1', - `page_is_link_visible` tinyint(1) NOT NULL DEFAULT '1', - `page_attr_target` enum('_self','_blank') DEFAULT '_self', - `page_attr_rel` varchar(255) DEFAULT NULL, - `page_sort_display` int(11) DEFAULT NULL, - `page_internal` varchar(255) DEFAULT NULL, - `page_code` text, + `page_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `page_url_key` VARCHAR(32) DEFAULT NULL, + `page_type` ENUM('internal','link') NOT NULL DEFAULT 'internal', + `page_file_path` VARCHAR(255) DEFAULT NULL, + `page_link_url` TEXT, + `page_icon` VARCHAR(255) DEFAULT NULL, + `page_title` VARCHAR(255) NOT NULL, + `page_description` TEXT, + `page_keywords` TEXT, + `page_is_active` TINYINT UNSIGNED NOT NULL DEFAULT '1', + `page_is_link_visible` TINYINT UNSIGNED NOT NULL DEFAULT '1', + `page_attr_target` ENUM('_self','_blank') DEFAULT '_self', + `page_attr_rel` VARCHAR(255) DEFAULT NULL, + `page_sort_display` INT UNSIGNED DEFAULT NULL, + `page_internal` VARCHAR(255) DEFAULT NULL, + `page_code` TEXT, PRIMARY KEY (`page_id`), UNIQUE KEY `page_internal` (`page_internal`), KEY `page_url_key` (`page_url_key`), diff --git a/app/schemas/mysql-8/queues.sql b/app/schemas/mysql-8/queues.sql index d55544e..75b48f5 100644 --- a/app/schemas/mysql-8/queues.sql +++ b/app/schemas/mysql-8/queues.sql @@ -1,11 +1,11 @@ DROP TABLE IF EXISTS `%table_prefix%queues`; CREATE TABLE `%table_prefix%queues` ( - `queue_id` bigint(32) NOT NULL AUTO_INCREMENT, - `queue_type` enum('storage-delete') NOT NULL, - `queue_date_gmt` datetime NOT NULL, - `queue_args` text NOT NULL, - `queue_join` bigint(32) NOT NULL, - `queue_attempts` varchar(255) DEFAULT '0', - `queue_status` enum('pending','failed') NOT NULL DEFAULT 'pending', + `queue_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `queue_type` ENUM('storage-delete') NOT NULL, + `queue_date_gmt` DATETIME NOT NULL, + `queue_args` TEXT NOT NULL, + `queue_join` INT UNSIGNED NOT NULL, + `queue_attempts` VARCHAR(255) DEFAULT '0', + `queue_status` ENUM('pending','failed') NOT NULL DEFAULT 'pending', PRIMARY KEY (`queue_id`) ) ENGINE=%table_engine% DEFAULT CHARSET=utf8; diff --git a/app/schemas/mysql-8/requests.sql b/app/schemas/mysql-8/requests.sql index f55e6e9..7e3f5a7 100644 --- a/app/schemas/mysql-8/requests.sql +++ b/app/schemas/mysql-8/requests.sql @@ -1,18 +1,20 @@ DROP TABLE IF EXISTS `%table_prefix%requests`; CREATE TABLE `%table_prefix%requests` ( - `request_id` bigint(32) NOT NULL AUTO_INCREMENT, - `request_type` enum('upload','signup','account-edit','account-password-forgot','account-password-reset','account-resend-activation','account-email-needed','account-change-email','account-activate','login', 'content-password', 'account-two-factor') NOT NULL, - `request_user_id` bigint(32) DEFAULT NULL, - `request_content_id` bigint(32) DEFAULT NULL, - `request_ip` varchar(255) NOT NULL, - `request_date` datetime NOT NULL, - `request_date_gmt` datetime NOT NULL, - `request_result` enum('success','fail') NOT NULL, + `request_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `request_type` ENUM('upload','signup','account-edit','account-password-forgot','account-password-reset','account-resend-activation','account-email-needed','account-change-email','account-activate','login', 'content-password', 'account-two-factor') NOT NULL, + `request_user_id` INT UNSIGNED DEFAULT NULL, + `request_content_id` INT UNSIGNED DEFAULT NULL, + `request_ip` VARCHAR(255) NOT NULL, + `request_date` DATETIME NOT NULL, + `request_date_gmt` DATETIME NOT NULL, + `request_result` ENUM('success','fail') NOT NULL, PRIMARY KEY (`request_id`), KEY `request_type` (`request_type`), KEY `request_user_id` (`request_user_id`), KEY `request_content_id` (`request_content_id`), KEY `request_ip` (`request_ip`), KEY `request_date_gmt` (`request_date_gmt`), - KEY `request_result` (`request_result`) + KEY `request_result` (`request_result`), + KEY `request_result_ip_type_date_gmt` (`request_result`, `request_type`, `request_ip`,`request_date_gmt`), + KEY `request_user_id_result_type_ip` (`request_user_id`, `request_result`, `request_type`, `request_ip`) ) ENGINE=%table_engine% DEFAULT CHARSET=utf8; diff --git a/app/schemas/mysql-8/settings.sql b/app/schemas/mysql-8/settings.sql index 9f9c37a..aaf5f68 100644 --- a/app/schemas/mysql-8/settings.sql +++ b/app/schemas/mysql-8/settings.sql @@ -1,10 +1,10 @@ DROP TABLE IF EXISTS `%table_prefix%settings`; CREATE TABLE `%table_prefix%settings` ( - `setting_id` int(11) NOT NULL AUTO_INCREMENT, - `setting_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL, - `setting_value` text, - `setting_default` text, - `setting_typeset` enum('string','bool') DEFAULT 'string', + `setting_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `setting_name` VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL, + `setting_value` TEXT, + `setting_default` TEXT, + `setting_typeset` ENUM('string','bool') DEFAULT 'string', PRIMARY KEY (`setting_id`), UNIQUE KEY `setting_name` (`setting_name`) USING BTREE ) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4; diff --git a/app/schemas/mysql-8/stats.sql b/app/schemas/mysql-8/stats.sql index e7127b9..09c1d25 100644 --- a/app/schemas/mysql-8/stats.sql +++ b/app/schemas/mysql-8/stats.sql @@ -1,21 +1,22 @@ DROP TABLE IF EXISTS `%table_prefix%stats`; CREATE TABLE `%table_prefix%stats` ( - `stat_id` bigint(32) NOT NULL AUTO_INCREMENT, - `stat_type` enum('total','date') NOT NULL, + `stat_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `stat_type` ENUM('total','date') NOT NULL, `stat_date_gmt` date DEFAULT NULL, - `stat_users` bigint(32) UNSIGNED NOT NULL DEFAULT '0', - `stat_images` bigint(32) UNSIGNED NOT NULL DEFAULT '0', - `stat_albums` bigint(32) UNSIGNED NOT NULL DEFAULT '0', - `stat_tags` bigint(32) UNSIGNED NOT NULL DEFAULT '0', - `stat_cron_runs` bigint(32) UNSIGNED NOT NULL DEFAULT '0', - `stat_cron_time` bigint(32) UNSIGNED NOT NULL DEFAULT '0', - `stat_image_views` bigint(32) UNSIGNED NOT NULL DEFAULT '0', - `stat_album_views` bigint(32) UNSIGNED NOT NULL DEFAULT '0', - `stat_image_likes` bigint(32) UNSIGNED NOT NULL DEFAULT '0', - `stat_album_likes` bigint(32) UNSIGNED NOT NULL DEFAULT '0', - `stat_disk_used` bigint(32) UNSIGNED NOT NULL DEFAULT '0', + `stat_users` INT UNSIGNED NOT NULL DEFAULT '0', + `stat_images` INT UNSIGNED NOT NULL DEFAULT '0', + `stat_albums` INT UNSIGNED NOT NULL DEFAULT '0', + `stat_tags` INT UNSIGNED NOT NULL DEFAULT '0', + `stat_cron_runs` INT UNSIGNED NOT NULL DEFAULT '0', + `stat_cron_time` INT UNSIGNED NOT NULL DEFAULT '0', + `stat_image_views` INT UNSIGNED NOT NULL DEFAULT '0', + `stat_album_views` INT UNSIGNED NOT NULL DEFAULT '0', + `stat_image_likes` INT UNSIGNED NOT NULL DEFAULT '0', + `stat_album_likes` INT UNSIGNED NOT NULL DEFAULT '0', + `stat_disk_used` BIGINT UNSIGNED NOT NULL DEFAULT '0', PRIMARY KEY (`stat_id`), UNIQUE KEY `stat_date_gmt` (`stat_date_gmt`) USING BTREE, - KEY `stat_type` (`stat_type`) + KEY `stat_type` (`stat_type`), + KEY `stat_type_date_gmt` (`stat_type`, `stat_date_gmt` DESC) ) ENGINE=%table_engine% DEFAULT CHARSET=utf8; INSERT INTO `%table_prefix%stats` (stat_id, stat_type) VALUES (1, 'total'); diff --git a/app/schemas/mysql-8/storage_apis.sql b/app/schemas/mysql-8/storage_apis.sql index 886244c..612abd5 100644 --- a/app/schemas/mysql-8/storage_apis.sql +++ b/app/schemas/mysql-8/storage_apis.sql @@ -1,8 +1,8 @@ DROP TABLE IF EXISTS `%table_prefix%storage_apis`; CREATE TABLE `%table_prefix%storage_apis` ( - `storage_api_id` bigint(32) NOT NULL AUTO_INCREMENT, - `storage_api_name` varchar(255) NOT NULL, - `storage_api_type` varchar(255) NOT NULL, + `storage_api_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `storage_api_name` VARCHAR(255) NOT NULL, + `storage_api_type` VARCHAR(255) NOT NULL, PRIMARY KEY (`storage_api_id`) ) ENGINE=%table_engine% DEFAULT CHARSET=utf8; INSERT INTO `%table_prefix%storage_apis` VALUES ('1', 'Amazon S3', 's3'); @@ -15,4 +15,4 @@ INSERT INTO `%table_prefix%storage_apis` VALUES ('7', 'OpenStack', 'openstack'); INSERT INTO `%table_prefix%storage_apis` VALUES ('8', 'Local', 'local'); INSERT INTO `%table_prefix%storage_apis` VALUES ('9', 'S3 compatible', 's3compatible'); INSERT INTO `%table_prefix%storage_apis` VALUES ('10', 'Alibaba Cloud OSS', 'oss'); -INSERT INTO `%table_prefix%storage_apis` VALUES ('11', 'Backblaze B2', 'b2'); \ No newline at end of file +INSERT INTO `%table_prefix%storage_apis` VALUES ('11', 'Backblaze B2', 'b2'); diff --git a/app/schemas/mysql-8/storages.sql b/app/schemas/mysql-8/storages.sql index 7fb4ef9..1970462 100644 --- a/app/schemas/mysql-8/storages.sql +++ b/app/schemas/mysql-8/storages.sql @@ -1,23 +1,23 @@ DROP TABLE IF EXISTS `%table_prefix%storages`; CREATE TABLE `%table_prefix%storages` ( - `storage_id` bigint(32) NOT NULL AUTO_INCREMENT, - `storage_api_id` bigint(32) NOT NULL, - `storage_name` varchar(255) NOT NULL, - `storage_service` varchar(255) DEFAULT NULL, - `storage_url` varchar(255) NOT NULL, - `storage_bucket` varchar(255) DEFAULT NULL, - `storage_region` varchar(255) DEFAULT NULL, - `storage_server` varchar(255) DEFAULT NULL, - `storage_account_id` varchar(255) DEFAULT NULL, - `storage_account_name` varchar(255) DEFAULT NULL, - `storage_key` text, - `storage_secret` text, - `storage_is_https` tinyint(1) NOT NULL DEFAULT '0', - `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', - `storage_use_path_style_endpoint` tinyint(1) NOT NULL DEFAULT '0', + `storage_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `storage_api_id` INT UNSIGNED NOT NULL, + `storage_name` VARCHAR(255) NOT NULL, + `storage_service` VARCHAR(255) DEFAULT NULL, + `storage_url` VARCHAR(255) NOT NULL, + `storage_bucket` VARCHAR(255) DEFAULT NULL, + `storage_region` VARCHAR(255) DEFAULT NULL, + `storage_server` VARCHAR(255) DEFAULT NULL, + `storage_account_id` VARCHAR(255) DEFAULT NULL, + `storage_account_name` VARCHAR(255) DEFAULT NULL, + `storage_key` TEXT, + `storage_secret` TEXT, + `storage_is_https` TINYINT UNSIGNED NOT NULL DEFAULT '0', + `storage_is_active` TINYINT UNSIGNED NOT NULL DEFAULT '0', + `storage_capacity` BIGINT UNSIGNED DEFAULT NULL, + `storage_space_used` BIGINT UNSIGNED DEFAULT '0', + `storage_type_chain` TINYINT UNSIGNED NOT NULL DEFAULT '1', + `storage_use_path_style_endpoint` TINYINT UNSIGNED NOT NULL DEFAULT '0', `storage_deleted_at` DATETIME NULL DEFAULT NULL, PRIMARY KEY (`storage_id`), KEY `storage_api_id` (`storage_api_id`), diff --git a/app/schemas/mysql-8/tags.sql b/app/schemas/mysql-8/tags.sql index 898ed76..4119b6c 100644 --- a/app/schemas/mysql-8/tags.sql +++ b/app/schemas/mysql-8/tags.sql @@ -1,12 +1,12 @@ DROP TABLE IF EXISTS `%table_prefix%tags`; CREATE TABLE `%table_prefix%tags` ( - `tag_id` bigint(32) NOT NULL AUTO_INCREMENT, - `tag_name` varchar(32) COLLATE utf8mb4_bin NOT NULL, - `tag_description` text, - `tag_user_id` bigint(32) NOT NULL, - `tag_date_gmt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, - `tag_files` bigint(32) NOT NULL DEFAULT 0, - `tag_views` bigint(32) NOT NULL DEFAULT 0, + `tag_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `tag_name` VARCHAR(32) COLLATE utf8mb4_bin NOT NULL, + `tag_description` TEXT, + `tag_user_id` INT UNSIGNED NOT NULL, + `tag_date_gmt` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `tag_files` INT UNSIGNED NOT NULL DEFAULT 0, + `tag_views` INT UNSIGNED NOT NULL DEFAULT 0, PRIMARY KEY (`tag_id`), UNIQUE KEY `tag_name` (`tag_name`) USING BTREE, KEY `tag_user_id` (`tag_user_id`), @@ -15,5 +15,6 @@ CREATE TABLE `%table_prefix%tags` ( KEY `tag_views` (`tag_views`), KEY `tag_user_id_date_gmt` (`tag_user_id`,`tag_date_gmt`), KEY `tag_user_id_files` (`tag_user_id`,`tag_files`), - KEY `tag_user_id_views` (`tag_user_id`,`tag_views`) + KEY `tag_user_id_views` (`tag_user_id`,`tag_views`), + KEY `tag_files_name` (`tag_files` DESC, `tag_name` ASC) ) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4; diff --git a/app/schemas/mysql-8/tags_albums.sql b/app/schemas/mysql-8/tags_albums.sql index b115eed..744857b 100644 --- a/app/schemas/mysql-8/tags_albums.sql +++ b/app/schemas/mysql-8/tags_albums.sql @@ -1,10 +1,10 @@ DROP TABLE IF EXISTS `%table_prefix%tags_albums`; CREATE TABLE `%table_prefix%tags_albums` ( - `tag_album_tag_id` bigint(32) NOT NULL, - `tag_album_album_id` bigint(32) NOT NULL, - `tag_album_user_id` bigint(32) NOT NULL, - `tag_album_count` int(11) NOT NULL DEFAULT 0, - `tag_album_last_used_datetime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `tag_album_tag_id` INT UNSIGNED NOT NULL, + `tag_album_album_id` INT UNSIGNED NOT NULL, + `tag_album_user_id` INT UNSIGNED NOT NULL, + `tag_album_count` INT UNSIGNED NOT NULL DEFAULT 0, + `tag_album_last_used_datetime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (tag_album_tag_id) REFERENCES `%table_prefix%tags` (tag_id) ON DELETE CASCADE, FOREIGN KEY (tag_album_album_id) REFERENCES `%table_prefix%albums` (album_id) ON DELETE CASCADE, FOREIGN KEY (tag_album_user_id) REFERENCES `%table_prefix%users` (user_id) ON DELETE CASCADE, diff --git a/app/schemas/mysql-8/tags_files.sql b/app/schemas/mysql-8/tags_files.sql index 863cdff..40c1e85 100644 --- a/app/schemas/mysql-8/tags_files.sql +++ b/app/schemas/mysql-8/tags_files.sql @@ -1,7 +1,7 @@ DROP TABLE IF EXISTS `%table_prefix%tags_files`; CREATE TABLE `%table_prefix%tags_files` ( - `tag_file_tag_id` bigint(32) NOT NULL, - `tag_file_file_id` bigint(32) NOT NULL, + `tag_file_tag_id` INT UNSIGNED NOT NULL, + `tag_file_file_id` INT UNSIGNED NOT NULL, FOREIGN KEY (tag_file_tag_id) REFERENCES `%table_prefix%tags` (tag_id) ON DELETE CASCADE, FOREIGN KEY (tag_file_file_id) REFERENCES `%table_prefix%images` (image_id) ON DELETE CASCADE, UNIQUE INDEX `tag_file_UNIQUE` (`tag_file_tag_id` ASC, `tag_file_file_id` ASC) diff --git a/app/schemas/mysql-8/tags_users.sql b/app/schemas/mysql-8/tags_users.sql index 8ee43fa..3fc3410 100644 --- a/app/schemas/mysql-8/tags_users.sql +++ b/app/schemas/mysql-8/tags_users.sql @@ -1,9 +1,9 @@ DROP TABLE IF EXISTS `%table_prefix%tags_users`; CREATE TABLE `%table_prefix%tags_users` ( - `tag_user_tag_id` bigint(32) NOT NULL, - `tag_user_user_id` bigint(32) NOT NULL, - `tag_user_count` int(11) NOT NULL DEFAULT 0, - `tag_user_last_used_datetime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `tag_user_tag_id` INT UNSIGNED NOT NULL, + `tag_user_user_id` INT UNSIGNED NOT NULL, + `tag_user_count` INT UNSIGNED NOT NULL DEFAULT 0, + `tag_user_last_used_datetime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (tag_user_tag_id) REFERENCES `%table_prefix%tags` (tag_id) ON DELETE CASCADE, FOREIGN KEY (tag_user_user_id) REFERENCES `%table_prefix%users` (user_id) ON DELETE CASCADE, UNIQUE INDEX `tag_user_UNIQUE` (`tag_user_tag_id` ASC, `tag_user_user_id` ASC), diff --git a/app/schemas/mysql-8/two_factors.sql b/app/schemas/mysql-8/two_factors.sql index 9e49a98..d264c41 100644 --- a/app/schemas/mysql-8/two_factors.sql +++ b/app/schemas/mysql-8/two_factors.sql @@ -1,9 +1,9 @@ DROP TABLE IF EXISTS `%table_prefix%two_factors`; CREATE TABLE `%table_prefix%two_factors` ( - `two_factor_id` bigint(32) NOT NULL AUTO_INCREMENT, - `two_factor_user_id` bigint(32) DEFAULT NULL, - `two_factor_date_gmt` datetime NOT NULL, - `two_factor_secret` text NOT NULL, + `two_factor_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `two_factor_user_id` INT UNSIGNED DEFAULT NULL, + `two_factor_date_gmt` DATETIME NOT NULL, + `two_factor_secret` TEXT NOT NULL, PRIMARY KEY (`two_factor_id`), KEY `two_factor_user_id` (`two_factor_user_id`), KEY `two_factor_date_gmt` (`two_factor_date_gmt`) diff --git a/app/schemas/mysql-8/uploads.sql b/app/schemas/mysql-8/uploads.sql new file mode 100644 index 0000000..9536044 --- /dev/null +++ b/app/schemas/mysql-8/uploads.sql @@ -0,0 +1,17 @@ +DROP TABLE IF EXISTS `%table_prefix%uploads`; +CREATE TABLE `%table_prefix%uploads` ( + `upload_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `upload_user_id` INT UNSIGNED DEFAULT NULL, + `upload_uploader_ip` VARCHAR(255) NOT NULL, + `upload_token` VARCHAR(64) NOT NULL, + `upload_checksum` VARCHAR(32) NOT NULL, + `upload_params` JSON NOT NULL, + `upload_chunks` INT UNSIGNED NOT NULL, + `upload_date_gmt` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `upload_completed` TINYINT UNSIGNED NOT NULL DEFAULT 0, + PRIMARY KEY (`upload_id`), + KEY `upload_id_token` (`upload_id`, `upload_token`), + KEY `upload_id_token_user_id` (`upload_id`, `upload_token`, `upload_user_id`), + KEY `upload_date_gmt` (`upload_date_gmt`), + KEY `upload_checksum_uploader_ip_date_gmt` (`upload_checksum`, `upload_uploader_ip`, `upload_date_gmt`) +) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4; diff --git a/app/schemas/mysql-8/uploads_chunks.sql b/app/schemas/mysql-8/uploads_chunks.sql new file mode 100644 index 0000000..f114b62 --- /dev/null +++ b/app/schemas/mysql-8/uploads_chunks.sql @@ -0,0 +1,10 @@ +DROP TABLE IF EXISTS `%table_prefix%uploads_chunks`; +CREATE TABLE `%table_prefix%uploads_chunks` ( + `upload_chunk_upload_id` INT UNSIGNED NOT NULL, + `upload_chunk_index` INT UNSIGNED NOT NULL, + `upload_chunk_path` VARCHAR(4096) DEFAULT NULL, + `upload_chunk_date_gmt` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`upload_chunk_upload_id`, `upload_chunk_index`), + FOREIGN KEY (upload_chunk_upload_id) REFERENCES `%table_prefix%uploads` (upload_id) ON DELETE CASCADE, + KEY `upload_chunk_date_gmt` (`upload_chunk_date_gmt`) +) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4; diff --git a/app/schemas/mysql-8/users.sql b/app/schemas/mysql-8/users.sql index 9a0f9c0..cbaaf16 100644 --- a/app/schemas/mysql-8/users.sql +++ b/app/schemas/mysql-8/users.sql @@ -1,38 +1,38 @@ DROP TABLE IF EXISTS `%table_prefix%users`; CREATE TABLE `%table_prefix%users` ( - `user_id` bigint(32) NOT NULL AUTO_INCREMENT, - `user_name` varchar(255) DEFAULT NULL, - `user_username` varchar(255) NOT NULL, - `user_date` datetime NOT NULL, - `user_date_gmt` datetime NOT NULL, - `user_email` varchar(255) DEFAULT NULL, - `user_avatar_filename` varchar(255) DEFAULT NULL, - `user_facebook_username` varchar(255) DEFAULT NULL, - `user_twitter_username` varchar(255) DEFAULT NULL, - `user_website` varchar(255) DEFAULT NULL, - `user_background_filename` varchar(255) DEFAULT NULL, - `user_bio` varchar(255) DEFAULT NULL, - `user_timezone` varchar(255) NOT NULL, - `user_language` varchar(255) DEFAULT NULL, - `user_status` enum('valid','awaiting-confirmation','awaiting-email','banned') NOT NULL, - `user_is_admin` tinyint(1) NOT NULL DEFAULT '0', - `user_is_manager` tinyint(1) NOT NULL DEFAULT '0', - `user_is_private` tinyint(1) NOT NULL DEFAULT '0', - `user_palette_id` int(11) NOT NULL DEFAULT '0', - `user_newsletter_subscribe` tinyint(1) NOT NULL DEFAULT '1', - `user_show_nsfw_listings` tinyint(1) NOT NULL DEFAULT '0', - `user_image_count` bigint(32) NOT NULL DEFAULT '0', - `user_album_count` bigint(32) NOT NULL DEFAULT '0', - `user_image_keep_exif` tinyint(1) NOT NULL DEFAULT '1', - `user_file_meta_tag_camera_model` tinyint(1) NOT NULL DEFAULT '1', - `user_image_expiration` varchar(255) DEFAULT NULL, - `user_registration_ip` varchar(255) NOT NULL, - `user_likes` bigint(32) NOT NULL DEFAULT '0' COMMENT 'Likes made to content owned by this user', - `user_liked` bigint(32) NOT NULL DEFAULT '0' COMMENT 'Likes made by this user', - `user_following` bigint(32) NOT NULL DEFAULT '0', - `user_followers` bigint(32) NOT NULL DEFAULT '0', - `user_content_views` bigint(32) NOT NULL DEFAULT '0', - `user_notifications_unread` bigint(32) NOT NULL DEFAULT '0', + `user_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `user_name` VARCHAR(255) DEFAULT NULL, + `user_username` VARCHAR(255) NOT NULL, + `user_date` DATETIME NOT NULL, + `user_date_gmt` DATETIME NOT NULL, + `user_email` VARCHAR(255) DEFAULT NULL, + `user_avatar_filename` VARCHAR(255) DEFAULT NULL, + `user_facebook_username` VARCHAR(255) DEFAULT NULL, + `user_twitter_username` VARCHAR(255) DEFAULT NULL, + `user_website` VARCHAR(255) DEFAULT NULL, + `user_background_filename` VARCHAR(255) DEFAULT NULL, + `user_bio` VARCHAR(255) DEFAULT NULL, + `user_timezone` VARCHAR(255) NOT NULL, + `user_language` VARCHAR(255) DEFAULT NULL, + `user_status` ENUM('valid','awaiting-confirmation','awaiting-email','banned') NOT NULL, + `user_is_admin` TINYINT UNSIGNED NOT NULL DEFAULT '0', + `user_is_manager` TINYINT UNSIGNED NOT NULL DEFAULT '0', + `user_is_private` TINYINT UNSIGNED NOT NULL DEFAULT '0', + `user_palette_id` INT UNSIGNED NOT NULL DEFAULT '0', + `user_newsletter_subscribe` TINYINT UNSIGNED NOT NULL DEFAULT '1', + `user_show_nsfw_listings` TINYINT UNSIGNED NOT NULL DEFAULT '0', + `user_image_count` INT UNSIGNED NOT NULL DEFAULT '0', + `user_album_count` INT UNSIGNED NOT NULL DEFAULT '0', + `user_image_keep_exif` TINYINT UNSIGNED NOT NULL DEFAULT '1', + `user_file_meta_tag_camera_model` TINYINT UNSIGNED NOT NULL DEFAULT '1', + `user_image_expiration` VARCHAR(255) DEFAULT NULL, + `user_registration_ip` VARCHAR(255) NOT NULL, + `user_likes` INT UNSIGNED NOT NULL DEFAULT '0' COMMENT 'Likes made to content owned by this user', + `user_liked` INT UNSIGNED NOT NULL DEFAULT '0' COMMENT 'Likes made by this user', + `user_following` INT UNSIGNED NOT NULL DEFAULT '0', + `user_followers` INT UNSIGNED NOT NULL DEFAULT '0', + `user_content_views` INT UNSIGNED NOT NULL DEFAULT '0', + `user_notifications_unread` INT UNSIGNED NOT NULL DEFAULT '0', PRIMARY KEY (`user_id`), UNIQUE KEY `username` (`user_username`) USING BTREE, UNIQUE KEY `email` (`user_email`) USING BTREE, diff --git a/app/schemas/mysql-8/variables.sql b/app/schemas/mysql-8/variables.sql index f6254c2..5b0f8ae 100644 --- a/app/schemas/mysql-8/variables.sql +++ b/app/schemas/mysql-8/variables.sql @@ -1,10 +1,10 @@ DROP TABLE IF EXISTS `%table_prefix%variables`; CREATE TABLE `%table_prefix%variables` ( - `variable_id` int(11) NOT NULL AUTO_INCREMENT, - `variable_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL, - `variable_datetime_utc` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `variable_value` text, - `variable_type` enum('string','bool','int','float','array','object') DEFAULT 'string', + `variable_id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `variable_name` VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL, + `variable_datetime_utc` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `variable_value` TEXT, + `variable_type` ENUM('string','bool','int','float','array','object') DEFAULT 'string', PRIMARY KEY (`variable_id`), UNIQUE KEY `variable_name` (`variable_name`) USING BTREE ) ENGINE=%table_engine% DEFAULT CHARSET=utf8mb4; diff --git a/app/src/Actions/Auth/AuthVerifyCSRFTokenAction.php b/app/src/Actions/Auth/AuthVerifyCSRFTokenAction.php deleted file mode 100644 index f650fa3..0000000 --- a/app/src/Actions/Auth/AuthVerifyCSRFTokenAction.php +++ /dev/null @@ -1,34 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Actions\Auth; - -use Chevere\Action\Action; -use Chevere\Parameter\Attributes\StringAttr; -use InvalidArgumentException; - -final class AuthVerifyCSRFTokenAction extends Action -{ - public function run( - #[StringAttr(description: 'Token granted to the user session.')] - string $sessionValue, - #[StringAttr(description: 'Token provided by the user.')] - string $userInput - ): array { - if (! hash_equals($sessionValue, $userInput)) { - throw new InvalidArgumentException( - 'Invalid CSRF token' - ); - } - - return []; - } -} diff --git a/app/src/Actions/Auth/AuthVerifyRepositoryAccessAction.php b/app/src/Actions/Auth/AuthVerifyRepositoryAccessAction.php deleted file mode 100644 index 0ed0599..0000000 --- a/app/src/Actions/Auth/AuthVerifyRepositoryAccessAction.php +++ /dev/null @@ -1,47 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Actions\Auth; - -use Chevere\Action\Action; -use function Chevere\DataStructure\data; -use Chevere\Parameter\Attributes\ParameterAttribute; -use Chevere\Parameter\Interfaces\ParametersInterface; -use function Chevere\Parameter\parameters; -use function Chevere\Parameter\stringParameter; - -class AuthVerifyRepositoryAccessAction extends Action -{ - public function getResponseParameters(): ParametersInterface - { - return parameters( - grant: stringParameter( - description: 'Describes the permission grantee.', - ) - ); - } - - public function run( - #[ParameterAttribute(description: 'User id for the user requesting this resource.')] - string $requesterUserId, - #[ParameterAttribute(description: 'Repository name to check access.')] - string $repository, - #[ParameterAttribute( - description: 'Permission level to check.', - regex: '/^(read|write|execute)$/' - )] - string $level - ): array { - return data( - grant: 'isAdmin' - ); - } -} diff --git a/app/src/Actions/Auth/AuthVerifyResourceAccessAction.php b/app/src/Actions/Auth/AuthVerifyResourceAccessAction.php deleted file mode 100644 index 4f11771..0000000 --- a/app/src/Actions/Auth/AuthVerifyResourceAccessAction.php +++ /dev/null @@ -1,56 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Actions\Auth; - -use Chevere\Action\Action; -use function Chevere\DataStructure\data; -use Chevere\Parameter\Attributes\ParameterAttribute; -use Chevere\Parameter\Interfaces\ParametersInterface; -use function Chevere\Parameter\parameters; -use function Chevere\Parameter\stringParameter; - -class AuthVerifyResourceAccessAction extends Action -{ - public function run( - #[ParameterAttribute( - description: 'User id for the user requesting this resource.' - )] - int $requesterUserId, - #[ParameterAttribute( - description: 'User id for the owner of the resource.' - )] - int $ownerUserId, - #[ParameterAttribute( - description: 'Resource name to check access.' - )] - string $resource, - #[ - ParameterAttribute( - description: 'Permission level to check.', - regex: '/^(read|write|execute)$/' - )] - string $level - ): array { - return data( - grant: 'isAdmin' - ); - } - - public function getResponseParameters(): ParametersInterface - { - return parameters( - grant: stringParameter( - description: 'Describes the permission grantee.', - ) - ); - } -} diff --git a/app/src/Actions/Database/DatabaseReserveRowAction.php b/app/src/Actions/Database/DatabaseReserveRowAction.php deleted file mode 100644 index d442a49..0000000 --- a/app/src/Actions/Database/DatabaseReserveRowAction.php +++ /dev/null @@ -1,64 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Actions\Database; - -use Chevere\Action\Action; -use function Chevere\DataStructure\data; -use function Chevere\Parameter\integerParameter; -use Chevere\Parameter\Interfaces\ParametersInterface; -use function Chevere\Parameter\objectParameter; -use Chevere\Parameter\Parameters; -use function Chevere\Parameter\parameters; -use Chevereto\Database\Database; - -/** - * Reserves a row in the database. - * - * Arguments: - * - * ```php - * table: string, - * ``` - * - * Response: - * - * ```php - * id: int, - * ``` - */ -class DatabaseReserveRowAction extends Action -{ - private Database $database; - - public function getContainerParameters(): ParametersInterface - { - return new Parameters( - database: objectParameter(Database::class) - ); - } - - public function getResponseParameters(): ParametersInterface - { - return - parameters( - id: integerParameter() - ); - } - - public function run(string $table): array - { - // $db->insert row - return data( - id: 123 - ); - } -} diff --git a/app/src/Actions/File/FileFetchSourceAction.php b/app/src/Actions/File/FileFetchSourceAction.php deleted file mode 100644 index b566ed9..0000000 --- a/app/src/Actions/File/FileFetchSourceAction.php +++ /dev/null @@ -1,61 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Actions\File; - -use Chevere\Action\Action; -use function Chevere\DataStructure\data; -use Chevere\Parameter\Attributes\ParameterAttribute; -use Chevere\Parameter\Interfaces\ParametersInterface; -use function Chevere\Parameter\parameters; -use function Chevere\Parameter\stringParameter; -use Chevere\Serialize\Deserialize; -use function Chevereto\Encoding\assertBase64; -use function Chevereto\Encoding\storeDecodedBase64; -use function Chevereto\File\storeDownloadedUrl; -use Laminas\Uri\UriFactory; -use Throwable; - -final class FileFetchSourceAction extends Action -{ - public function getResponseParameters(): ParametersInterface - { - return - parameters( - filepath: stringParameter(), - ); - } - - public function run( - #[ParameterAttribute( - description: 'A binary file, base64 data, or an URL for a file.', - )] - string $source - ): array { - try { - $deserialize = new Deserialize($source); - $filepath = $deserialize->var()['tmp_name']; - } catch (Throwable) { - $filepath = tempnam(sys_get_temp_dir(), 'chv.temp'); - $uri = UriFactory::factory($source); - if ($uri->isValid()) { - storeDownloadedUrl($source, $filepath); - } else { - assertBase64($source); - storeDecodedBase64($source, $filepath); - } - } - - return data( - filepath: $filepath - ); - } -} diff --git a/app/src/Actions/File/FileNamingAction.php b/app/src/Actions/File/FileNamingAction.php deleted file mode 100644 index 8aa142a..0000000 --- a/app/src/Actions/File/FileNamingAction.php +++ /dev/null @@ -1,99 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Actions\File; - -use Chevere\Action\Action; -use Chevere\Filesystem\Filename; -use Chevere\Filesystem\Interfaces\FilenameInterface; -use Chevere\Filesystem\Interfaces\PathInterface; -use Chevere\Parameter\Attributes\StringAttr; -use Chevere\Parameter\Interfaces\ParametersInterface; -use Chevereto\Storage\Storage; -use function Chevere\DataStructure\data; -use function Chevere\Parameter\object; -use function Chevere\Parameter\parameters; -use function Chevere\Standard\randomString; - -/** - * Determines the best available target filename for the given storage, path and naming. - */ -class FileNamingAction extends Action -{ - public function run( - int $id, - #[StringAttr('/^.+\.[a-zA-Z]+$/')] - string $name, - Storage $storage, - PathInterface $path, - #[StringAttr('/^original|random|mixed|id$/')] - string $naming = 'original', - ): array { - $encodedId = 'encoded'; - $file = new Filename($name); - if ($naming === 'id') { - return [ - 'filename' => new Filename($encodedId . '.' . $file->extension()), - ]; - } - $name = $this->getName($naming, $file); - // USE OWN INDEX, REQUIRE STORAGE ID PARAM - while ($storage->adapter()->fileExists($path->getChild($name)->__toString())) { - if ($naming === 'original') { - $naming = 'mixed'; - } - $name = $this->getName($naming, $file); - } - - return data( - filename: new Filename($name) - ); - } - - public function getResponseParameters(): ParametersInterface - { - return - parameters( - filename: object( - className: Filename::class - ), - ); - } - - public function getName(string $naming, FilenameInterface $filename): string - { - return match ($naming) { - 'original' => $filename->__toString(), - 'random' => $this->getRandomName($filename), - 'mixed' => $this->getMixedName($filename), - }; - } - - private function getRandomName(FilenameInterface $filename): string - { - return randomString(32) . '.' . $filename->extension(); - } - - private function getMixedName(FilenameInterface $filename): string - { - $charsLength = 16; - $chars = randomString($charsLength); - $name = $filename->name(); - $nameLength = mb_strlen($name); - $withExtensionLength = mb_strlen($filename->extension()) + 1; - if ($nameLength + $charsLength > Filename::MAX_LENGTH_BYTES) { - $chop = Filename::MAX_LENGTH_BYTES - $charsLength - $nameLength - $withExtensionLength; - $name = mb_substr($name, 0, $chop); - } - - return $name . $chars . '.' . $filename->extension(); - } -} diff --git a/app/src/Actions/File/FileUploadAction.php b/app/src/Actions/File/FileUploadAction.php deleted file mode 100644 index 2249335..0000000 --- a/app/src/Actions/File/FileUploadAction.php +++ /dev/null @@ -1,33 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Actions\File; - -use Chevere\Action\Action; -use Chevere\Filesystem\Interfaces\FilenameInterface; -use Chevere\Filesystem\Interfaces\PathInterface; -use Chevereto\Storage\Storage; - -/** - * Upload the filename to the target storage. - * @TODO If this does storage, it should be under /Storage - */ -class FileUploadAction extends Action -{ - public function run( - string $filepath, - FilenameInterface $targetFilename, - Storage $storage, - PathInterface $path - ): array { - return []; - } -} diff --git a/app/src/Actions/File/FileValidateAction.php b/app/src/Actions/File/FileValidateAction.php deleted file mode 100644 index 4b1d57e..0000000 --- a/app/src/Actions/File/FileValidateAction.php +++ /dev/null @@ -1,137 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Actions\File; - -use Chevere\Action\Action; -use Chevere\Parameter\Attributes\StringAttr; -use Chevere\Parameter\Interfaces\ParametersInterface; -use InvalidArgumentException; -use Throwable; -use function Chevere\DataStructure\data; -use function Chevere\Message\message; -use function Chevere\Parameter\int; -use function Chevere\Parameter\parameters; -use function Chevere\Parameter\string; -use function Safe\filesize; -use function Safe\md5_file; -use function Safe\mime_content_type; - -/** - * Validate file type and its size. - */ -class FileValidateAction extends Action -{ - private array $mimes = []; - - private int $maxBytes = 0; - - private int $minBytes = 0; - - public function run( - #[StringAttr( - '/^([\w]+\/[\w\-\+\.]+)+(,([\w]+\/[\w\-\+\.]+))*$/', - 'Comma-separated list of allowed mime-types.' - )] - string $mimes, - string $filepath, - int $maxBytes = 0, - int $minBytes = 0, - ): array { - $this->mimes = explode(',', $mimes); - $this->minBytes = $minBytes; - $this->maxBytes = $maxBytes; - $bytes = $this->assertGetFileBytes($filepath); - $this->assertMaxBytes($bytes); - $this->assertMinBytes($bytes); - $mime = mime_content_type($filepath); - $this->assertMime($mime); - - return data( - bytes: $bytes, - mime: $mime, - md5: md5_file($filepath), - ); - } - - public function getResponseParameters(): ParametersInterface - { - return - parameters( - bytes : int(), - mime : string(), - md5 : string(), - ); - } - - /** - * @codeCoverageIgnore - */ - private function assertGetFileBytes(string $filepath): int - { - try { - return filesize($filepath); - } catch (Throwable $e) { - throw new InvalidArgumentException( - message($e->getMessage()), - 1000 - ); - } - } - - private function assertMinBytes(int $bytes): void - { - if ($this->minBytes === 0) { - return; - } - if ($bytes < $this->minBytes) { - throw new InvalidArgumentException( - message( - "Filesize (`%fileSize%`) doesn't meet the minimum bytes required (`%required%`)", - fileSize: (string) ($bytes . ' B'), - required: (string) ($this->minBytes . ' B'), - ), - 1001 - ); - } - } - - private function assertMaxBytes(int $bytes): void - { - if ($this->maxBytes === 0) { - return; - } - if ($bytes > $this->maxBytes) { - throw new InvalidArgumentException( - message( - 'Filesize (`%fileSize%`) exceeds the maximum bytes allowed (`%allowed%`)', - fileSize: (string) ($bytes . ' B'), - allowed: (string) ($this->maxBytes . ' B'), - ), - 1002 - ); - } - } - - private function assertMime(string $mime): void - { - if (! in_array($mime, $this->mimes, true)) { - throw new InvalidArgumentException( - message( - 'File mime-type `%type%` is not allowed (allows `%allowed%`)', - type: $mime, - allowed: implode(', ', $this->mimes), - ), - 1004 - ); - } - } -} diff --git a/app/src/Actions/File/FileVerifyNotDuplicateAction.php b/app/src/Actions/File/FileVerifyNotDuplicateAction.php deleted file mode 100644 index 5fe7348..0000000 --- a/app/src/Actions/File/FileVerifyNotDuplicateAction.php +++ /dev/null @@ -1,49 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Actions\File; - -use Chevere\Action\Action; -use Chevere\Parameter\Attributes\ParameterAttribute; -use Chevere\Parameter\Interfaces\ParametersInterface; -use function Chevere\Parameter\objectParameter; -use Chevere\Parameter\Parameters; -use Chevereto\Database\Database; - -/** - * Detects file duplication based in both perceptual and file hashing, against the uploading frequency. - */ -class FileVerifyNotDuplicateAction extends Action -{ - private Database $database; - - public function getContainerParameters(): ParametersInterface - { - return new Parameters( - database: objectParameter(Database::class) - ); - } - - public function run( - #[ParameterAttribute(regex: '/^[a-f0-9]{32}$/')] - string $md5, - #[ParameterAttribute(regex: '/^[0-9A-F]+$/i')] - string $perceptual, - #[ParameterAttribute(regex: '/((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))/')] - string $ip, - #[ParameterAttribute(regex: '/^[4|6]$/')] - string $ipVersion - ): array { - // $db->query hash, rate, HALT if dupe - - return []; - } -} diff --git a/app/src/Actions/Image/ImageFetchMetaAction.php b/app/src/Actions/Image/ImageFetchMetaAction.php deleted file mode 100644 index a85e2ed..0000000 --- a/app/src/Actions/Image/ImageFetchMetaAction.php +++ /dev/null @@ -1,54 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Actions\Image; - -use Chevere\Action\Action; -use Chevere\Parameter\ArrayParameter; -use Chevere\Parameter\Interfaces\ParametersInterface; -use function Chevere\Parameter\parameters; -use Intervention\Image\Image; -use JeroenDesloovere\XmpMetadataExtractor\XmpMetadataExtractor; - -/** - * Fetch image metadata. - * - * Response parameters: - * - * ```php - * exif: array, - * iptc: array, - * xmp: array, - * ``` - */ -class ImageFetchMetaAction extends Action -{ - public function run(Image $image): array - { - $data = array_fill_keys(['exif', 'iptc', 'xmp'], []); - $data['exif'] = $image->exif() ?? []; - $data['iptc'] = $image->iptc() ?? []; - $xmpDataExtractor = new XmpMetadataExtractor(); - $data['xmp'] = $xmpDataExtractor->extractFromFile($image->basePath()); - - return $data; - } - - public function getResponseParameters(): ParametersInterface - { - return - parameters( - exif: new ArrayParameter(), - iptc: new ArrayParameter(), - xmp: new ArrayParameter(), - ); - } -} diff --git a/app/src/Actions/Image/ImageFixOrientationAction.php b/app/src/Actions/Image/ImageFixOrientationAction.php deleted file mode 100644 index 029cd63..0000000 --- a/app/src/Actions/Image/ImageFixOrientationAction.php +++ /dev/null @@ -1,28 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Actions\Image; - -use Chevere\Action\Action; -use Intervention\Image\Image; - -/** - * Fix the image orientation based on Exif Orientation (if any, if needed). - */ -class ImageFixOrientationAction extends Action -{ - public function run(Image $image): array - { - $image->orientate()->save(); - - return []; - } -} diff --git a/app/src/Actions/Image/ImageInsertAction.php b/app/src/Actions/Image/ImageInsertAction.php deleted file mode 100644 index 95bad4e..0000000 --- a/app/src/Actions/Image/ImageInsertAction.php +++ /dev/null @@ -1,43 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Actions\Image; - -use Chevere\Action\Action; -use Chevere\Parameter\Interfaces\ParametersInterface; -use function Chevere\Parameter\objectParameter; -use Chevere\Parameter\Parameters; -use Chevereto\Database\Database; - -/** - * Insert the image in the database. - */ -class ImageInsertAction extends Action -{ - private Database $database; - - public function getContainerParameters(): ParametersInterface - { - return new Parameters( - database: objectParameter(Database::class) - ); - } - - public function run( - int $id, - int $expires, - int $userId, - int $albumId, - ): array { - // TODO: DB inserting - return []; - } -} diff --git a/app/src/Actions/Image/ImageStripMetaAction.php b/app/src/Actions/Image/ImageStripMetaAction.php deleted file mode 100644 index 6c6def4..0000000 --- a/app/src/Actions/Image/ImageStripMetaAction.php +++ /dev/null @@ -1,41 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Actions\Image; - -use Chevere\Action\Action; -use Imagick; -use Intervention\Image\Image; - -/** - * Strip image metadata. - */ -class ImageStripMetaAction extends Action -{ - public function run(Image $image): array - { - /** @var Imagick $imagick */ - $imagick = $image->getCore(); - if (!($imagick instanceof Imagick)) { - return []; - } - $profiles = $imagick->getImageProfiles('icc', true); - $imagick->stripImage(); - // @codeCoverageIgnoreStart - if (!empty($profiles)) { - $imagick->profileImage('icc', $profiles['icc']); - } - // @codeCoverageIgnoreEnd - $image->save(); - - return []; - } -} diff --git a/app/src/Actions/Image/ImageVerifyMediaAction.php b/app/src/Actions/Image/ImageVerifyMediaAction.php deleted file mode 100644 index dcd3979..0000000 --- a/app/src/Actions/Image/ImageVerifyMediaAction.php +++ /dev/null @@ -1,175 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Actions\Image; - -use Chevere\Action\Action; -use Chevere\Message\Interfaces\MessageInterface; -use Chevere\Parameter\Interfaces\ParametersInterface; -use Intervention\Image\Image; -use Intervention\Image\ImageManager; -use InvalidArgumentException; -use Throwable; -use function Chevere\DataStructure\data; -use function Chevere\Message\message; -use function Chevere\Parameter\object; -use function Chevere\Parameter\parameters; -use function Chevere\Parameter\string; -use function Chevereto\Image\imageHash; -use function Chevereto\Image\imageManager; - -/** - * Validates an image against the image processing and image dimensions. - * - * Response parameters: - * - * ```php - * image: \Intervention\Image\Image, - * perceptual: string, - * ``` - */ -class ImageVerifyMediaAction extends Action -{ - private int $width = 0; - - private int $height = 0; - - private int $maxWidth = 0; - - private int $maxHeight = 0; - - private int $minWidth = 0; - - private int $minHeight = 0; - - public function run( - string $filepath, - int $maxHeight, - int $maxWidth, - int $minHeight, - int $minWidth, - ): array { - $image = $this->assertGetImage($filepath); - $this->width = $image->width(); - $this->height = $image->height(); - $this->maxWidth = $maxWidth; - $this->maxHeight = $maxHeight; - $this->minWidth = $minWidth; - $this->minHeight = $minHeight; - $this->assertMinHeight(); - $this->assertMaxHeight(); - $this->assertMinWidth(); - $this->assertMaxWidth(); - - return data( - image: $image, - perceptual: imageHash()->hash($filepath) - ); - } - - public function getResponseParameters(): ParametersInterface - { - return - parameters( - image: object( - className: Image::class - ), - perceptual: string(), - ); - } - - private function assertGetImage(string $filepath): Image - { - try { - return imageManager()->make($filepath); - } catch (Throwable $e) { - throw new InvalidArgumentException( - previous: $e, - code: 1000, - message: message( - "Filepath `%filepath%` provided can't be handled by `%manager%`", - filepath: $filepath, - manager: ImageManager::class - ) - ); - } - } - - private function assertMinHeight(): void - { - if ($this->height < $this->minHeight) { - throw new InvalidArgumentException( - $this->getMinExceptionMessage('height', $this->height), - 1001 - ); - } - } - - private function assertMaxHeight(): void - { - if ($this->height > $this->maxHeight) { - throw new InvalidArgumentException( - $this->getMaxExceptionMessage('height', $this->height), - 1002 - ); - } - } - - private function assertMinWidth(): void - { - if ($this->width < $this->minWidth) { - throw new InvalidArgumentException( - $this->getMinExceptionMessage('width', $this->width), - 1003 - ); - } - } - - private function assertMaxWidth(): void - { - if ($this->width > $this->maxWidth) { - throw new InvalidArgumentException( - $this->getMaxExceptionMessage('width', $this->width), - 1004 - ); - } - } - - private function getMinExceptionMessage(string $dimension, int $provided): MessageInterface - { - return message( - "Image `%dimension%` `%provided%` doesn't meet the the minimum required (`%required%`)", - dimension: $dimension, - provided: (string) $provided, - required: $this->getMinRequired(), - ); - } - - private function getMinRequired(): string - { - return (string) $this->minWidth . 'x' . (string) $this->minHeight; - } - - private function getMaxExceptionMessage(string $dimension, int $provided): MessageInterface - { - return message( - 'Image `%dimension%` `%provided%` exceeds the maximum allowed (`%allowed%`)', - dimension: $dimension, - provided: (string) $provided, - allowed: $this->getMaxAllowed(), - ); - } - - private function getMaxAllowed(): string - { - return (string) $this->maxWidth . 'x' . (string) $this->maxHeight; - } -} diff --git a/app/src/Actions/Legacy/Api/V1/ImageInsertAction.php b/app/src/Actions/Legacy/Api/V1/ImageInsertAction.php deleted file mode 100644 index b26d561..0000000 --- a/app/src/Actions/Legacy/Api/V1/ImageInsertAction.php +++ /dev/null @@ -1,36 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Actions\Legacy\Api\V1; - -use Chevere\Action\Action; -use Chevere\Parameter\Interfaces\ParametersInterface; -use function Chevere\Parameter\objectParameter; -use Chevere\Parameter\Parameters; -use Chevereto\Database\Database; - -class ImageInsertAction extends Action -{ - private Database $database; - - public function getContainerParameters(): ParametersInterface - { - return new Parameters( - database: objectParameter(Database::class) - ); - } - - public function run(int $id): array - { - // TODO: DB inserting - return []; - } -} diff --git a/app/src/Actions/Legacy/Api/V1/LegacyApiV1OutputAction.php b/app/src/Actions/Legacy/Api/V1/LegacyApiV1OutputAction.php deleted file mode 100644 index 4511488..0000000 --- a/app/src/Actions/Legacy/Api/V1/LegacyApiV1OutputAction.php +++ /dev/null @@ -1,39 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Actions\Legacy\Api\V1; - -use Chevere\Action\Action; -use function Chevere\DataStructure\data; -use Chevere\Parameter\Attributes\ParameterAttribute; -use Chevere\Parameter\Interfaces\ParametersInterface; -use function Chevere\Parameter\parameters; -use function Chevere\Parameter\stringParameter; - -class LegacyApiV1OutputAction extends Action -{ - public function run( - #[ParameterAttribute(regex: '/^(json|txt|redirect)$/')] - string $format - ): array { - return data( - document: 'formatted_document' - ); - } - - public function getResponseParameters(): ParametersInterface - { - return - parameters( - document: stringParameter() - ); - } -} diff --git a/app/src/Actions/Legacy/Api/V1/LegacyApiV1VerifyKeyAction.php b/app/src/Actions/Legacy/Api/V1/LegacyApiV1VerifyKeyAction.php deleted file mode 100644 index 09ea214..0000000 --- a/app/src/Actions/Legacy/Api/V1/LegacyApiV1VerifyKeyAction.php +++ /dev/null @@ -1,32 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Actions\Legacy\Api\V1; - -use Chevere\Action\Action; -use InvalidArgumentException; - -class LegacyApiV1VerifyKeyAction extends Action -{ - public function run( - string $key, - string $apiV1Key - ): array { - if ($key !== $apiV1Key) { - throw new InvalidArgumentException( - 'Invalid API V1 key provided', - 100 - ); - } - - return []; - } -} diff --git a/app/src/Actions/Storage/StorageGetForAssetAction.php b/app/src/Actions/Storage/StorageGetForAssetAction.php deleted file mode 100644 index 446d19f..0000000 --- a/app/src/Actions/Storage/StorageGetForAssetAction.php +++ /dev/null @@ -1,61 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Actions\Storage; - -use Chevere\Action\Action; -use function Chevere\DataStructure\data; -use Chevere\Parameter\Interfaces\ParametersInterface; -use function Chevere\Parameter\objectParameter; -use Chevere\Parameter\Parameters; -use function Chevere\Parameter\parameters; -use Chevereto\Database\Database; -use Chevereto\Storage\Storage; -use League\Flysystem\Local\LocalFilesystemAdapter; - -/** - * Finds a valid storage to allocate the bytes required. - * - * Response parameters: - * - * ```php - * storage: \Chevereto\Interfaces\Storage\StorageInterface, - * ``` - */ -class StorageGetForAssetAction extends Action -{ - public function getContainerParameters(): ParametersInterface - { - return new Parameters( - database: objectParameter(Database::class) - ); - } - - public function run(int $userId, int $bytesRequired): array - { - // $adapter = db->query storage for user assert; - $adapter = new LocalFilesystemAdapter(__DIR__); - - return data( - storage: new Storage(__DIR__) - ); - } - - public function getResponseParameters(): ParametersInterface - { - return - parameters( - storage: objectParameter( - className: Storage::class - ) - ); - } -} diff --git a/app/src/Actions/Storage/StorageGetForUserAction.php b/app/src/Actions/Storage/StorageGetForUserAction.php deleted file mode 100644 index ddaba37..0000000 --- a/app/src/Actions/Storage/StorageGetForUserAction.php +++ /dev/null @@ -1,58 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Actions\Storage; - -use Chevere\Action\Action; -use function Chevere\DataStructure\data; -use Chevere\Parameter\Interfaces\ParametersInterface; -use function Chevere\Parameter\objectParameter; -use Chevere\Parameter\Parameters; -use function Chevere\Parameter\parameters; -use Chevereto\Database\Database; -use Chevereto\Storage\Storage; - -/** - * Finds a valid storage to allocate the bytes required. - * - * Response parameters: - * - * ```php - * storage: \Chevereto\Interfaces\Storage\StorageInterface, - * ``` - */ -class StorageGetForUserAction extends Action -{ - public function getContainerParameters(): ParametersInterface - { - return new Parameters( - database: objectParameter(Database::class) - ); - } - - public function run(int $userId, int $bytesRequired): array - { - // $adapter = db->query storage for user; - - return data( - storage: new Storage(__DIR__) - ); - } - - public function getResponseParameters(): ParametersInterface - { - return parameters( - storage: objectParameter( - className: Storage::class - ) - ); - } -} diff --git a/app/src/Controllers/Api/V1/Upload/UploadPostController.php b/app/src/Controllers/Api/V1/Upload/UploadPostController.php deleted file mode 100644 index b1313b2..0000000 --- a/app/src/Controllers/Api/V1/Upload/UploadPostController.php +++ /dev/null @@ -1,52 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V1\Upload; - -use function Chevere\DataStructure\data; -use Chevere\Parameter\Attributes\ParameterAttribute; -use Chevere\Parameter\Interfaces\ParametersInterface; -use function Chevere\Parameter\parameters; -use function Chevere\Parameter\stringParameter; -use Chevere\Workflow\Attributes\Provider; -use Chevereto\Controllers\WorkflowController; -use Chevereto\Workflows\Legacy\LegacyUploadPostWorkflow; - -#[Provider(LegacyUploadPostWorkflow::class)] -final class UploadPostController extends WorkflowController -{ - public function getDescription(): string - { - return 'Uploads an image resource.'; - } - - public function getResponseParameters(): ParametersInterface - { - return - parameters( - document: stringParameter() - ); - } - - public function run( - #[ParameterAttribute(description: 'A binary file, base64 data, or an URL for an image.')] - string $source, // try: files - #[ParameterAttribute(description: 'API V1 key.')] - string $key, - #[ParameterAttribute( - description: 'Response document output format.', - regex: '/^(json|txt|redirect)$/' - )] - string $format = 'json' - ): array { - return data(); - } -} diff --git a/app/src/Controllers/Api/V4/Album/AlbumDeleteController.php b/app/src/Controllers/Api/V4/Album/AlbumDeleteController.php deleted file mode 100644 index 786b37e..0000000 --- a/app/src/Controllers/Api/V4/Album/AlbumDeleteController.php +++ /dev/null @@ -1,36 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\Album; - -use Chevere\Controller\Attributes\RelationWorkflow; -use Chevere\Parameter\Attributes\ParameterAttribute; -use Chevereto\Controllers\WorkflowController; -use Chevereto\Workflows\Album\AlbumDeleteWorkflow; - -#[RelationWorkflow(AlbumDeleteWorkflow::class)] -final class AlbumDeleteController extends WorkflowController -{ - public function getDescription(): string - { - return 'Delete an album identified by its id.'; - } - - public function run( - #[ParameterAttribute( - description: 'The identifier.', - regex: '/\w+/' - )] - string $id - ): array { - return []; - } -} diff --git a/app/src/Controllers/Api/V4/Album/AlbumGetController.php b/app/src/Controllers/Api/V4/Album/AlbumGetController.php deleted file mode 100644 index 1b540e2..0000000 --- a/app/src/Controllers/Api/V4/Album/AlbumGetController.php +++ /dev/null @@ -1,36 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\Album; - -use Chevere\Controller\Attributes\RelationWorkflow; -use Chevere\Parameter\Attributes\ParameterAttribute; -use Chevereto\Controllers\WorkflowController; -use Chevereto\Workflows\Album\AlbumGetWorkflow; - -#[RelationWorkflow(AlbumGetWorkflow::class)] -final class AlbumGetController extends WorkflowController -{ - public function getDescription(): string - { - return 'Get an album identified by its id.'; - } - - public function run( - #[ParameterAttribute( - description: 'The identifier.', - regex: '/\w+/' - )] - string $id - ): array { - return []; - } -} diff --git a/app/src/Controllers/Api/V4/Album/AlbumPatchController.php b/app/src/Controllers/Api/V4/Album/AlbumPatchController.php deleted file mode 100644 index 62d6704..0000000 --- a/app/src/Controllers/Api/V4/Album/AlbumPatchController.php +++ /dev/null @@ -1,52 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\Album; - -use Chevere\Controller\Attributes\RelationWorkflow; -use Chevere\Parameter\Attributes\ParameterAttribute; -use Chevere\Parameter\Interfaces\ParametersInterface; -use function Chevere\Parameter\objectParameter; -use function Chevere\Parameter\parameters; -use Chevereto\Controllers\WorkflowController; -use Chevereto\Workflows\Album\AlbumPatchWorkflow; - -#[RelationWorkflow(AlbumPatchWorkflow::class)] -final class AlbumPatchController extends WorkflowController -{ - public function getDescription(): string - { - return 'Updates the album.'; - } - - public function run( - #[ParameterAttribute( - description: 'The identifier.', - regex: '/\w+/' - )] - string $id, - #[ParameterAttribute( - description: 'The image identifier.', - regex: '/\w+/' - )] - string $cover_id - ): array { - return []; - } - - public function getResponseParameters(): ParametersInterface - { - return - parameters( - image: objectParameter(Image::class) - ); - } -} diff --git a/app/src/Controllers/Api/V4/Album/AlbumPostController.php b/app/src/Controllers/Api/V4/Album/AlbumPostController.php deleted file mode 100644 index 9287ced..0000000 --- a/app/src/Controllers/Api/V4/Album/AlbumPostController.php +++ /dev/null @@ -1,48 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\Album; - -use Chevere\Controller\Attributes\RelationWorkflow; -use Chevere\Parameter\Interfaces\ParametersInterface; -use function Chevere\Parameter\objectParameter; -use function Chevere\Parameter\parameters; -use Chevereto\Controllers\WorkflowController; -use Chevereto\Workflows\Album\AlbumPostWorkflow; - -#[RelationWorkflow(AlbumPostWorkflow::class)] -final class AlbumPostController extends WorkflowController -{ - public function getDescription(): string - { - return 'Creates an album.'; - } - - public function run( - string $description, - string $name, - string $parent_id, - string $password, - string $privacy, - ): array { - return []; - } - - public function getResponseParameters(): ParametersInterface - { - return - parameters( - album: objectParameter( - className: Album::class - ), - ); - } -} diff --git a/app/src/Controllers/Api/V4/Album/Like/AlbumLikeDeleteController.php b/app/src/Controllers/Api/V4/Album/Like/AlbumLikeDeleteController.php deleted file mode 100644 index 985467d..0000000 --- a/app/src/Controllers/Api/V4/Album/Like/AlbumLikeDeleteController.php +++ /dev/null @@ -1,36 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\Album\Like; - -use Chevere\Controller\Attributes\RelationWorkflow; -use Chevere\Parameter\Attributes\ParameterAttribute; -use Chevereto\Controllers\WorkflowController; -use Chevereto\Workflows\Album\Like\AlbumLikeDeleteWorkflow; - -#[RelationWorkflow(AlbumLikeDeleteWorkflow::class)] -final class AlbumLikeDeleteController extends WorkflowController -{ - public function getDescription(): string - { - return 'Delete album like.'; - } - - public function run( - #[ParameterAttribute( - description: 'The identifier.', - regex: '/\w+/' - )] - string $id - ): array { - return []; - } -} diff --git a/app/src/Controllers/Api/V4/Album/Like/AlbumLikePostController.php b/app/src/Controllers/Api/V4/Album/Like/AlbumLikePostController.php deleted file mode 100644 index 440a1d7..0000000 --- a/app/src/Controllers/Api/V4/Album/Like/AlbumLikePostController.php +++ /dev/null @@ -1,36 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\Album\Like; - -use Chevere\Controller\Attributes\RelationWorkflow; -use Chevere\Parameter\Attributes\ParameterAttribute; -use Chevereto\Controllers\WorkflowController; -use Chevereto\Workflows\Album\Like\AlbumLikePostWorkflow; - -#[RelationWorkflow(AlbumLikePostWorkflow::class)] -final class AlbumLikePostController extends WorkflowController -{ - public function getDescription(): string - { - return 'Like the album.'; - } - - public function run( - #[ParameterAttribute( - description: 'The identifier.', - regex: '/\w+/' - )] - string $id - ): array { - return []; - } -} diff --git a/app/src/Controllers/Api/V4/Ban/Ip/BanIpDeleteController.php b/app/src/Controllers/Api/V4/Ban/Ip/BanIpDeleteController.php deleted file mode 100644 index 1857ad9..0000000 --- a/app/src/Controllers/Api/V4/Ban/Ip/BanIpDeleteController.php +++ /dev/null @@ -1,36 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\Ban\Ip; - -use Chevere\Controller\Attributes\RelationWorkflow; -use Chevere\Parameter\Attributes\ParameterAttribute; -use Chevereto\Controllers\WorkflowController; -use Chevereto\Workflows\Ban\Ip\BanIpDeleteWorkflow; - -#[RelationWorkflow(BanIpDeleteWorkflow::class)] -final class BanIpDeleteController extends WorkflowController -{ - public function getDescription(): string - { - return 'Delete an IP ban identified by its id.'; - } - - public function run( - #[ParameterAttribute( - description: 'The identifier.', - regex: '/\w+/' - )] - string $id - ): array { - return []; - } -} diff --git a/app/src/Controllers/Api/V4/Ban/Ip/BanIpPatchController.php b/app/src/Controllers/Api/V4/Ban/Ip/BanIpPatchController.php deleted file mode 100644 index 3567677..0000000 --- a/app/src/Controllers/Api/V4/Ban/Ip/BanIpPatchController.php +++ /dev/null @@ -1,52 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\Ban\Ip; - -use Chevere\Controller\Attributes\RelationWorkflow; -use Chevere\Parameter\Attributes\ParameterAttribute; -use Chevere\Parameter\Interfaces\ParametersInterface; -use function Chevere\Parameter\objectParameter; -use function Chevere\Parameter\parameters; -use Chevereto\Controllers\WorkflowController; -use Chevereto\Workflows\Ban\Ip\BanIpPatchWorkflow; - -#[RelationWorkflow(BanIpPatchWorkflow::class)] -final class BanIpPatchController extends WorkflowController -{ - public function getDescription(): string - { - return 'Updates the album.'; - } - - public function run( - #[ParameterAttribute( - description: 'The album identifier.', - regex: '/\w+/' - )] - string $id, - #[ParameterAttribute( - description: 'The image identifier.', - regex: '/\w+/' - )] - string $cover_id = '' - ): array { - return []; - } - - public function getResponseParameters(): ParametersInterface - { - return - parameters( - image: objectParameter(Image::class) - ); - } -} diff --git a/app/src/Controllers/Api/V4/Ban/Ip/BanIpPostController.php b/app/src/Controllers/Api/V4/Ban/Ip/BanIpPostController.php deleted file mode 100644 index 0f15b02..0000000 --- a/app/src/Controllers/Api/V4/Ban/Ip/BanIpPostController.php +++ /dev/null @@ -1,47 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\Ban\Ip; - -use Chevere\Parameter\Attributes\ParameterAttribute; -use Chevere\Parameter\Interfaces\ParametersInterface; -use function Chevere\Parameter\objectParameter; -use function Chevere\Parameter\parameters; -use Chevereto\Controllers\WorkflowController; - -final class BanIpPostController extends WorkflowController -{ - public function getDescription(): string - { - return 'Creates a IP ban.'; - } - - public function run( - string $ip, - #[ParameterAttribute( - regex: '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/' - )] - string $expires, - string $message - ): array { - return []; - } - - public function getResponseParameters(): ParametersInterface - { - return - parameters( - ban_ip: objectParameter( - className: BanIp::class - ), - ); - } -} diff --git a/app/src/Controllers/Api/V4/Category/CategoryPostController.php b/app/src/Controllers/Api/V4/Category/CategoryPostController.php deleted file mode 100644 index 4a66c46..0000000 --- a/app/src/Controllers/Api/V4/Category/CategoryPostController.php +++ /dev/null @@ -1,48 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\Category; - -use Chevere\Parameter\Attributes\ParameterAttribute; -use Chevere\Parameter\Interfaces\ParametersInterface; -use function Chevere\Parameter\objectParameter; -use function Chevere\Parameter\parameters; -use Chevereto\Controllers\WorkflowController; - -final class CategoryPostController extends WorkflowController -{ - public function getDescription(): string - { - return 'Creates a category.'; - } - - public function run( - string $name, - #[ParameterAttribute( - description: 'Category URL key (slug)', - regex: '/^[-\w]+$/', - )] - string $url_key, - string $description - ): array { - return []; - } - - public function getResponseParameters(): ParametersInterface - { - return - parameters( - category: objectParameter( - className: Category::class - ), - ); - } -} diff --git a/app/src/Controllers/Api/V4/File/FilePostController.php b/app/src/Controllers/Api/V4/File/FilePostController.php deleted file mode 100644 index 23e82fb..0000000 --- a/app/src/Controllers/Api/V4/File/FilePostController.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\File; - -use Chevere\Controller\Controller; -use function Chevere\DataStructure\data; - -abstract class FilePostController extends Controller -{ - public function run( - string $source - ): array { - return data(); - } -} diff --git a/app/src/Controllers/Api/V4/Image/Bulk/ImageBulkPatchController.php b/app/src/Controllers/Api/V4/Image/Bulk/ImageBulkPatchController.php deleted file mode 100644 index 240461a..0000000 --- a/app/src/Controllers/Api/V4/Image/Bulk/ImageBulkPatchController.php +++ /dev/null @@ -1,51 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\Image\Bulk; - -use Chevere\Controller\Attributes\RelationWorkflow; -use Chevere\Parameter\ArrayParameter; -use Chevere\Parameter\Attributes\ParameterAttribute; -use Chevere\Parameter\Interfaces\ParametersInterface; -use function Chevere\Parameter\parameters; -use Chevereto\Controllers\WorkflowController; -use Chevereto\Workflows\Image\Bulk\ImageBulkPatchWorkflow; - -#[RelationWorkflow(ImageBulkPatchWorkflow::class)] -class ImageBulkPatchController extends WorkflowController -{ - public function getDescription(): string - { - return 'Bulk image edit.'; - } - - public function run( - #[ParameterAttribute( - description: 'Comma-separated list of images to edit.', - regex: '/^\w+(,+\w+)*$/' - )] - string $image_ids, - string $category_id = '', - string $is_approved = '', - string $is_nsfw = '', - ): array { - return []; - } - - public function getResponseParameters(): ParametersInterface - { - return - parameters( - edited: new ArrayParameter(), - failed: new ArrayParameter(), - ); - } -} diff --git a/app/src/Controllers/Api/V4/Image/ImageGetController.php b/app/src/Controllers/Api/V4/Image/ImageGetController.php deleted file mode 100644 index fd57f3a..0000000 --- a/app/src/Controllers/Api/V4/Image/ImageGetController.php +++ /dev/null @@ -1,36 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\Image; - -use Chevere\Controller\Attributes\RelationWorkflow; -use Chevere\Parameter\Attributes\ParameterAttribute; -use Chevereto\Controllers\WorkflowController; -use Chevereto\Workflows\Image\ImageGetWorkflow; - -#[RelationWorkflow(ImageGetWorkflow::class)] -final class ImageGetController extends WorkflowController -{ - public function getDescription(): string - { - return 'Get the image identified by its id.'; - } - - public function run( - #[ParameterAttribute( - description: 'The image identifier.', - regex: '/\w+/' - )] - string $id - ): array { - return []; - } -} diff --git a/app/src/Controllers/Api/V4/Image/ImagePatchController.php b/app/src/Controllers/Api/V4/Image/ImagePatchController.php deleted file mode 100644 index 37ee74a..0000000 --- a/app/src/Controllers/Api/V4/Image/ImagePatchController.php +++ /dev/null @@ -1,56 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\Image; - -use Chevere\Controller\Attributes\RelationWorkflow; -use Chevere\Parameter\Attributes\ParameterAttribute; -use Chevere\Parameter\Interfaces\ParametersInterface; -use function Chevere\Parameter\objectParameter; -use function Chevere\Parameter\parameters; -use Chevereto\Controllers\WorkflowController; -use Chevereto\Workflows\Image\ImagePatchWorkflow; - -#[RelationWorkflow(ImagePatchWorkflow::class)] -class ImagePatchController extends WorkflowController -{ - public function getDescription(): string - { - return 'Edit the image resource.'; - } - - public function run( - #[ParameterAttribute( - description: 'The image identifier.', - regex: '/\w+/' - )] - string $id, - string $category_id, - #[ParameterAttribute( - regex: '/^(0|1)$/' - )] - string $is_approved, - #[ParameterAttribute( - regex: '/^(0|1)$/' - )] - string $is_nsfw - ): array { - return []; - } - - public function getResponseParameters(): ParametersInterface - { - return - parameters( - image: objectParameter(stdClass::class, Image::class), - ); - } -} diff --git a/app/src/Controllers/Api/V4/Image/ImagePostController.php b/app/src/Controllers/Api/V4/Image/ImagePostController.php deleted file mode 100644 index 508af7b..0000000 --- a/app/src/Controllers/Api/V4/Image/ImagePostController.php +++ /dev/null @@ -1,63 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\Image; - -use Chevere\Controller\Controller; -use function Chevere\DataStructure\data; -use Chevere\Parameter\Attributes\ParameterAttribute; -use Chevere\Parameter\Interfaces\ParametersInterface; -use function Chevere\Parameter\objectParameter; -use function Chevere\Parameter\parameters; -use Chevereto\Workflows\Image\ImagePostWorkflow; - -final class ImagePostController extends Controller -{ - public function run( - #[ParameterAttribute( - description: 'A binary file, base64 data, or an URL for an image.', - // try: 'files' - )] - string $image, - string $album_id = '', - ): array { - $workflow = (new ImagePostWorkflow())->getWorkflow(); - // $source - // $mimes - // $max_bytes - // $min_bytes - // $max_width - // $max_height - // $min_width - // $min_height - // $ip - // $ip_version - // $user_id - // $table - // $name - // $naming - // $path - // $upload_filepath - // $expires - // $album_id - return data(); - } - - public function getResponseParameters(): ParametersInterface - { - return - parameters( - image: objectParameter( - className: Image::class - ) - ); - } -} diff --git a/app/src/Controllers/Api/V4/Image/Like/ImageLikeDeleteController.php b/app/src/Controllers/Api/V4/Image/Like/ImageLikeDeleteController.php deleted file mode 100644 index 10c5ef8..0000000 --- a/app/src/Controllers/Api/V4/Image/Like/ImageLikeDeleteController.php +++ /dev/null @@ -1,33 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\Image\Like; - -use Chevere\Parameter\Attributes\ParameterAttribute; -use Chevereto\Controllers\WorkflowController; - -class ImageLikeDeleteController extends WorkflowController -{ - public function getDescription(): string - { - return 'Delete image like.'; - } - - public function run( - #[ParameterAttribute( - description: 'The image identifier.', - regex: '/\w+/' - )] - string $id, - ): array { - return []; - } -} diff --git a/app/src/Controllers/Api/V4/Image/Like/ImageLikePostController.php b/app/src/Controllers/Api/V4/Image/Like/ImageLikePostController.php deleted file mode 100644 index 3a3c7c6..0000000 --- a/app/src/Controllers/Api/V4/Image/Like/ImageLikePostController.php +++ /dev/null @@ -1,33 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\Image\Like; - -use Chevere\Parameter\Attributes\ParameterAttribute; -use Chevereto\Controllers\WorkflowController; - -class ImageLikePostController extends WorkflowController -{ - public function getDescription(): string - { - return 'Like the image.'; - } - - public function run( - #[ParameterAttribute( - description: ('The image identifier.'), - regex: '/\w+/' - )] - string $id - ): array { - return []; - } -} diff --git a/app/src/Controllers/Api/V4/Stat/Rebuild/StatRebuildPostController.php b/app/src/Controllers/Api/V4/Stat/Rebuild/StatRebuildPostController.php deleted file mode 100644 index 22aae27..0000000 --- a/app/src/Controllers/Api/V4/Stat/Rebuild/StatRebuildPostController.php +++ /dev/null @@ -1,22 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\Stat\Rebuild; - -use Chevereto\Controllers\WorkflowController; - -class StatRebuildPostController extends WorkflowController -{ - public function getDescription(): string - { - return 'Rebuild stats.'; - } -} diff --git a/app/src/Controllers/Api/V4/Storage/Migrate/StorageMigratePostController.php b/app/src/Controllers/Api/V4/Storage/Migrate/StorageMigratePostController.php deleted file mode 100644 index 209b34c..0000000 --- a/app/src/Controllers/Api/V4/Storage/Migrate/StorageMigratePostController.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\Storage\Migrate; - -use Chevereto\Controllers\WorkflowController; - -class StorageMigratePostController extends WorkflowController -{ - public function getDescription(): string - { - return 'Migrate all stored content to another storage.'; - } - - public function run(string $storage_id): array - { - return []; - } -} diff --git a/app/src/Controllers/Api/V4/Storage/Stat/Regen/StorageStatRegenPostController.php b/app/src/Controllers/Api/V4/Storage/Stat/Regen/StorageStatRegenPostController.php deleted file mode 100644 index 56dc12d..0000000 --- a/app/src/Controllers/Api/V4/Storage/Stat/Regen/StorageStatRegenPostController.php +++ /dev/null @@ -1,39 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\Storage\Stat\Regen; - -use Chevere\Parameter\Interfaces\ParametersInterface; -use function Chevere\Parameter\objectParameter; -use function Chevere\Parameter\parameters; -use Chevereto\Controllers\WorkflowController; -use stdClass; - -class StorageStatRegenPostController extends WorkflowController -{ - public function getDescription(): string - { - return 'Regenerate storage stats.'; - } - - public function run(string $storage_id): array - { - return []; - } - - public function getResponseParameters(): ParametersInterface - { - return - parameters( - storage: objectParameter(stdClass::class, Storage::class), - ); - } -} diff --git a/app/src/Controllers/Api/V4/Storage/StoragePostController.php b/app/src/Controllers/Api/V4/Storage/StoragePostController.php deleted file mode 100644 index d5468bb..0000000 --- a/app/src/Controllers/Api/V4/Storage/StoragePostController.php +++ /dev/null @@ -1,53 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\Storage; - -use Chevere\Parameter\Interfaces\ParametersInterface; -use function Chevere\Parameter\objectParameter; -use function Chevere\Parameter\parameters; -use Chevereto\Controllers\WorkflowController; - -class StoragePostController extends WorkflowController -{ - public function getDescription(): string - { - return 'Creates a storage.'; - } - - public function run( - string $account_id, - string $account_name, - string $api_id, - string $bucket, - string $capacity, - string $id, - string $key, - string $name, - string $region, - string $secret, - string $server, - string $service, - string $url, - ): array { - return []; - } - - public function getResponseParameters(): ParametersInterface - { - return - parameters( - storage: objectParameter( - className: Storage::class - ), - ); - } -} diff --git a/app/src/Controllers/Api/V4/Tool/Id/Decode/ToolDecodeIdGetController.php b/app/src/Controllers/Api/V4/Tool/Id/Decode/ToolDecodeIdGetController.php deleted file mode 100644 index f4de8e3..0000000 --- a/app/src/Controllers/Api/V4/Tool/Id/Decode/ToolDecodeIdGetController.php +++ /dev/null @@ -1,38 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\Tool\Id\Decode; - -use Chevere\Parameter\Interfaces\ParametersInterface; -use function Chevere\Parameter\parameters; -use function Chevere\Parameter\stringParameter; -use Chevereto\Controllers\WorkflowController; - -class ToolDecodeIdGetController extends WorkflowController -{ - public function getDescription(): string - { - return 'Retrieve a decoded representation of the Id.'; - } - - public function run(string $id): array - { - return []; - } - - public function getResponseParameters(): ParametersInterface - { - return - parameters( - data: stringParameter(), - ); - } -} diff --git a/app/src/Controllers/Api/V4/Tool/Id/Encode/ToolEncodeIdGetController.php b/app/src/Controllers/Api/V4/Tool/Id/Encode/ToolEncodeIdGetController.php deleted file mode 100644 index 1a7e66a..0000000 --- a/app/src/Controllers/Api/V4/Tool/Id/Encode/ToolEncodeIdGetController.php +++ /dev/null @@ -1,38 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\Tool\Id\Encode; - -use Chevere\Parameter\Interfaces\ParametersInterface; -use function Chevere\Parameter\parameters; -use function Chevere\Parameter\stringParameter; -use Chevereto\Controllers\WorkflowController; - -class ToolEncodeIdGetController extends WorkflowController -{ - public function getDescription(): string - { - return 'Retrieve an encoded representation of the Id.'; - } - - public function run(string $id): array - { - return []; - } - - public function getResponseParameters(): ParametersInterface - { - return - parameters( - data: stringParameter(), - ); - } -} diff --git a/app/src/Controllers/Api/V4/Tool/Probe/Email/ToolProbeEmailPostController.php b/app/src/Controllers/Api/V4/Tool/Probe/Email/ToolProbeEmailPostController.php deleted file mode 100644 index b81569f..0000000 --- a/app/src/Controllers/Api/V4/Tool/Probe/Email/ToolProbeEmailPostController.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\Tool\Probe\Email; - -use Chevereto\Controllers\WorkflowController; - -class ToolProbeEmailPostController extends WorkflowController -{ - public function getDescription(): string - { - return 'Probe email delivery.'; - } - - public function run(string $email): array - { - return []; - } -} diff --git a/app/src/Controllers/Api/V4/User/Asset/Avatar/UserAssetAvatarDeleteController.php b/app/src/Controllers/Api/V4/User/Asset/Avatar/UserAssetAvatarDeleteController.php deleted file mode 100644 index a8fa2b8..0000000 --- a/app/src/Controllers/Api/V4/User/Asset/Avatar/UserAssetAvatarDeleteController.php +++ /dev/null @@ -1,36 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\User\Asset\Avatar; - -use Chevere\Controller\Attributes\RelationWorkflow; -use Chevere\Parameter\Attributes\ParameterAttribute; -use Chevereto\Controllers\WorkflowController; -use Chevereto\Workflows\User\Asset\Avatar\UserAssetAvatarDeleteWorkflow; - -#[RelationWorkflow(UserAssetAvatarDeleteWorkflow::class)] -final class UserAssetAvatarDeleteController extends WorkflowController -{ - public function getDescription(): string - { - return 'Delete the user avatar image resource.'; - } - - public function run( - #[ParameterAttribute( - description: 'The username.', - regex: '/\w+/' - )] - string $username - ): array { - return []; - } -} diff --git a/app/src/Controllers/Api/V4/User/Asset/Avatar/UserAssetAvatarPostController.php b/app/src/Controllers/Api/V4/User/Asset/Avatar/UserAssetAvatarPostController.php deleted file mode 100644 index 5229cc2..0000000 --- a/app/src/Controllers/Api/V4/User/Asset/Avatar/UserAssetAvatarPostController.php +++ /dev/null @@ -1,38 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\User\Asset\Avatar; - -use Chevere\Controller\Attributes\RelationWorkflow; -use Chevere\Parameter\Interfaces\ParametersInterface; -use function Chevere\Parameter\objectParameter; -use function Chevere\Parameter\parameters; -use Chevereto\Controllers\Api\V4\File\FilePostController; -use Chevereto\Workflows\User\Asset\Avatar\UserAssetAvatarPostWorkflow; - -#[RelationWorkflow(UserAssetAvatarPostWorkflow::class)] -final class UserAssetAvatarPostController extends FilePostController -{ - public function getDescription(): string - { - return 'Uploads an image resource to be used as user avatar'; - } - - public function getResponseParameters(): ParametersInterface - { - return - parameters( - file_info: objectParameter( - className: FileInfo::class - ) - ); - } -} diff --git a/app/src/Controllers/Api/V4/User/Asset/Background/UserAssetBackgroundDeleteController.php b/app/src/Controllers/Api/V4/User/Asset/Background/UserAssetBackgroundDeleteController.php deleted file mode 100644 index f592ce7..0000000 --- a/app/src/Controllers/Api/V4/User/Asset/Background/UserAssetBackgroundDeleteController.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\User\Asset\Background; - -use Chevere\Controller\Attributes\RelationWorkflow; -use Chevereto\Controllers\WorkflowController; -use Chevereto\Workflows\User\Asset\Background\UserAssetBackgroundDeleteWorkflow; - -#[RelationWorkflow(UserAssetBackgroundDeleteWorkflow::class)] -final class UserAssetBackgroundDeleteController extends WorkflowController -{ - public function getDescription(): string - { - return 'Delete the user background image resource.'; - } -} diff --git a/app/src/Controllers/Api/V4/User/Asset/Background/UserAssetBackgroundPostController.php b/app/src/Controllers/Api/V4/User/Asset/Background/UserAssetBackgroundPostController.php deleted file mode 100644 index ba7f5ae..0000000 --- a/app/src/Controllers/Api/V4/User/Asset/Background/UserAssetBackgroundPostController.php +++ /dev/null @@ -1,38 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\User\Asset\Background; - -use Chevere\Controller\Attributes\RelationWorkflow; -use Chevere\Parameter\Interfaces\ParametersInterface; -use function Chevere\Parameter\objectParameter; -use function Chevere\Parameter\parameters; -use Chevereto\Controllers\Api\V4\File\FilePostController; -use Chevereto\Workflows\User\Asset\Background\UserAssetBackgroundPostWorkflow; - -#[RelationWorkflow(UserAssetBackgroundPostWorkflow::class)] -final class UserAssetBackgroundPostController extends FilePostController -{ - public function getDescription(): string - { - return 'Uploads an image resource to be used as user background.'; - } - - public function getResponseParameters(): ParametersInterface - { - return - parameters( - file_info: objectParameter( - className: FileInfo::class - ) - ); - } -} diff --git a/app/src/Controllers/Api/V4/User/Export/UserExportGetController.php b/app/src/Controllers/Api/V4/User/Export/UserExportGetController.php deleted file mode 100644 index 59516fa..0000000 --- a/app/src/Controllers/Api/V4/User/Export/UserExportGetController.php +++ /dev/null @@ -1,39 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\User\Export; - -use Chevere\Parameter\Interfaces\ParametersInterface; -use function Chevere\Parameter\objectParameter; -use function Chevere\Parameter\parameters; -use Chevereto\Controllers\WorkflowController; -use stdClass; - -class UserExportGetController extends WorkflowController -{ - public function getDescription(): string - { - return 'Exports the user.'; - } - - public function run(string $username): array - { - return []; - } - - public function getResponseParameters(): ParametersInterface - { - return - parameters( - key: objectParameter(stdClass::class, 'className'), - ); - } -} diff --git a/app/src/Controllers/Api/V4/User/Follow/UserFollowDeleteController.php b/app/src/Controllers/Api/V4/User/Follow/UserFollowDeleteController.php deleted file mode 100644 index 4042a73..0000000 --- a/app/src/Controllers/Api/V4/User/Follow/UserFollowDeleteController.php +++ /dev/null @@ -1,33 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\User\Follow; - -use Chevere\Parameter\Attributes\ParameterAttribute; -use Chevereto\Controllers\WorkflowController; - -class UserFollowDeleteController extends WorkflowController -{ - public function getDescription(): string - { - return 'Unfollow the user.'; - } - - public function run( - #[ParameterAttribute( - description: 'The username.', - regex: '/\w+/' - )] - string $username - ): array { - return []; - } -} diff --git a/app/src/Controllers/Api/V4/User/Follow/UserFollowPostController.php b/app/src/Controllers/Api/V4/User/Follow/UserFollowPostController.php deleted file mode 100644 index 7514dcb..0000000 --- a/app/src/Controllers/Api/V4/User/Follow/UserFollowPostController.php +++ /dev/null @@ -1,33 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\User\Follow; - -use Chevere\Parameter\Attributes\ParameterAttribute; -use Chevereto\Controllers\WorkflowController; - -class UserFollowPostController extends WorkflowController -{ - public function getDescription(): string - { - return 'Follows the user.'; - } - - public function run( - #[ParameterAttribute( - description: 'The username.', - regex: '/\w+/' - )] - string $username - ): array { - return []; - } -} diff --git a/app/src/Controllers/Api/V4/User/Setting/UserSettingPatchController.php b/app/src/Controllers/Api/V4/User/Setting/UserSettingPatchController.php deleted file mode 100644 index 873075a..0000000 --- a/app/src/Controllers/Api/V4/User/Setting/UserSettingPatchController.php +++ /dev/null @@ -1,46 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\User\Setting; - -use Chevere\Controller\Attributes\RelationWorkflow; -use Chevere\Parameter\Attributes\ParameterAttribute; -use Chevere\Parameter\Interfaces\ParametersInterface; -use function Chevere\Parameter\objectParameter; -use function Chevere\Parameter\parameters; -use Chevereto\Controllers\WorkflowController; -use stdClass; - -#[RelationWorkflow('')] -class UserSettingPatchController extends WorkflowController -{ - public function getDescription(): string - { - return 'Updates user settings.'; - } - - public function run( - #[ParameterAttribute( - description: 'The user identifier.' - )] - string $userId - ): array { - return []; - } - - public function getResponseParameters(): ParametersInterface - { - return - parameters( - key: objectParameter(stdClass::class, 'className'), - ); - } -} diff --git a/app/src/Controllers/Api/V4/User/UserGetController.php b/app/src/Controllers/Api/V4/User/UserGetController.php deleted file mode 100644 index 47cb91b..0000000 --- a/app/src/Controllers/Api/V4/User/UserGetController.php +++ /dev/null @@ -1,33 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\User; - -use Chevere\Parameter\Attributes\ParameterAttribute; -use Chevereto\Controllers\WorkflowController; - -final class UserGetController extends WorkflowController -{ - public function getDescription(): string - { - return 'Get an user identified by its id.'; - } - - public function run( - #[ParameterAttribute( - description: 'The user identifier.', - regex: '/\w+/', - )] - string $id - ): array { - return []; - } -} diff --git a/app/src/Controllers/Api/V4/User/UserPostController.php b/app/src/Controllers/Api/V4/User/UserPostController.php deleted file mode 100644 index df86346..0000000 --- a/app/src/Controllers/Api/V4/User/UserPostController.php +++ /dev/null @@ -1,60 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers\Api\V4\User; - -use Chevere\Parameter\Attributes\ParameterAttribute; -use Chevere\Parameter\Interfaces\ParametersInterface; -use function Chevere\Parameter\objectParameter; -use function Chevere\Parameter\parameters; -use Chevereto\Controllers\WorkflowController; -use Chevereto\Workflows\User\UserPostWorkflow; - -final class UserPostController extends WorkflowController -{ - public function getWorkflowName(): string - { - return UserPostWorkflow::class; - } - - public function getDescription(): string - { - return 'Creates an user.'; - } - - public function run( - #[ParameterAttribute( - regex: '/^[\w]{3,16}$/' - )] - string $username, - string $email, - #[ParameterAttribute( - regex: '/^.{6,128}$/' - )] - string $password, - #[ParameterAttribute( - regex: '/^(user|manager|admin)$/', - )] - string $role = 'user' - ): array { - return []; - } - - public function getResponseParameters(): ParametersInterface - { - return - parameters( - user: objectParameter( - className: User::class - ) - ); - } -} diff --git a/app/src/Controllers/LegacyController.php b/app/src/Controllers/LegacyController.php deleted file mode 100644 index b9fbc1b..0000000 --- a/app/src/Controllers/LegacyController.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers; - -use Chevere\Controller\Controller; -use Chevere\Parameter\Interfaces\ArgumentsInterface; -use Chevere\Response\Interfaces\ResponseInterface; - -final class LegacyController extends Controller -{ - final public function run(ArgumentsInterface $arguments): ResponseInterface - { - return $this - ->getResponse( - document: $arguments->getString('document'), - ); - } -} diff --git a/app/src/Controllers/WorkflowController.php b/app/src/Controllers/WorkflowController.php deleted file mode 100644 index bbb315e..0000000 --- a/app/src/Controllers/WorkflowController.php +++ /dev/null @@ -1,45 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Controllers; - -use Chevere\Controller\Controller; -use Chevere\Workflow\Interfaces\WorkflowInterface; -use Chevere\Workflow\Interfaces\WorkflowProviderInterface; -use LogicException; -use function Chevere\Message\message; - -abstract class WorkflowController extends Controller -{ - final public function getWorkflow(): WorkflowInterface - { - $relation = $this->relation(); - if ($relation === '') { - throw new LogicException( - message: message('Missing workflow provider relationship') - ); - } - if (! is_subclass_of($relation, WorkflowProviderInterface::class, true)) { - throw new LogicException( - message: message( - 'Relation` %relation%` is not of type `%type%`', - relation: $relation, - type: WorkflowProviderInterface::class, - ) - ); - } - /** @var WorkflowProviderInterface $workflowProvider */ - $workflowProvider = new $relation(); - - return $workflowProvider->getWorkflow(); - // $this->hook('getWorkflow:after', $workflow); - } -} diff --git a/app/src/Controllers/functions.php b/app/src/Controllers/functions.php deleted file mode 100644 index 97831c6..0000000 --- a/app/src/Controllers/functions.php +++ /dev/null @@ -1,23 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - - -namespace App\Controllers; - -use Chevereto\Controllers\LegacyController; - -/** - * @var String $route `name.php` file. - */ -function legacyController(string $route) -{ - return new LegacyController(dispatch: $route); -} diff --git a/app/src/Database/Database.php b/app/src/Database/Database.php deleted file mode 100644 index b7ef8f1..0000000 --- a/app/src/Database/Database.php +++ /dev/null @@ -1,33 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - - -namespace Chevereto\Database; - -use Doctrine\DBAL\Connection; -use Doctrine\DBAL\Query\QueryBuilder; - -final class Database -{ - private Connection $connection; - - private QueryBuilder $queryBuilder; - - public function __construct(Connection $connection) - { - $this->connection = $connection; - } - - public function getQueryBuilder(): QueryBuilder - { - return new QueryBuilder($this->connection); - } -} diff --git a/app/src/Database/EntitiesIo.php b/app/src/Database/EntitiesIo.php deleted file mode 100644 index bdececd..0000000 --- a/app/src/Database/EntitiesIo.php +++ /dev/null @@ -1,71 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Database; - -use Chevereto\Database\Traits\GetWhereEqualsTrait; -use Doctrine\DBAL\Result; -use OutOfBoundsException; - -/** - * Provides database I/O for the X entities. - */ -abstract class EntitiesIo implements EntitiesIoInterface -{ - use GetWhereEqualsTrait; - - protected Database $database; - - public function __construct(Database $database) - { - $this->database = $database; - } - - abstract public function table(): string; - - public function selectWhereAllValues(array $columns = ['*'], string ...$values): array - { - return $this->selectWhereValues($columns, ...$values); - } - - public function selectWhereAnyValues(array $columns = ['*'], string ...$values): array - { - return $this->selectWhereValues($columns, ...$values); - } - - protected function selectWhereValues(array $columns = ['*'], string ...$values): array - { - $all = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['function'] === 'selectWhereAllValues'; - $queryBuilder = $this->database->getQueryBuilder() - ->select(...$columns) - ->from($this->table()); - foreach ($values as $column => $value) { - $column = (string) $column; - $where = $this->getWhereEquals($column); - if ($all) { - $queryBuilder->andWhere($where); - } else { - $queryBuilder->orWhere($where); - } - $queryBuilder->setParameter($column, $value); - } - /** @var Result $result */ - $result = $queryBuilder->execute(); - $fetch = $result->fetchAllAssociative(); - if ($fetch === false) { - throw new OutOfBoundsException( - 'No record exists for values provided' - ); - } - - return $fetch; - } -} diff --git a/app/src/Database/EntitiesIoInterface.php b/app/src/Database/EntitiesIoInterface.php deleted file mode 100644 index bb46d98..0000000 --- a/app/src/Database/EntitiesIoInterface.php +++ /dev/null @@ -1,39 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Database; - -/** - * Describes the component in charge of providing multiple entity I/O interaction. - */ -interface EntitiesIoInterface -{ - public function __construct(Database $database); - - /** - * Defines the table name. - */ - public function table(): string; - - /** - * Select the entities for the given values (all). - * - * @return array Raw associative result. - */ - public function selectWhereAllValues(array $columns = ['*'], string ...$values): array; - - /** - * Select the entities for the given values (any). - * - * @return array Raw associative result. - */ - public function selectWhereAnyValues(array $columns = ['*'], string ...$values): array; -} diff --git a/app/src/Database/EntityIo.php b/app/src/Database/EntityIo.php deleted file mode 100644 index 871b4aa..0000000 --- a/app/src/Database/EntityIo.php +++ /dev/null @@ -1,102 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Database; - -use Chevereto\Database\Traits\GetWhereEqualsTrait; -use Doctrine\DBAL\ParameterType; -use Doctrine\DBAL\Result; -use OutOfBoundsException; -use function Chevere\Message\message; - -/** - * Provides database I/O for the X entity. - */ -abstract class EntityIo implements EntityIoInterface -{ - use GetWhereEqualsTrait; - - protected string $whereIdClause; - - public function __construct( - protected Database $database - ) { - $this->whereIdClause = $this->getWhereEquals($this->id()); - } - - abstract public function table(): string; - - abstract public function id(): string; - - public function select(int $id, string ...$columns): array - { - $args = empty($columns) ? ['*'] : $columns; - $queryBuilder = $this->database->getQueryBuilder() - ->select(...$args) - ->from($this->table()) - ->where($this->whereIdClause) - ->setParameter($this->id(), $id, ParameterType::INTEGER); - /** @var Result $result */ - $result = $queryBuilder->execute(); - $fetch = $result->fetchAssociative(); - if ($fetch === false) { - throw new OutOfBoundsException( - message('No record exists for id `%id%`', (string) $id) - ); - } - - return $fetch; - } - - public function delete(int $id): int - { - return $this->database->getQueryBuilder() - ->delete($this->table()) - ->where($this->whereIdClause) - ->setParameter($this->id(), $id, ParameterType::INTEGER) - ->execute(); - } - - public function update(int $id, string ...$values): int - { - $queryBuilder = $this->database->getQueryBuilder() - ->update($this->table()); - foreach ($values as $column => $value) { - $column = (string) $column; - $queryBuilder - ->set($column, ":{$column}") - ->setParameter($column, $value); - } - - return $queryBuilder - ->where($this->whereIdClause) - ->setParameter($this->id(), $id, ParameterType::INTEGER) - ->execute(); - } - - public function insert(string ...$values): int - { - $queryBuilder = $this->database->getQueryBuilder() - ->insert($this->table()); - foreach ($values as $column => $value) { - $column = (string) $column; - $queryBuilder - ->setValue($column, ":{$column}") - ->setParameter($column, $value); - } - $result = $queryBuilder->execute(); - if ($result === 1) { - return (int) $queryBuilder->getConnection()->lastInsertId(); - } - - return 0; - } -} diff --git a/app/src/Database/EntityIoInterface.php b/app/src/Database/EntityIoInterface.php deleted file mode 100644 index a4646d2..0000000 --- a/app/src/Database/EntityIoInterface.php +++ /dev/null @@ -1,52 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Database; - -/** - * Describes the component in charge of providing entity I/O interaction. - */ -interface EntityIoInterface -{ - public function __construct(Database $database); - - /** - * Defines the table name. - */ - public function table(): string; - - /** - * Defines the column id name. - */ - public function id(): string; - - /** - * Select the entity columns identified by its id. - * - * @return array Raw associative result. - */ - public function select(int $id, string ...$columns): array; - - /** - * @return int Number of deleted rows `0`, `1`. - */ - public function delete(int $id): int; - - /** - * @return int Number of updated rows. - */ - public function update(int $id, string ...$values): int; - - /** - * @return int Last inserted Id. - */ - public function insert(string ...$values): int; -} diff --git a/app/src/Database/Traits/GetWhereEqualsTrait.php b/app/src/Database/Traits/GetWhereEqualsTrait.php deleted file mode 100644 index 13f5ec2..0000000 --- a/app/src/Database/Traits/GetWhereEqualsTrait.php +++ /dev/null @@ -1,20 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Database\Traits; - -trait GetWhereEqualsTrait -{ - private function getWhereEquals(string $column): string - { - return str_replace('%s', $column, '%s = :%s'); - } -} diff --git a/app/src/Encoding/functions.php b/app/src/Encoding/functions.php deleted file mode 100644 index a156bdf..0000000 --- a/app/src/Encoding/functions.php +++ /dev/null @@ -1,70 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Encoding; - -use Chevere\Regex\Interfaces\RegexInterface; -use Chevere\Regex\Regex; -use InvalidArgumentException; -use RuntimeException; -use Safe\Exceptions\FilesystemException; -use Safe\Exceptions\StreamException; -use function Chevere\Message\message; -use function Safe\fclose; -use function Safe\fopen; -use function Safe\fwrite; -use function Safe\stream_filter_append; - -/** - * @throws InvalidArgumentException - */ -function assertBase64(string $string): void -{ - $double = base64_encode(base64_decode($string, true)); - if ($string !== $double) { - // @codeCoverageIgnoreStart - throw new InvalidArgumentException( - message('Invalid base64 formatting'), - 600 - ); - // @codeCoverageIgnoreEnd - } - unset($double); -} - -/** - * @param string $base64 A base64 encoded string - * @param string $filepath Filename or stream to store decoded base64 - * - * @throws FilesystemException - * @throws StreamException - * @throws RuntimeException - */ -function storeDecodedBase64(string $base64, string $filepath): void -{ - $filter = 'convert.base64-decode'; - $fh = fopen($filepath, 'w'); - stream_filter_append($fh, $filter, STREAM_FILTER_WRITE); - if (fwrite($fh, $base64) === 0) { - // @codeCoverageIgnoreStart - throw new RuntimeException( - (string) message('Unable to write `%filter%` provided string', filter: $filter), - 1200 - ); - // @codeCoverageIgnoreEnd - } - fclose($fh); -} - -function getBase64Regex(): RegexInterface -{ - return new Regex('/^[a-zA-Z0-9\/\r\n+]*={0,2}$/'); -} diff --git a/app/src/File/functions.php b/app/src/File/functions.php deleted file mode 100644 index 89f7547..0000000 --- a/app/src/File/functions.php +++ /dev/null @@ -1,38 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\File; - -use GuzzleHttp\Client; -use LogicException; -use Throwable; -use function Safe\file_put_contents; - -function storeDownloadedUrl(string $url, string $filepath) -{ - $clientArgs = [ - 'base_uri' => $url, - // 'timeout' => $_ENV['CHEVERETO_HTTP_TIMEOUT'] ?? 30, - ]; - - // @codeCoverageIgnoreStart - // if (isset($_ENV['CHEVERETO_HTTP_PROXY'])) { - // $clientArgs['proxy'] = $_ENV['CHEVERETO_HTTP_PROXY']; - // } - // @codeCoverageIgnoreEnd - try { - $httpClient = new Client($clientArgs); - $response = $httpClient->request('GET'); - } catch (Throwable $e) { - throw new LogicException(previous: $e); - } - file_put_contents($filepath, $response->getBody()); -} diff --git a/app/src/HashId/HashId.php b/app/src/HashId/HashId.php deleted file mode 100644 index e131f05..0000000 --- a/app/src/HashId/HashId.php +++ /dev/null @@ -1,113 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\HashId; - -use InvalidArgumentException; -use function Chevere\Message\message; - -/** - * Provides encoding/decoding for integer IDs. - */ -final class HashId -{ - private string $alphabet; - - private string $salt; - - private int $padding; - - private string $hash; - - private string $index; - - private array $table; - - private int $base; - - private string $baseString; - - public function __construct(string $salt) - { - $this->assertSalt($salt); - $this->salt = $salt; - $this->padding = 0; - $this->alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; - $index = str_split($this->alphabet, 1); - $this->hash = hash('sha256', $this->salt); - $this->table = []; - for ($n = 0; $n < strlen($this->alphabet); ++$n) { - $this->table[] = substr($this->hash, $n, 1); - } - array_multisort($this->table, SORT_DESC, $index); - $this->index = implode('', $index); - $this->base = strlen($this->index); - $this->baseString = (string) $this->base; - } - - public function withPadding(int $padding): self - { - $new = clone $this; - $this->assertPadding($padding); - $new->padding = $padding; - - return $new; - } - - public function decode(string $alpha): int - { - $out = 0; - $len = strlen($alpha) - 1; - for ($i = 0; $i <= $len; ++$i) { - $bcpow = bcpow($this->baseString, (string) ($len - $i)); - $out = $out + strpos($this->index, substr($alpha, $i, 1)) * $bcpow; - } - if ($this->padding > 0) { - $out = $out / $this->padding; - } - - return (int) $out; - } - - public function encode(int $id): string - { - if ($this->padding > 0) { - $id = $id * $this->padding; - } - $out = ''; - for ($i = floor(log((float) $id, $this->base)); $i >= 0; --$i) { - $bcpow = bcpow($this->baseString, (string) $i); - $start = floor($id / $bcpow) % $this->base; - $out = $out . substr($this->index, $start, 1); - $id = $id - ($start * $bcpow); - } - - return $out; - } - - private function assertSalt(string $salt): void - { - if (empty($salt) || ctype_space($salt)) { - throw new InvalidArgumentException( - message('Invalid salt provided'), - ); - } - } - - private function assertPadding(int $padding): void - { - if ($padding < 0) { - throw new InvalidArgumentException( - message('Padding must be greater than zero'), - ); - } - } -} diff --git a/app/src/Image/ImageHashInstance.php b/app/src/Image/ImageHashInstance.php deleted file mode 100644 index 518f344..0000000 --- a/app/src/Image/ImageHashInstance.php +++ /dev/null @@ -1,40 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Image; - -use Jenssegers\ImageHash\ImageHash; -use LogicException; -use function Chevere\Message\message; - -/** - * @codeCoverageIgnore - */ -final class ImageHashInstance -{ - private static ?ImageHash $instance; - - public function __construct(ImageHash $imageHash) - { - self::$instance = $imageHash; - } - - public static function get(): ImageHash - { - if (! isset(self::$instance)) { - throw new LogicException( - message('No `%instance%` instance present', s: ImageHash::class) - ); - } - - return self::$instance; - } -} diff --git a/app/src/Image/ImageManagerInstance.php b/app/src/Image/ImageManagerInstance.php deleted file mode 100644 index 00a4cce..0000000 --- a/app/src/Image/ImageManagerInstance.php +++ /dev/null @@ -1,37 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Image; - -use Intervention\Image\ImageManager; -use LogicException; - -/** - * @codeCoverageIgnore - */ -final class ImageManagerInstance -{ - private static ?ImageManager $instance; - - public function __construct(ImageManager $imageManager) - { - self::$instance = $imageManager; - } - - public static function get(): ImageManager - { - if (! isset(self::$instance)) { - throw new LogicException('No ImageManager instance present'); - } - - return self::$instance; - } -} diff --git a/app/src/Image/functions.php b/app/src/Image/functions.php deleted file mode 100644 index f11df93..0000000 --- a/app/src/Image/functions.php +++ /dev/null @@ -1,63 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Image; - -use Intervention\Image\ImageManager; -use Jenssegers\ImageHash\ImageHash; -use Jenssegers\ImageHash\Implementations\DifferenceHash; -use RuntimeException; -use Throwable; - -function hasExtGd(): bool -{ - return extension_loaded('gd') && function_exists('gd_info'); -} - -function hasExtImagick(): bool -{ - return extension_loaded('imagick') && class_exists('Imagick'); -} - -function imageManager(): ImageManager -{ - try { - return ImageManagerInstance::get(); - } catch (Throwable) { - $driver = match (true) { - hasExtImagick() => 'Imagick', - hasExtGd() => 'Gd', - default => '', - }; - if ($driver === '') { - throw new RuntimeException('No image driver available'); - } - $manager = new ImageManager([ - 'driver' => $driver, - ]); - new ImageManagerInstance($manager); - - return ImageManagerInstance::get(); - } -} - -function imageHash(): ImageHash -{ - try { - return ImageHashInstance::get(); - } catch (Throwable) { - new ImageHashInstance( - new ImageHash(new DifferenceHash(16)) - ); - - return ImageHashInstance::get(); - } -} diff --git a/app/src/Job/Job.php b/app/src/Job/Job.php deleted file mode 100644 index 4191aee..0000000 --- a/app/src/Job/Job.php +++ /dev/null @@ -1,48 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Job; - -use Chevere\Parameter\Interfaces\ArgumentsInterface; -use Chevereto\Controllers\WorkflowController; -use Ramsey\Uuid\Uuid; - -final class Job -{ - private string $id; - - private string $document; - - public function __construct( - private WorkflowController $controller, - private ArgumentsInterface $arguments - ) { - $this->id = Uuid::uuid4()->toString(); - } - - public function withDocument(string $document): self - { - $new = clone $this; - $new->document = $document; - - return $new; - } - - public function id(): string - { - return $this->id; - } - - public function document(): string - { - return $this->document ??= 'document'; - } -} diff --git a/app/src/Legacy/Classes/Album.php b/app/src/Legacy/Classes/Album.php index 69c91f4..3a993f3 100644 --- a/app/src/Legacy/Classes/Album.php +++ b/app/src/Legacy/Classes/Album.php @@ -40,6 +40,59 @@ use function Emoji\detect_emoji; class Album { + public const COLUMNS = [ + 'album_id', + 'album_name', + 'album_user_id', + 'album_date', + 'album_date_gmt', + 'album_creation_ip', + 'album_privacy', + 'album_privacy_extra', + 'album_password', + 'album_image_count', + 'album_description', + 'album_likes', + 'album_views', + 'album_cover_id', + 'album_parent_id', + 'album_cta_enable', + 'album_cta', + 'user_id', + 'user_name', + 'user_username', + // 'user_date', + // 'user_date_gmt', + // 'user_email', + 'user_avatar_filename', + 'user_facebook_username', + 'user_twitter_username', + 'user_website', + 'user_background_filename', + // 'user_bio', + // 'user_timezone', + // 'user_language', + 'user_status', + 'user_is_admin', + 'user_is_manager', + 'user_is_private', + // 'user_palette_id', + // 'user_newsletter_subscribe', + // 'user_show_nsfw_listings', + // 'user_image_count', + // 'user_album_count', + // 'user_image_keep_exif', + // 'user_file_meta_tag_camera_model', + // 'user_image_expiration', + // 'user_registration_ip', + 'user_likes', + 'user_liked', + 'user_following', + 'user_followers', + 'user_content_views', + // 'user_notifications_unread', + ]; + public const ENCRYPTED_NAMES = [ 'password', ]; @@ -50,8 +103,8 @@ class Album bool $pretty = true, array $requester = [] ): array { + $columns = self::COLUMNS; $tables = DB::getTables(); - $query = 'SELECT * FROM ' . $tables['albums'] . "\n"; $joins = [ 'LEFT JOIN ' . $tables['users'] . ' ON ' . $tables['albums'] . '.album_user_id = ' . $tables['users'] . '.user_id', ]; @@ -59,7 +112,24 @@ class Album if (version_compare(cheveretoVersionInstalled(), '3.9.0', '>=')) { $joins[] = 'LEFT JOIN ' . $tables['likes'] . ' ON ' . $tables['likes'] . '.like_content_type = "album" AND ' . $tables['albums'] . '.album_id = ' . $tables['likes'] . '.like_content_id AND ' . $tables['likes'] . '.like_user_id = ' . $requester['id']; } + array_push( + $columns, + ...[ + // 'like_id', + // 'like_date', + // 'like_date_gmt', + 'like_user_id', + // 'like_content_type', + // 'like_content_id', + // 'like_content_user_id', + // 'like_ip', + ] + ); } + $columnsString = implode(', ', $columns); + $query = "SELECT {$columnsString} FROM " + . $tables['albums'] + . "\n"; $query .= implode("\n", $joins) . "\n"; $query .= 'WHERE album_id=:album_id;' . "\n"; if ($sumView) { @@ -89,7 +159,7 @@ class Album } return $pretty - ? self::formatArray($album_db) + ? self::formatArray($album_db, fillCover: false) : self::cipherAwareDbRow($album_db); } @@ -99,7 +169,10 @@ class Album throw new Exception('Empty ids provided', 600); } $tables = DB::getTables(); - $query = 'SELECT * FROM ' . $tables['albums'] . "\n"; + $columnsString = implode(', ', self::COLUMNS); + $query = "SELECT {$columnsString} FROM " + . $tables['albums'] + . "\n"; $joins = [ 'LEFT JOIN ' . $tables['users'] . ' ON ' . $tables['albums'] . '.album_user_id = ' . $tables['users'] . '.user_id', ]; @@ -243,17 +316,15 @@ class Album } } $insert = DB::insert('albums', $album_array); - if (Login::isLoggedUser()) { + if ($values['user_id'] ?? false) { DB::increment('users', [ 'album_count' => '+1', ], [ 'id' => $values['user_id'], ]); - } else { - $addValue = session()['guest_albums'] ?? []; - $addValue[] = $insert; - sessionVar()->put('guest_albums', $addValue); + User::deleteAlbumsCache($values['user_id']); } + Stat::track([ 'action' => 'insert', 'table' => 'albums', @@ -278,7 +349,7 @@ class Album } $children = implode(',', $children); $tableAlbums = DB::getTable('albums'); - $query = << $id, ]); + if ($return) { + $db = DB::getInstance(); + $tableAlbums = DB::getTable('albums'); + $db->query( + <<bind(':album_id', $id); + $fetchSingle = $db->fetchSingle(); + if ($fetchSingle) { + $userId = $fetchSingle['album_user_id']; + User::deleteAlbumsCache($userId); + } + } + + return $return; } public static function populateCover(int $id) @@ -429,7 +519,7 @@ class Album public static function delete(int $id): int { $images_deleted = 0; - $user_id = DB::get('albums', [ + $userId = DB::get('albums', [ 'id' => $id, ])[0]['album_user_id'] ?? null; $album = self::getSingle($id); @@ -460,13 +550,13 @@ class Album $images_deleted++; } } - if (isset($user_id)) { + if (isset($userId)) { $user_updated_counts = [ 'album_count' => '-1', 'image_count' => '-' . $images_deleted, ]; DB::increment('users', $user_updated_counts, [ - 'id' => $user_id, + 'id' => $userId, ]); } DB::delete('notifications', [ @@ -479,6 +569,8 @@ class Album 'value' => '-1', 'date_gmt' => $album['date_gmt'], ]); + Listing::deleteTypeIdCache('a', $id); + User::deleteAlbumsCache($userId); return $images_deleted; } @@ -509,25 +601,16 @@ class Album return $db->exec(); } - public static function fill(array &$album, array &$user = []) + public static function fill(array &$album, array &$user = [], bool $fillCover = true) { static::fillEssential($album, $user); if (! empty($user)) { User::fill($user); } - $display_url = ''; - $display_width = ''; - $display_height = ''; + $display_url = $album['display_url'] ?? ''; + $display_width = $album['display_width'] ?? ''; + $display_height = $album['display_height'] ?? ''; if (! empty($album['cover_id'])) { - $image = Image::getSingle((int) $album['cover_id']); - if ($image !== []) { - $image = DB::formatRow($image); - unset($image['album']); - Image::fill($image); - $display_url = $image['display_url']; - $display_width = $image['display_width']; - $display_height = $image['display_height']; - } $album['cover_id_encoded'] = encodeID((int) $album['cover_id']); } if (! empty($album['parent_id'])) { @@ -627,27 +710,27 @@ class Album $album['name_truncated_html'] = safe_html($album['name_truncated']); } - public static function cipherAwareDbRow(array &$dbrow): array + public static function cipherAwareDbRow(array &$row): array { - if (isset($dbrow['album_password']) && hasEncryption()) { + if (isset($row['album_password']) && hasEncryption()) { try { - $dbrow['album_password'] = decrypt($dbrow['album_password']); + $row['album_password'] = decrypt($row['album_password']); } catch (Throwable) { - $dbrow['album_password'] = $dbrow['album_password']; + $row['album_password'] = $row['album_password']; } } - return $dbrow; + return $row; } - public static function formatArray(array $dbrow, bool $safe = false): array + public static function formatArray(array $row, bool $safe = false, bool $fillCover = true): array { - self::cipherAwareDbRow($dbrow); - $output = DB::formatRow($dbrow); + self::cipherAwareDbRow($row); + $output = DB::formatRow($row); if (! isset($output['user'])) { $output['user'] = []; } - self::fill($output, $output['user']); + self::fill($output, $output['user'], $fillCover); $output['views_label'] = _n('view', 'views', $output['views'] ?? 0); $output['how_long_ago'] = time_elapsed_string($output['date_gmt'] ?? ''); if (isset($output['images_slice'])) { @@ -705,7 +788,7 @@ class Album int $limit = 5, ): array { $tableAlbums = DB::getTable('albums'); - $query = << + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Chevereto\Legacy\Classes; + +final class Cache +{ + private static KeyValueInterface $instance; + + public function __construct(KeyValueInterface $keyValue) + { + self::$instance = $keyValue; + } + + public static function isEnabled(): bool + { + return ! (self::$instance instanceof KeyValueNull); + } + + public static function instance(): KeyValueInterface + { + return self::$instance; + } + + public static function hash(string $message): string + { + if (function_exists('sodium_crypto_generichash')) { + return bin2hex(sodium_crypto_generichash($message, '', 20)); + } + + return sha1($message); + } +} diff --git a/app/src/Legacy/Classes/Categories.php b/app/src/Legacy/Classes/Categories.php new file mode 100644 index 0000000..b8c4dff --- /dev/null +++ b/app/src/Legacy/Classes/Categories.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Chevereto\Legacy\Classes; + +use Throwable; +use function Chevereto\Legacy\G\get_base_url; + +final class Categories +{ + public const CACHE_KEY = 'categories'; + + public static function get(): array + { + $categories = []; + $cached = Cache::instance()->get(self::CACHE_KEY); + if ($cached) { + return $cached; + } + + try { + $columns = [ + 'category_id', + 'category_name', + 'category_url_key', + 'category_description', + ]; + $columnsString = implode(', ', $columns); + $rows = DB::queryFetchAll( + "SELECT {$columnsString} FROM " + . DB::getTable('categories') + . ' ORDER BY category_name ASC;' + ); + foreach ($rows as $v) { + $key = $v['category_id']; + $v['category_url'] = get_base_url('category/' . $v['category_url_key']); + $categories[$key] = DB::formatRow($v); + } + } catch (Throwable) { + } + + Cache::instance()->set(self::CACHE_KEY, $categories, 3600); + + return $categories; + } + + public static function deleteCache(): void + { + Cache::instance()->delete(self::CACHE_KEY); + } +} diff --git a/app/src/Legacy/Classes/DB.php b/app/src/Legacy/Classes/DB.php index 7e7ca68..64b4354 100644 --- a/app/src/Legacy/Classes/DB.php +++ b/app/src/Legacy/Classes/DB.php @@ -13,6 +13,7 @@ namespace Chevereto\Legacy\Classes; use Chevereto\Legacy\G\DB as GDB; use PDO; +use function Chevere\Message\message; use function Chevereto\Legacy\G\starts_with; use function Chevereto\Vars\env; @@ -52,6 +53,8 @@ class DB extends GDB 'two_factors', 'users', 'variables', + 'uploads', + 'uploads_chunks', ]; public const PREFIX_TO_TABLE = [ @@ -61,6 +64,7 @@ class DB extends GDB 'tag_file' => 'tags_files', 'tag_user' => 'tags_users', 'tag_album' => 'tags_albums', + 'upload_chunk' => 'uploads_chunks', ]; public const TABLES_TO_PREFIX = [ @@ -70,6 +74,7 @@ class DB extends GDB 'tags_files' => 'tag_file', 'tags_users' => 'tag_user', 'tags_albums' => 'tag_album', + 'uploads_chunks' => 'upload_chunk', ]; public static function getTable(string $table): string @@ -94,14 +99,15 @@ class DB extends GDB array $sort = [], ?int $limit = null, int $fetch_style = PDO::FETCH_ASSOC, - array $valuesOperators = [] + array $valuesOperators = [], + array $columns = [], ): mixed { $prefix = self::getFieldPrefix($table); $where = self::getPrefixedValues($prefix, $where); $valuesOperators = self::getPrefixedValues($prefix, $valuesOperators); $sort = self::getPrefixedSort($prefix, $sort); - return GDB::get($table, $where, $clause, $sort, $limit, $fetch_style, $valuesOperators); + return GDB::get($table, $where, $clause, $sort, $limit, $fetch_style, $valuesOperators, $columns); } public static function update( @@ -219,6 +225,13 @@ class DB extends GDB return rtrim($table, 's'); } + public static function translate(string $query, string|int|float ...$pair) + { + $pair['table_prefix'] = env()['CHEVERETO_DB_TABLE_PREFIX']; + + return message($query, ...$pair)->__toString(); + } + protected static function getPrefixedValues(string $prefix, array|string $values): array|string { if (! is_array($values)) { diff --git a/app/src/Legacy/Classes/ExifTool.php b/app/src/Legacy/Classes/ExifTool.php new file mode 100644 index 0000000..c3dc5d6 --- /dev/null +++ b/app/src/Legacy/Classes/ExifTool.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Chevereto\Legacy\Classes; + +use Chevereto\Legacy\Classes\Traits\BinaryTrait; +use Symfony\Component\Process\Exception\ProcessFailedException; +use Symfony\Component\Process\Process; + +final class ExifTool +{ + use BinaryTrait; + + public function name(): string + { + return 'ExifTool'; + } + + public function version(): string + { + $process = new Process([$this->path, '-ver']); + $process->run(); + if (! $process->isSuccessful()) { + throw new ProcessFailedException($process); + } + + return trim($process->getOutput()); + } + + public function strip(string $path): void + { + $process = new Process([ + $this->path, + '-all=', + '-overwrite_original', + $path, + ]); + $process->run(); + if (! $process->isSuccessful()) { + throw new ProcessFailedException($process); + } + } +} diff --git a/app/src/Legacy/Classes/ExifTran.php b/app/src/Legacy/Classes/ExifTran.php new file mode 100644 index 0000000..84c7354 --- /dev/null +++ b/app/src/Legacy/Classes/ExifTran.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Chevereto\Legacy\Classes; + +use Chevereto\Legacy\Classes\Traits\BinaryTrait; +use Symfony\Component\Process\Exception\ProcessFailedException; +use Symfony\Component\Process\Process; + +final class ExifTran +{ + use BinaryTrait; + + public function name(): string + { + return 'ExifTran'; + } + + public function orientate(string $path): void + { + $process = new Process([ + $this->path, + '-a', + '-i', + $path, + ]); + $process->run(); + if (! $process->isSuccessful()) { + throw new ProcessFailedException($process); + } + } +} diff --git a/app/src/Legacy/Classes/Image.php b/app/src/Legacy/Classes/Image.php index 322c553..31f09d2 100644 --- a/app/src/Legacy/Classes/Image.php +++ b/app/src/Legacy/Classes/Image.php @@ -45,7 +45,7 @@ use function Chevereto\Legacy\G\format_bytes; use function Chevereto\Legacy\G\get_bytes; use function Chevereto\Legacy\G\get_client_ip; use function Chevereto\Legacy\G\get_ffmpeg_error; -use function Chevereto\Legacy\G\get_image_fileinfo as GGet_image_fileinfo; +use function Chevereto\Legacy\G\get_image_fileinfo; use function Chevereto\Legacy\G\get_mimetype; use function Chevereto\Legacy\G\get_public_url; use function Chevereto\Legacy\G\is_animated_image; @@ -58,6 +58,7 @@ use function Chevereto\Legacy\G\unlinkIfExists; use function Chevereto\Legacy\G\url_to_relative; use function Chevereto\Legacy\get_fileinfo; use function Chevereto\Legacy\getSetting; +use function Chevereto\Legacy\hashFile; use function Chevereto\Legacy\time_elapsed_string; use function Chevereto\Vars\env; use function Chevereto\Vars\session; @@ -66,6 +67,144 @@ use function Safe\password_hash; class Image { + public const COLUMNS = [ + 'images' => [ + 'image_id', + 'image_name', + 'image_extension', + 'image_size', + 'image_width', + 'image_height', + 'image_date', + 'image_date_gmt', + 'image_title', + 'image_description', + 'image_nsfw', + 'image_user_id', + 'image_album_id', + 'image_uploader_ip', + 'image_storage_mode', + 'image_path', + 'image_storage_id', + 'image_checksum', + 'image_source_checksum', + 'image_original_filename', + 'image_original_exifdata', + 'image_views', + 'image_category_id', + 'image_chain', + 'image_thumb_size', + 'image_medium_size', + 'image_frame_size', + 'image_expiration_date_gmt', + 'image_likes', + 'image_is_animated', + 'image_is_approved', + 'image_is_360', + 'image_duration', + 'image_type', + ], + 'users' => [ + 'user_id', + 'user_name', + 'user_username', + 'user_date', + 'user_date_gmt', + 'user_email', + 'user_avatar_filename', + 'user_facebook_username', + 'user_twitter_username', + 'user_website', + 'user_background_filename', + 'user_bio', + 'user_timezone', + 'user_language', + 'user_status', + 'user_is_admin', + 'user_is_manager', + 'user_is_private', + 'user_palette_id', + 'user_newsletter_subscribe', + 'user_show_nsfw_listings', + 'user_image_count', + 'user_album_count', + 'user_image_keep_exif', + 'user_file_meta_tag_camera_model', + 'user_image_expiration', + 'user_registration_ip', + 'user_likes', + 'user_liked', + 'user_following', + 'user_followers', + 'user_content_views', + // 'user_notifications_unread', + ], + 'albums' => [ + 'album_id', + 'album_name', + 'album_user_id', + 'album_date', + 'album_date_gmt', + 'album_creation_ip', + 'album_privacy', + 'album_privacy_extra', + 'album_password', + 'album_image_count', + 'album_description', + 'album_likes', + 'album_views', + 'album_cover_id', + 'album_parent_id', + 'album_cta_enable', + 'album_cta', + ], + 'tags' => [ + 'tag_id', + 'tag_name', + 'tag_description', + 'tag_user_id', + 'tag_date_gmt', + 'tag_files', + 'tag_views', + ], + 'likes' => [ + 'like_id', + 'like_date', + 'like_date_gmt', + 'like_user_id', + 'like_content_type', + 'like_content_id', + 'like_content_user_id', + 'like_ip', + ], + 'storages' => [ + 'storage_id', + // 'storage_api_id', + 'storage_name', + // 'storage_service', + 'storage_url', + // 'storage_bucket', + // 'storage_region', + // 'storage_server', + // 'storage_account_id', + // 'storage_account_name', + // 'storage_key', + // 'storage_secret', + // 'storage_is_https', + 'storage_is_active', + 'storage_capacity', + // 'storage_space_used', + 'storage_type_chain', + // 'storage_use_path_style_endpoint', + // 'storage_deleted_at', + ], + 'storage_apis' => [ + 'storage_api_id', + 'storage_api_name', + 'storage_api_type', + ], + ]; + public static array $table_chv_image = [ 'name', 'extension', @@ -80,8 +219,8 @@ class Image 'uploader_ip', 'storage_mode', 'storage_id', - 'md5', - 'source_md5', + 'checksum', + 'source_checksum', 'original_filename', 'original_exifdata', 'category_id', @@ -148,9 +287,6 @@ class Image array $requester = [] ): array { $tables = DB::getTables(); - $query = 'SELECT * FROM ' - . $tables['images'] - . "\n"; $joins = [ 'LEFT JOIN ' . $tables['storages'] @@ -181,6 +317,17 @@ class Image . $tables['albums'] . '.album_id', ]; + $storageApiColumns = self::COLUMNS['storage_apis']; + $find = array_search('storage_api_id', $storageApiColumns); + unset($storageApiColumns[$find]); + $storageApiColumns[] = DB::getTable('storages') . '.' . 'storage_api_id'; + $columns = array_merge( + self::COLUMNS['images'], + self::COLUMNS['storages'], + $storageApiColumns, + self::COLUMNS['users'], + self::COLUMNS['albums'] + ); if ($requester !== []) { if (version_compare(cheveretoVersionInstalled(), '3.7.0', '>=')) { $joins[] = 'LEFT JOIN ' @@ -195,8 +342,14 @@ class Image . $tables['likes'] . '.like_user_id = ' . $requester['id']; + $columns = array_merge($columns, self::COLUMNS['likes']); } } + $columnsString = implode(', ', $columns); + $query = "SELECT {$columnsString} FROM " + . $tables['images'] + . "\n"; + $query .= implode("\n", $joins) . "\n"; $query .= 'WHERE image_id=:image_id;' . "\n"; if ($sumView) { @@ -248,7 +401,7 @@ class Image $image_db['image_tags'] = $image_tags; $image_db['image_tags_string'] = $image_tags_string; $return = $image_db; - $return = $pretty ? self::formatArray($return) : $return; + $return = $pretty ? self::formatArray($return, fillAlbumCover: false) : $return; if (! isset($return['file_resource'])) { $return['file_resource'] = self::getSrcTargetSingle($image_db); } @@ -262,7 +415,15 @@ class Image throw new Exception('Null $ids provided in Image::get_multiple', 600); } $tables = DB::getTables(); - $query = 'SELECT * FROM ' . $tables['images'] . "\n"; + $columns = array_merge( + self::COLUMNS['images'], + self::COLUMNS['users'], + self::COLUMNS['albums'] + ); + $columnsString = implode(', ', $columns); + $query = "SELECT {$columnsString} FROM " + . $tables['images'] + . "\n"; $joins = [ 'LEFT JOIN ' . $tables['users'] . ' ON ' . $tables['images'] . '.image_user_id = ' . $tables['users'] . '.user_id', 'LEFT JOIN ' . $tables['albums'] . ' ON ' . $tables['images'] . '.image_album_id = ' . $tables['albums'] . '.album_id', @@ -355,7 +516,7 @@ class Image } $images = []; foreach ($list as $v) { - $format = self::formatArray($v); + $format = self::formatArray($v, fillAlbumCover: false); $images[$format['id']] = $format; } if ($prevListing->output !== [] && $prevListing->count > 1) { @@ -496,24 +657,34 @@ class Image return $return; } - public static function watermarkFromDb(): void + public static function watermarkTempFile(): string + { + return + sys_get_temp_dir() + . '/chv' + . env()['CHEVERETO_ID'] + . '_image_' + . getSetting('watermark_image'); + } + + public static function watermarkFromDb(): string { - $file = PATH_PUBLIC_CONTENT_IMAGES_SYSTEM . getSetting('watermark_image'); $assetsDb = DB::get('assets', [ 'key' => 'watermark_image', ], 'AND', [], 1); if ($assetsDb === false) { - return; + return PATH_PUBLIC_CONTENT_IMAGES_SYSTEM . getSetting('watermark_image'); } + $file = static::watermarkTempFile(); if (file_exists($file) - && md5_file($file) !== $assetsDb['asset_md5'] + && hashFile($file) !== $assetsDb['asset_checksum'] && ! starts_with('default/', getSetting('watermark_image')) ) { unlinkIfExists($file); } if (! file_exists($file)) { $fh = fopen($file, 'w'); - $st = ! $fh || fwrite($fh, $assetsDb['asset_blob']) === false ? false : true; + $st = $fh && fwrite($fh, $assetsDb['asset_blob']) !== false; fclose($fh); if (! $st) { throw new LogicException( @@ -522,6 +693,11 @@ class Image ); } } + if (! is_readable($file)) { + throw new Exception("Can't read watermark file at " . $file, 600); + } + + return $file; } public static function watermark(string $image_path, array $options = []): bool @@ -529,12 +705,8 @@ class Image $options = array_merge([ 'ratio' => getSetting('watermark_percentage') / 100, 'position' => explode(' ', getSetting('watermark_position')), - 'file' => PATH_PUBLIC_CONTENT_IMAGES_SYSTEM . getSetting('watermark_image'), + 'file' => self::watermarkFromDb(), ], $options); - self::watermarkFromDb(); - if (! is_readable($options['file'])) { - throw new Exception("Can't read watermark file at " . $options['file'], 600); - } $image = ImageManagerStatic::make($image_path); $options['ratio'] = min(1, (is_numeric($options['ratio']) ? max(0.01, $options['ratio']) : 0.01)); if (! in_array($options['position'][0], ['left', 'center', 'right'], true)) { @@ -604,7 +776,8 @@ class Image string|null $filename = null, array $options = [], int|null $storage_id = null, - bool $guestSessionHandle = true + bool $guestSessionHandle = true, + string $checksum = null ): array { if ((! (bool) env()['CHEVERETO_ENABLE_LOCAL_STORAGE'])) { if ($storage_id === null) { @@ -631,6 +804,9 @@ class Image if ($guestSessionHandle === false) { $upload->detectFlood = false; } + if ($checksum != null) { + $upload->setChecksum($checksum); + } $upload->exec(); return [ @@ -640,30 +816,19 @@ 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 $timePeriod = 'P1D'): bool + public static function isDuplicatedChunkUpload(string $checksum, string $timePeriod = 'P1D'): bool { - if (is_array($source) && isset($source['tmp_name'])) { - $filename = $source['tmp_name']; - if (stream_resolve_include_path($filename) === false) { - throw new Exception("Concurrency: {$filename} is gone", 666); - } - $md5_file = md5_file($filename); - } else { - $filename = $source; - $md5_file = $filename; - } - if ($md5_file === false) { - throw new Exception('Unable to process md5_file', 600); - } $db = DB::getInstance(); + $tableUploads = DB::getTable('uploads'); $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' + << :date_gmt; + MySQL ); - $db->bind(':md5', $md5_file); + $db->bind(':checksum', $checksum); $db->bind(':ip', get_client_ip()); $db->bind(':date_gmt', datetime_sub(datetimegmt(), $timePeriod)); $db->exec(); @@ -671,6 +836,82 @@ class Image return (bool) $db->fetchColumn(); } + // Mostly for people uploading two times the same image to test or just bug you + // $mixed => $_FILES or checksum string + public static function isDuplicatedUpload(array|string $source, string $timePeriod = 'P1D'): bool + { + if (is_string($source)) { + $checksum = $source; + } else { + if ($source['tmp_name'] ?? false) { + $filename = $source['tmp_name']; + if (stream_resolve_include_path($filename) === false) { + throw new Exception("Concurrency: {$filename} is gone", 666); + } + } + $checksum = $source['checksum'] + ?? hashFile($filename); + } + if ($checksum === false) { + throw new Exception('Unable to process checksum', 600); + } + $db = DB::getInstance(); + $tableImages = DB::getTable('images'); + $db->query( + << :date_gmt; + MySQL + ); + $db->bind(':checksum', $checksum); + $db->bind(':ip', get_client_ip()); + $db->bind(':date_gmt', datetime_sub(datetimegmt(), $timePeriod)); + $db->exec(); + + return (bool) $db->fetchColumn(); + } + + public static function validateParamsUploadToWebsite(array &$params = [], array $user = []): void + { + // Validate user_id existence + // Validate category_id existence + // Validate album_id ownership + // params: + // {"type":"chunked","privacy":"","timestamp":"1744383904543","expiration":"","category_id":"","nsfw":"0","album_id":"","tags":"","mimetype":"image\/jpeg","source":"Sony HV-30 DSC00271.JPG","size":"23122540"} + $params['use_file_date'] = $params['use_file_date'] ?? false; + nullify_string($params['album_id']); + } + + /** + * Uploads an image to the Chevereto website. + * + * Handles the entire image upload process including storage selection, + * file validation, image processing, watermarking, and database insertion. + * + * @param array|string $source Either an array with file data or a URL string to fetch + * @param array $user User array data, empty for guest uploads + * @param array $params Upload parameters that may include: + * - album_id: ID of target album + * - title: Image title + * - description: Image description + * - category_id: Image category ID + * - nsfw: NSFW flag (0 or 1) + * - expiration: Expiration time (seconds) + * - expiration_date_gmt: Direct expiration date in UTC format + * - width: Target width for resizing + * - height: Target height for resizing + * - use_file_date: Use file's EXIF date instead of current date + * - privacy: public, password, private, private_but_link + * - mimetype: Force specific mimetype for the upload + * - tags: Comma-separated tags + * - timestamp: Upload timestamp + * @param bool $guestSessionHandle Whether to handle guest session tracking + * @param string|null $ip Override for uploader IP address + * @return array Array containing [inserted_id, delete_password] + * @throws Exception For various error conditions including duplicates, storage issues, moderation rejection + */ public static function uploadToWebsite( array|string $source, array $user = [], @@ -678,16 +919,15 @@ class Image bool $guestSessionHandle = true, string|null $ip = null ): array { - $params['use_file_date'] = $params['use_file_date'] ?? false; - nullify_string($params['album_id']); + self::validateParamsUploadToWebsite($params); $dateFolder = ''; try { $storage_mode = getSetting('upload_storage_mode'); - $upload_path = ''; + $uploadPath = ''; switch ($storage_mode) { case 'direct': - $upload_path = CHV_PATH_IMAGES; + $uploadPath = CHV_PATH_IMAGES; break; case 'datefolder': @@ -709,26 +949,34 @@ class Image 'date_gmt' => $stockDateGmt, ]; $dateFolder = date('Y/m/d/', strtotime($datefolder_stock['date'])); - $upload_path = CHV_PATH_IMAGES . $dateFolder; + $uploadPath = CHV_PATH_IMAGES . $dateFolder; break; } if (is_string($source)) { - if (! getSetting('enable_uploads_url')) { + if (preg_match('/^CHUNKED_([^_]+)_([a-zA-Z0-9]{64})_([a-zA-Z0-9]{64})$/', $source, $matches)) { + [$tempName, $uploadParams] = Uploads::join( + uploadPath: $uploadPath, + uploadId: decodeID($matches[1]), + token: $matches[2], + hash: $matches[3], + ); + $source = $uploadParams['source']; + } elseif (! getSetting('enable_uploads_url')) { throw new Exception( message('URL uploading is disabled'), 403 ); + $tempName = Upload::getTempNam($uploadPath); + fetch_url($source, $tempName); } - $temp_name = Upload::getTempNam($upload_path); - fetch_url($source, $temp_name); - $mimetype = get_mimetype($temp_name); + $mimetype = get_mimetype($tempName); $source = [ 'name' => basename($source), 'type' => $mimetype, - 'tmp_name' => $temp_name, + 'tmp_name' => $tempName, 'error' => 'UPLOAD_ERR_OK', - 'size' => filesize($temp_name), + 'size' => filesize($tempName), ]; } if ($user !== [] @@ -738,6 +986,7 @@ class Image Settings::setValue('upload_max_filesize_mb', getSetting('upload_max_filesize_mb_bak')); } $do_dupe_check = ! getSetting('enable_duplicate_uploads') && ! ($user['is_admin'] ?? false); + $source['checksum'] = hashFile($source['tmp_name']); if ($do_dupe_check && self::isDuplicatedUpload($source)) { throw new Exception(_s('Duplicated upload'), 101); } @@ -749,6 +998,7 @@ class Image // 'document' => 8, // 'other' => 16, ]; + // @deprecate $params['mimetype'] $mimetype = strtok($params['mimetype'] ?? 'image', '/'); $type_chain = $upload_types[$mimetype] ?? 1; $get_active_storages = Storage::get([ @@ -790,7 +1040,6 @@ class Image if ($storage_id === null && (! (bool) env()['CHEVERETO_ENABLE_LOCAL_STORAGE'])) { throw new LogicException('No storage available', 900); } - $fileNaming = getSetting('upload_filenaming'); if ($fileNaming !== 'id' && in_array($params['privacy'] ?? '', ['password', 'private', 'private_but_link'], true) @@ -815,7 +1064,7 @@ class Image 'date_gmt' => '0000-01-01 00:00:00', 'nsfw' => 0, 'uploader_ip' => '', - 'md5' => '', + 'checksum' => '', 'original_filename' => '', 'chain' => 0, 'thumb_size' => 0, @@ -836,18 +1085,18 @@ class Image $upload_options['allowed_formats'] = self::getEnabledImageExtensions(); $image_upload = self::upload( $source, - $upload_path, + $uploadPath, ($fileNaming === 'id' && isset($target_id)) ? encodeID((int) $target_id) : null, $upload_options, $storage_id, $guestSessionHandle - ); + ); // slow: 6s $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); - } + // if ($do_dupe_check && self::isDuplicatedUpload($image_upload['uploaded']['fileinfo']['checksum'])) { + // throw new Exception(_s('Duplicated upload'), 102); + // } $image_ratio = $image_upload['uploaded']['fileinfo']['ratio']; $must_resize = false; $image_max_size_cfg = [ @@ -907,8 +1156,8 @@ class Image $chain_mask[0] = 1; } if ($must_resize) { - $source_md5 = $image_upload['uploaded']['fileinfo']['md5']; - if ($do_dupe_check && self::isDuplicatedUpload($source_md5)) { + $sourceChecksum = $image_upload['uploaded']['fileinfo']['checksum']; + if ($do_dupe_check && self::isDuplicatedUpload($sourceChecksum)) { throw new Exception(_s('Duplicated upload'), 103); } $image_ratio = $image_upload['uploaded']['fileinfo']['ratio']; @@ -950,7 +1199,7 @@ class Image filename: $image_upload['uploaded']['name'] . '.th', options: $image_thumb_options ); - $original_md5 = $image_upload['source']['fileinfo']['md5']; + $originalChecksum = $image_upload['source']['fileinfo']['checksum']; $watermark_enable = getSetting('watermark_enable'); if ($watermark_enable) { $watermark_user = $user !== [] @@ -979,8 +1228,8 @@ class Image } } if ($apply_watermark && self::watermark($resizeSourceImage)) { - $image_upload['uploaded']['fileinfo'] = GGet_image_fileinfo($resizeSourceImage); - $image_upload['uploaded']['fileinfo']['md5'] = $original_md5; + $image_upload['uploaded']['fileinfo'] = get_image_fileinfo($resizeSourceImage); + $image_upload['uploaded']['fileinfo']['checksum'] = $originalChecksum; } if ($image_upload['uploaded']['fileinfo'][$medium_fixed_dimension] > $medium_size || $is_animated_image @@ -1050,7 +1299,7 @@ class Image '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, + 'source_checksum' => $sourceChecksum ?? null, 'is_360' => $is_360, 'duration' => $image_upload['uploaded']['fileinfo']['duration'] ?? 0, ]; @@ -1171,6 +1420,7 @@ class Image $image_insert_values['id'] = $target_id; } $image_insert_values['title'] = mb_substr($image_insert_values['title'] ?? '', 0, 100, 'UTF-8'); + // Validate user_id album ownership if ($user !== [] && isset($image_insert_values['album_id'])) { $album = Album::getSingle((int) $image_insert_values['album_id']); if (($album['user']['id'] ?? 0) !== $user['id']) { @@ -1216,6 +1466,7 @@ class Image $image_insert_values['album_id'] = $album['id']; } } + if (isset($image_insert_values['album_id'])) { Album::addImage($image_insert_values['album_id'], $uploaded_id); } @@ -1424,7 +1675,6 @@ class Image $binds[':tag_name_' . $pos] = $name; } } - xr(sql: $sql, binds: $binds); $db = DB::getInstance(); $db->query($sql); foreach ($binds as $key => $value) { @@ -1454,7 +1704,7 @@ class Image 'size' => $image[$k]['size'], ]; } - Storage::deleteFiles($targets, $image['storage']); + Storage::deleteFiles($targets, $image['storage']['id']); } if ($update_user && isset($image['user']['id'])) { DB::increment('users', [ @@ -1526,16 +1776,19 @@ class Image 'content_user_id' => $image['user']['id'] ?? null, 'content_ip' => $image['uploader_ip'], 'content_views' => $image['views'], - 'content_md5' => $image['md5'], + 'content_checksum' => $image['checksum'], 'content_likes' => $image['likes'], 'content_original_filename' => $image['original_filename'], ]); $result = DB::delete('images', [ 'id' => $id, ]); - DB::delete('images_hash', [ - 'image_id' => $id, - ]); + if ($result) { + DB::delete('images_hash', [ + 'image_id' => $id, + ]); + Listing::deleteTypeIdCache('i', $id); + } return $result; } @@ -1701,7 +1954,7 @@ class Image ?? ($image['name'] . '.' . $image['extension']); } - public static function formatArray(array $dbRow, bool $safe = false): array + public static function formatArray(array $dbRow, bool $safe = false, bool $fillAlbumCover = true): array { $output = DB::formatRow($dbRow); if (isset($output['user']['id'])) { @@ -1718,7 +1971,8 @@ class Image $output['album']['password'] = $output['album']['password']; } } - Album::fill($output['album'], $output['user']); + $output['album'] ??= []; + Album::fill($output['album'], $output['user'], $fillAlbumCover); } else { unset($output['album']); } @@ -1833,7 +2087,7 @@ class Image } $populate_values = [ 'uploader_ip' => $values['uploader_ip'], - 'md5' => $image_upload['uploaded']['fileinfo']['md5'], + 'checksum' => $image_upload['uploaded']['fileinfo']['checksum'], 'original_filename' => $image_upload['source']['filename'], 'original_exifdata' => $original_exifdata, 'is_360' => $is360, diff --git a/app/src/Legacy/Classes/Import.php b/app/src/Legacy/Classes/Import.php index 93cd7c2..8036b0b 100644 --- a/app/src/Legacy/Classes/Import.php +++ b/app/src/Legacy/Classes/Import.php @@ -757,6 +757,7 @@ class Import try { $params['use_file_date'] = true; + // @deprecate $params['mimetype'] $params['mimetype'] = $mimetype; $user = User::getSingle($user_id, 'id'); $metaFile = $pathName . '.json'; diff --git a/app/src/Legacy/Classes/KeyValue.php b/app/src/Legacy/Classes/KeyValue.php new file mode 100644 index 0000000..5f1b514 --- /dev/null +++ b/app/src/Legacy/Classes/KeyValue.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Chevereto\Legacy\Classes; + +use MatthiasMullie\Scrapbook\Adapters\Redis as RedisAdapter; +use MatthiasMullie\Scrapbook\KeyValueStore; +use MatthiasMullie\Scrapbook\Scale\StampedeProtector; +use Redis; + +class KeyValue implements KeyValueInterface +{ + private KeyValueStore $keyValueStore; + + public function __construct( + private Redis $redis, + private string $prefix = '', + private int $maxTtl = 0, + ) { + $this->keyValueStore = new StampedeProtector( + new RedisAdapter($redis) + ); + } + + public function redis(): Redis + { + return $this->redis; + } + + public function set(string $key, mixed $value, int $ttl = 0): bool + { + if ($ttl === 0) { + $ttl = $this->maxTtl; + } + $ttl = min($ttl, $this->maxTtl); + + return $this->keyValueStore->set( + $this->getKey($key), + $value, + $ttl + ); + } + + public function setMulti(array $items, int $expire = 0): array + { + if ($expire === 0) { + $expire = $this->maxTtl; + } + $expire = min($expire, $this->maxTtl); + + return $this->keyValueStore->setMulti( + array_combine( + $this->getKeys(array_keys($items)), + array_values($items) + ), + $expire + ); + } + + public function get(string $key, &$token = null): mixed + { + return $this->keyValueStore->get( + $this->getKey($key), + $token + ); + } + + public function delete(string $key): bool + { + return $this->keyValueStore->delete( + $this->getKey($key) + ); + } + + public function deleteMulti(string ...$key): array + { + return $this->keyValueStore->deleteMulti( + $this->getKeys($key) + ); + } + + public function getKey(string $key): string + { + return $this->prefix . $key; + } + + private function getKeys(array $keys): array + { + return array_map( + fn ($key) => $this->getKey($key), + $keys + ); + } +} diff --git a/app/src/Legacy/Classes/KeyValueInterface.php b/app/src/Legacy/Classes/KeyValueInterface.php new file mode 100644 index 0000000..cc89e40 --- /dev/null +++ b/app/src/Legacy/Classes/KeyValueInterface.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Chevereto\Legacy\Classes; + +use Redis; + +interface KeyValueInterface +{ + public function redis(): Redis; + + /** + * Stores a value, regardless of whether or not the key already exists (in + * which case it will overwrite the existing value for that key). + * + * Return value is a boolean true when the operation succeeds, or false on + * failure. + */ + public function set(string $key, mixed $value, int $ttl = 0): bool; + + /** + * Store multiple values at once. + * + * Return value will be an associative array in [key => status] form, where + * status is a boolean true for success, or false for failure. + * + * setMulti is preferred over multiple individual set operations as you'll + * set them all in 1 request. + * + * @param int $expire Time when item falls out of the cache: + * 0 = permanent (doesn't expires); + * under 2592000 (30 days) = relative time, in seconds from now; + * over 2592000 = absolute time, unix timestamp + * + * @return bool[] + */ + public function setMulti(array $values, int $expire = 0): array; + + /** + * Retrieves an item from the cache. + * + * Optionally, an 2nd variable can be passed to this function. It will be + * filled with a value that can be used for cas() + * + * @return mixed|bool Value, or false on failure + */ + public function get(string $key): mixed; + + /** + * Retrieves the cache key. + */ + public function getKey(string $key): string; + + /** + * Deletes an item from the cache. + * Returns true if item existed & was successfully deleted, false otherwise. + * + * Return value is a boolean true when the operation succeeds, or false on + * failure. + */ + public function delete(string $key): bool; + + /** + * Deletes multiple items at once (reduced network traffic compared to + * individual operations). + * + * Return value will be an associative array in [key => status] form, where + * status is a boolean true for success, or false for failure. + * + * @return bool[] + */ + public function deleteMulti(string ...$key): array; +} diff --git a/app/src/Legacy/Classes/KeyValueNull.php b/app/src/Legacy/Classes/KeyValueNull.php new file mode 100644 index 0000000..8704e7b --- /dev/null +++ b/app/src/Legacy/Classes/KeyValueNull.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Chevereto\Legacy\Classes; + +use BadMethodCallException; +use Redis; + +class KeyValueNull implements KeyValueInterface +{ + public function __construct( + private string $prefix = '', + ) { + } + + public function redis(): Redis + { + throw new BadMethodCallException('Redis is not available'); + } + + public function set(string $key, mixed $value, int $ttl = 0): bool + { + return false; + } + + public function setMulti(array $values, int $ttl = 0): array + { + return []; + } + + public function get(string $key, &$token = null): mixed + { + return null; + } + + public function delete(string $key): bool + { + return true; + } + + public function deleteMulti(string ...$key): array + { + return []; + } + + public function getKey(string $key): string + { + return $this->prefix . $key; + } +} diff --git a/app/src/Legacy/Classes/Listing.php b/app/src/Legacy/Classes/Listing.php index 020abc0..981b853 100644 --- a/app/src/Legacy/Classes/Listing.php +++ b/app/src/Legacy/Classes/Listing.php @@ -15,6 +15,7 @@ use BadMethodCallException; use Chevereto\Legacy\G\Handler; use DateTime; use Exception; +use LogicException; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use RecursiveRegexIterator; @@ -36,9 +37,303 @@ use function Chevereto\Vars\request; class Listing { + public const COLUMNS = [ + 'images' => [ + 'image_id', + 'image_name', + 'image_extension', + 'image_size', + 'image_width', + 'image_height', + 'image_date', + 'image_date_gmt', + 'image_title', + 'image_description', + 'image_nsfw', + 'image_user_id', + 'image_album_id', + 'image_uploader_ip', + 'image_storage_mode', + 'image_path', + 'image_storage_id', + 'image_checksum', + 'image_source_checksum', + 'image_original_filename', + // 'image_original_exifdata', + 'image_views', + 'image_category_id', + 'image_chain', + 'image_thumb_size', + 'image_medium_size', + 'image_frame_size', + 'image_expiration_date_gmt', + 'image_likes', + 'image_is_animated', + 'image_is_approved', + 'image_is_360', + 'image_duration', + 'image_type', + ], + 'users' => [ + 'user_id', + 'user_name', + 'user_username', + 'user_date', + 'user_date_gmt', + 'user_email', + 'user_avatar_filename', + 'user_facebook_username', + 'user_twitter_username', + 'user_website', + 'user_background_filename', + 'user_bio', + 'user_timezone', + 'user_language', + 'user_status', + 'user_is_admin', + 'user_is_manager', + 'user_is_private', + 'user_palette_id', + 'user_newsletter_subscribe', + 'user_show_nsfw_listings', + 'user_image_count', + 'user_album_count', + 'user_image_keep_exif', + 'user_file_meta_tag_camera_model', + 'user_image_expiration', + 'user_registration_ip', + 'user_likes', + 'user_liked', + 'user_following', + 'user_followers', + 'user_content_views', + // 'user_notifications_unread', + ], + 'albums' => [ + 'album_id', + 'album_name', + 'album_user_id', + 'album_date', + 'album_date_gmt', + 'album_creation_ip', + 'album_privacy', + 'album_privacy_extra', + 'album_password', + 'album_image_count', + 'album_description', + 'album_likes', + 'album_views', + 'album_cover_id', + 'album_parent_id', + 'album_cta_enable', + 'album_cta', + ], + 'tags' => [ + 'tag_id', + 'tag_name', + 'tag_description', + 'tag_user_id', + 'tag_date_gmt', + 'tag_files', + 'tag_views', + ], + 'likes' => [ + 'like_id', + 'like_date', + 'like_date_gmt', + 'like_user_id', + 'like_content_type', + 'like_content_id', + 'like_content_user_id', + 'like_ip', + ], + 'follows' => [ + 'follow_id', + 'follow_date', + 'follow_date_gmt', + 'follow_user_id', + 'follow_followed_user_id', + 'follow_ip', + ], + 'storages' => [ + 'storage_id', + 'storage_api_id', + 'storage_name', + // 'storage_service', + 'storage_url', + // 'storage_bucket', + // 'storage_region', + // 'storage_server', + // 'storage_account_id', + // 'storage_account_name', + // 'storage_key', + // 'storage_secret', + // 'storage_is_https', + 'storage_is_active', + 'storage_capacity', + // 'storage_space_used', + 'storage_type_chain', + // 'storage_use_path_style_endpoint', + // 'storage_deleted_at', + ], + 'categories' => [ + 'category_id', + 'category_name', + 'category_url_key', + 'category_description', + ], + ]; + + public const COLUMNS_JOIN = [ + 'images' => [ + 'image_id', + 'image_name', + 'image_extension', + 'image_size', + 'image_width', + 'image_height', + 'image_date', + 'image_date_gmt', + 'image_title', + 'image_description', + 'image_nsfw', + 'image_user_id', + 'image_album_id', + 'image_uploader_ip', + 'image_storage_mode', + 'image_path', + 'image_storage_id', + 'image_checksum', + 'image_source_checksum', + 'image_original_filename', + // 'image_original_exifdata', + 'image_views', + 'image_category_id', + 'image_chain', + 'image_thumb_size', + 'image_medium_size', + 'image_frame_size', + 'image_expiration_date_gmt', + 'image_likes', + 'image_is_animated', + 'image_is_approved', + 'image_is_360', + 'image_duration', + 'image_type', + ], + 'users' => [ + 'user_id', + 'user_name', + 'user_username', + // 'user_date', + // 'user_date_gmt', + // 'user_email', + 'user_avatar_filename', + 'user_facebook_username', + 'user_twitter_username', + 'user_website', + 'user_background_filename', + // 'user_bio', + // 'user_timezone', + 'user_language', + 'user_status', + 'user_is_admin', + 'user_is_manager', + 'user_is_private', + // 'user_palette_id', + // 'user_newsletter_subscribe', + // 'user_show_nsfw_listings', + // 'user_image_count', + // 'user_album_count', + // 'user_image_keep_exif', + // 'user_file_meta_tag_camera_model', + // 'user_image_expiration', + // 'user_registration_ip', + 'user_likes', + 'user_liked', + 'user_following', + 'user_followers', + 'user_content_views', + // 'user_notifications_unread', + ], + 'albums' => [ + 'album_id', + 'album_name', + // 'album_user_id', + 'album_date', + 'album_date_gmt', + 'album_creation_ip', + 'album_privacy', + // 'album_privacy_extra', + // 'album_password', + 'album_image_count', + // 'album_description', + 'album_likes', + 'album_views', + 'album_cover_id', + 'album_parent_id', + // 'album_cta_enable', + // 'album_cta', + ], + 'tags' => [ + 'tag_id', + 'tag_name', + // 'tag_description', + // 'tag_user_id', + // 'tag_date_gmt', + // 'tag_files', + // 'tag_views', + ], + 'likes' => [ + 'like_id', + 'like_date', + 'like_date_gmt', + 'like_user_id', + 'like_content_type', + 'like_content_id', + 'like_content_user_id', + 'like_ip', + ], + 'follows' => [ + 'follow_id', + 'follow_date', + 'follow_date_gmt', + 'follow_user_id', + 'follow_followed_user_id', + 'follow_ip', + ], + 'storages' => [ + 'storage_id', + 'storage_api_id', + 'storage_name', + // 'storage_service', + 'storage_url', + // 'storage_bucket', + // 'storage_region', + // 'storage_server', + // 'storage_account_id', + // 'storage_account_name', + // 'storage_key', + // 'storage_secret', + // 'storage_is_https', + 'storage_is_active', + 'storage_capacity', + // 'storage_space_used', + 'storage_type_chain', + // 'storage_use_path_style_endpoint', + // 'storage_deleted_at', + ], + 'categories' => [ + 'category_id', + 'category_name', + 'category_url_key', + // 'category_description', + ], + ]; + public string $query; - public array $seek; + public array $seek = []; public string $seekEnd = ''; @@ -108,6 +403,15 @@ class Listing private ?string $outputTpl; + private array $first = []; + + private bool $isOutputAssoc = false; + + public function first(): array + { + return $this->first; + } + public function outputCount(): int { return $this->output_count; @@ -296,8 +600,7 @@ class Listing } /** - * Do the thing - * @Exeption 4xx + * @Exception 4xx */ public function exec() { @@ -588,7 +891,7 @@ class Listing } $sort_field = $type_singular . '_' . $this->sort_type; $key_field = $type_singular . '_id'; - if (isset($this->seek)) { + if ($this->seek !== []) { if (ends_with('date_gmt', $this->sort_type)) { $d = DateTime::createFromFormat('Y-m-d H:i:s', $this->seek[0]); if (! $d || $d->format('Y-m-d H:i:s') !== $this->seek[0]) { @@ -633,12 +936,15 @@ class Listing $limit = "\n" . 'LIMIT ' . ($this->limit + 1); // +1 allows to fetch "one extra" to detect prev/next pages } $base_table = $tables[$this->type]; + $joins = array_filter($joins); // Normal query if (empty($joins[$this->type])) { - $query = 'SELECT * FROM ' . $base_table; + $query = 'SELECT ' + . implode(', ', self::COLUMNS[$this->type]) + . ' FROM ' . $base_table; $query .= $this->where . $order_by . $limit; - // Alternative query } else { + // Alternative query if ($this->where !== '') { preg_match_all('/' . env()['CHEVERETO_DB_TABLE_PREFIX'] . '([\w_]+)\./', $this->where, $where_tables); $where_tables = array_values(array_diff(array_unique($where_tables[1]), [$this->type])); @@ -651,34 +957,113 @@ class Listing reset($joins); $join_tables = [key($joins)]; } + $joinSelect = [$this->type]; + $subSelectColumns = self::COLUMNS[$this->type]; $join = ''; if (is_iterable($join_tables)) { foreach ($join_tables as $join_table) { if (! empty($joins[$this->type][$join_table])) { $join .= "\n" . $joins[$this->type][$join_table]; unset($joins[$this->type][$join_table]); + $joinSelect[] = $join_table; + $subSelectColumns = array_merge( + $subSelectColumns, + self::COLUMNS_JOIN[$join_table] + ); } } } - // Get rid of the original Exif data (for listings) - $null_db = $this->type === 'images' - ? ', NULL as image_original_exifdata ' - : null; - $query = 'SELECT * ' - . $null_db - . 'FROM (SELECT * FROM ' + $outerColumns = [$this->type]; + $typeJoins = array_keys($joins[$this->type]); + foreach ($typeJoins as $joinKey) { + if (isset($joins[$joinKey])) { + array_push($outerColumns, ...array_keys($joins[$joinKey])); + } + array_push($outerColumns, $joinKey); + } + $outerColumns = array_unique($outerColumns); + $selectColumns = []; + foreach ($outerColumns as $columnName) { + $selectColumns = array_merge($selectColumns, self::COLUMNS_JOIN[$columnName]); + } + $selectColumns = array_merge($selectColumns, $subSelectColumns); + $selectColumns = array_unique($selectColumns); + $columns = implode(', ', $selectColumns); + $subColumns = implode(', ', $subSelectColumns); + $query = 'SELECT ' + . $columns + . ' FROM (SELECT ' + . $subColumns + . ' FROM ' . $base_table . $join . $this->where . $order_by . $limit - . ') ' - . $base_table; - if (! empty($joins[$this->type])) { - $query .= "\n" - . implode("\n", $joins[$this->type]); + . ') AS subquery '; + $topJoins = implode("\n", $joins[$this->type]); + $topJoins = str_replace( + "{$base_table}.", + 'subquery.', + $topJoins + ); + $query .= "\n" + . $topJoins; + $topOrder = str_replace( + "{$base_table}.", + 'subquery.', + $order_by + ); + $query .= $topOrder; + } + usort($this->binds, function ($a, $b) { + return strcmp($a['param'], $b['param']); + }); + $cacheable = Cache::isEnabled() + && $this->requester === []; + if ($cacheable) { + $listingPageBinds = []; + foreach ($this->binds as $k => $v) { + $listingPageBinds[$v['param']] = $v['value']; + } + // $rootBinds = $listingPageBinds; + // unset($rootBinds[':seekSort'], $rootBinds[':seekKey']); + // $listingHash = Cache::hash($query . serialize($rootBinds)); + $listingPageHash = Cache::hash($query . serialize($listingPageBinds)); + $cacheKeyListingPage = "l:{$listingPageHash}"; + $cached = Cache::instance()->get($cacheKeyListingPage); + if ($cached) { + $this->count = $cached['count']; + $this->has_page_next = $cached['has_page_next']; + $this->has_page_prev = $cached['has_page_prev']; + $this->nsfw = $cached['nsfw']; + $this->output = unserialize(gzuncompress($cached['output'])); + $filler = []; + foreach ($cached['joinSelect'] as $joinSelect) { + $filler = array_merge($filler, array_fill_keys(static::COLUMNS[$joinSelect], null)); + } + foreach ($this->output as $k => $v) { + $this->output[$k] = array_merge($filler, $v); + } + $this->output_count = $cached['output_count']; + $this->seek = $cached['seek']; + $this->seekEnd = $cached['seekEnd']; + $this->seekStart = $cached['seekStart']; + $this->sfw = $cached['sfw']; + $this->output_assoc = []; + $this->first = $cached['first']; + if (! $this->isOutputAssoc()) { + return; + } + $formatFunction = 'Chevereto\Legacy\Classes\\' . ucfirst(substr($this->type, 0, -1)); + foreach ($this->output as $k => $v) { + $this->output_assoc[] = $this->type === 'images' + ? $formatFunction::formatArray($v, fillAlbumCover: false) + : $formatFunction::formatArray($v); + } + + return; } - $query .= $order_by; } $db = DB::getInstance(); $this->query = $query; @@ -723,32 +1108,48 @@ class Listing $this->count = count($this->output); $this->nsfw = false; $this->output_assoc = []; - $formatfn = 'Chevereto\Legacy\Classes\\' . ucfirst(substr($this->type, 0, -1)); + $formatFunction = 'Chevereto\Legacy\Classes\\' . ucfirst(substr($this->type, 0, -1)); + $prefix = DB::getFieldPrefix($this->type) . '_'; + $ids = []; foreach ($this->output as $k => $v) { - $val = $formatfn::formatArray($v); - $this->output_assoc[] = $val; - if (! $this->nsfw && isset($val['nsfw']) && $val['nsfw']) { + if ($this->isOutputAssoc()) { + $this->output_assoc[] = $this->type === 'images' + ? $formatFunction::formatArray($v, fillAlbumCover: false) + : $formatFunction::formatArray($v); + } + $ids[] = $v[$prefix . 'id']; + if (! $this->nsfw && isset($v[$prefix . 'nsfw']) && $v[$prefix . 'nsfw']) { $this->nsfw = true; } } + if (in_array($this->type, ['images', 'albums'])) { + $this->first = $this->output_assoc[0] ?? $this->output[0] ?? []; + if ($this->first !== [] && $this->output_assoc === []) { + $this->first = $this->type === 'images' + ? $formatFunction::formatArray($this->first, fillAlbumCover: false) + : $formatFunction::formatArray($this->first); + } + } if ($this->type === 'albums') { $this->nsfw = false; } $this->sfw = ! $this->nsfw; Handler::setCond('show_viewer_zero', isset(request()['viewer']) && $this->count > 0); if ($this->type === 'albums' && $this->output !== []) { - $coverTpl = '(SELECT * - FROM %tImages% - LEFT JOIN %tStorages% ON %tImages%.image_storage_id = %tStorages%.storage_id - WHERE image_id = (SELECT album_cover_id FROM %tAlbums% WHERE album_id = %ALBUM_ID%) - AND %tImages%.image_is_approved = 1 - LIMIT 1)'; - $album_cover_qry_tpl = strtr($coverTpl, [ - '%tImages%' => $tables['images'], - '%tStorages%' => $tables['storages'], - '%tAlbums%' => $tables['albums'], - ]); - $albums_cover_qry_arr = []; + $selectColumns = array_merge( + self::COLUMNS['images'], + self::COLUMNS['storages'] + ); + $selectColumns = implode(', ', $selectColumns); + $inIds = implode(',', $ids); + $coverSQL = <<output as $k => &$album) { $album['album_id'] ??= ''; @@ -757,15 +1158,9 @@ class Listing $album['album_image_count'] = 0; } $album['album_image_count_label'] = _n('image', 'images', $album['album_image_count']); - $albums_cover_qry_arr[] = str_replace( - '%ALBUM_ID%', - $album['album_id'], - $album_cover_qry_tpl - ); $albums_mapping[$album['album_id']] = $k; } - $albums_slice_qry = implode("\n" . 'UNION ALL ' . "\n", $albums_cover_qry_arr); - $db->query($albums_slice_qry); + $db->query($coverSQL); $albums_slice = $db->fetchAll(); if (! empty($albums_slice)) { foreach ($albums_slice as $slice) { @@ -776,13 +1171,79 @@ class Listing if (! isset($this->output[$album_key]['album_images_slice'])) { $this->output[$album_key]['album_images_slice'] = []; } + foreach ($slice as $key => $value) { + if ($value === null) { + unset($slice[$key]); + } + } $this->output[$album_key]['album_images_slice'][] = $slice; } } } + if (! $cacheable) { + return; + } + $output = $this->output; + foreach ($output as $pos => $array) { + foreach ($array as $key => $value) { + if ($value === null) { + unset($output[$pos][$key]); + } + } + } + Cache::instance()->set( + $cacheKeyListingPage, + [ + 'first' => $this->first, + 'joinSelect' => $joinSelect ?? [], + 'count' => $this->count, + 'has_page_next' => $this->has_page_next, + 'has_page_prev' => $this->has_page_prev, + 'nsfw' => $this->nsfw, + 'output_count' => $this->output_count, + 'output' => gzcompress(serialize($output)), + 'seek' => $this->seek, + 'seekEnd' => $this->seekEnd, + 'seekStart' => $this->seekStart, + 'sfw' => $this->sfw, + ], + 300 + ); + $cacheType = match ($type_singular) { + 'image' => 'i', + 'album' => 'a', + 'user' => 'u', + 'tag' => 't', + default => throw new LogicException('Invalid cache type::' . $type_singular), + }; + $cache = Cache::instance(); + foreach ($ids as $id) { + $cacheKeyTypeIdListing = $cache->getKey("{$cacheType}:{$id}:l"); + $cacheValue = "{$listingPageHash}"; + $cache->redis()->sAdd($cacheKeyTypeIdListing, $cacheValue); + $cache->redis()->expire($cacheKeyTypeIdListing, 300); + } } - public static function getTabs($args = [], $autoParams = [], $expanded = false) + public static function deleteTypeIdCache(string $type, int ...$id): void + { + if (! Cache::isEnabled()) { + return; + } + $cache = Cache::instance(); + $redis = $cache->redis(); + foreach ($id as $item) { + $key = $cache->getKey("{$type}:{$item}:l"); + $set = $redis->sMembers($key) ?: []; + foreach ($set as $hash) { + $cacheKeyListing = $cache->getKey("l:{$hash}"); + $redis->del($cacheKeyListing); + $redis->sRem($key, $hash); + } + } + } + + public static function getTabs($args = [], &$autoParams = [], $expanded = false) { $default = [ 'list' => true, @@ -1037,6 +1498,7 @@ class Listing 'label' => $v['label'], 'id' => $id, 'params' => $http_build_query, + 'sort' => $params['sort'] ?? null, 'current' => false, 'type' => $content, 'url' => $url, @@ -1071,6 +1533,13 @@ class Listing } $tabs[$currentKey]['current'] = 1; self::fillCurrentTabPeekSeek($tabs, $currentKey, $autoParams); + $currentSort = explode('_', $tabs[$currentKey]['sort'] ?? ''); + if (count($currentSort) > 2) { + $autoParams['sort'] = [ + implode('_', array_slice($currentSort, 0, -1)), + end($currentSort), + ]; + } if ($expanded) { return [ 'tabs' => $tabs, @@ -1174,6 +1643,9 @@ class Listing /** @var callable $tagFn */ $tagFn = require_theme_file_return('snippets/tag'); $items = []; + // if (str_ends_with($tpl_list, 'album')) { + // $ids = array_column($this->output, 'album_id'); + // } foreach ($this->output as $pos => &$row) { switch ($tpl_list) { case 'image': @@ -1206,7 +1678,11 @@ class Listing break; } - $item = $Class::formatArray($row); + if ($Class === Image::class) { + $item = $Class::formatArray($row, fillAlbumCover: false); + } else { + $item = $Class::formatArray($row); + } if (str_ends_with($tpl_list, 'album') && $this->tagsString !== '') { $item['url'] .= '/?tag=' . rawurlencode($this->tagsString); } @@ -1310,6 +1786,11 @@ class Listing return $params; } + public function setOutputAssoc(bool $isOutputAssoc): void + { + $this->isOutputAssoc = $isOutputAssoc; + } + /** * validate_input aka "first stage validation" * This checks for valid input source data before exec @@ -1348,4 +1829,9 @@ class Listing { return ($this->where === '' ? 'WHERE ' : ($this->where . ' AND ')) . $where; } + + private function isOutputAssoc(): bool + { + return $this->isOutputAssoc; + } } diff --git a/app/src/Legacy/Classes/Login.php b/app/src/Legacy/Classes/Login.php index bcc8f3e..e6701ff 100644 --- a/app/src/Legacy/Classes/Login.php +++ b/app/src/Legacy/Classes/Login.php @@ -157,36 +157,6 @@ class Login } } - public static function addGuestContentToUser(array $user, int $id): void - { - if ($user === []) { - return; - } - foreach (['albums', 'images'] as $table) { - $sessionKey = 'guest_' . $table; - if (! is_array(session()[$sessionKey] ?? null)) { - continue; - } - - try { - $db = DB::getInstance(); - $getTable = DB::getTable($table); - $fieldPrefix = DB::getFieldPrefix($table); - $db->query('UPDATE ' . $getTable . ' SET ' . $fieldPrefix . '_user_id=' . $id . ' WHERE ' . $fieldPrefix . '_id IN (' . implode(',', session()[$sessionKey]) . ')'); - $db->exec(); - if ($db->rowCount() !== 0) { - DB::increment('users', [ - $fieldPrefix . '_count' => '+' . $db->rowCount(), - ], [ - 'id' => $id, - ]); - } - } catch (Exception) { - } // Silence - sessionVar()->remove($sessionKey); - } - } - public static function login(string|int $id, string $cookieType = 'cookie'): array { $id = (int) $id; @@ -195,7 +165,6 @@ class Login throw new Exception(sprintf('Invalid login $by %s', $cookieType), 600); } $user = User::getSingle($id, 'id'); - self::addGuestContentToUser($user, $id); RequestLog::delete([ 'user_id' => $id, 'result' => 'fail', diff --git a/app/src/Legacy/Classes/Page.php b/app/src/Legacy/Classes/Page.php index acd9888..1650ee0 100644 --- a/app/src/Legacy/Classes/Page.php +++ b/app/src/Legacy/Classes/Page.php @@ -13,10 +13,8 @@ namespace Chevereto\Legacy\Classes; use Chevereto\Config\Config; use function Chevereto\Legacy\G\get_base_url; -use function Chevereto\Legacy\G\get_file_extension; use function Chevereto\Legacy\G\is_url; use function Chevereto\Legacy\G\safe_html; -use function Chevereto\Legacy\G\str_replace_last; use function Chevereto\Vars\get; use function Chevereto\Vars\post; @@ -25,7 +23,6 @@ class Page public static array $table_fields = [ 'url_key', 'type', - 'file_path', 'link_url', 'icon', 'title', @@ -40,17 +37,17 @@ class Page 'code', ]; - public static function getSingle(string $var, $by = 'url_key'): array + public static function getSingle(string $var, string $by = 'url_key', bool $withCode = true): array { return []; } - public static function getAll(array $args = [], array $sort = []): array + public static function getAll(array $args = [], array $sort = [], bool $withCode = false): array { return []; } - public static function get(array $values, array $sort = [], ?int $limit = null): array + public static function get(array $values, array $sort = [], ?int $limit = null, bool $withCode = false): array { return []; } @@ -62,7 +59,12 @@ class Page public static function getFields(): array { - return self::$table_fields; + $fields = self::$table_fields; + if (Config::enabled()->phpPages()) { + $fields[] = 'file_path'; + } + + return $fields; } public static function update(int $id, array $values): int @@ -83,7 +85,6 @@ class Page 'link' => _s('Link'), ]; $page['type_tr'] = $type_tr[$page['type']]; - switch ($page['type']) { case 'internal': $page['url'] = get_base_url('page/' . $page['url_key']); @@ -93,28 +94,26 @@ class Page 'user' => null, // base ]; $file_basename = $page['url_key'] . '.php'; - foreach ($filepaths as $k => $v) { + foreach ($filepaths as $v) { if (is_readable(self::getPath($v) . $file_basename)) { $page['file_path'] = $v . $file_basename; } } - } else { - $page_extension = get_file_extension($page['file_path']); - if (! Config::enabled()->phpPages() && $page_extension == 'php') { - $page['file_path'] = str_replace_last($page_extension, 'html', $page['file_path']); - } + } elseif (Config::enabled()->phpPages()) { if ($page['internal'] === 'contact' - && (post() !== [] || (get()['sent'] ?? '0' == '1'))) { - $page_extension = 'php'; + && (post() !== [] || (get()['sent'] ?? '0' == '1')) + ) { $page['file_path'] = 'default/contact.php'; } } $page['file_path_absolute'] = self::getPath($page['file_path']); - if (! file_exists($page['file_path_absolute'])) { - self::writePage([ - 'file_path' => $page['file_path'], - 'code' => $page['code'] ?? '', - ]); + if (Config::enabled()->phpPages()) { + if (! file_exists($page['file_path_absolute'])) { + self::writePage([ + 'file_path' => $page['file_path'], + 'code' => $page['code'] ?? '', + ]); + } } break; diff --git a/app/src/Legacy/Classes/RequestLog.php b/app/src/Legacy/Classes/RequestLog.php index 7094a74..eda242c 100644 --- a/app/src/Legacy/Classes/RequestLog.php +++ b/app/src/Legacy/Classes/RequestLog.php @@ -14,6 +14,7 @@ namespace Chevereto\Legacy\Classes; use function Chevereto\Legacy\G\datetime; use function Chevereto\Legacy\G\datetimegmt; use function Chevereto\Legacy\G\get_client_ip; +use function Chevereto\Vars\env; class RequestLog { @@ -27,47 +28,85 @@ class RequestLog if (defined('PHPUNIT_CHEVERETO_TESTSUITE')) { return 0; } - if (!isset($values['ip'])) { + if (! isset($values['ip'])) { $values['ip'] = get_client_ip(); } $values['date'] = datetime(); $values['date_gmt'] = datetimegmt(); + $rows = DB::insert('requests', $values); + if ($rows && Cache::isEnabled()) { + $cache = Cache::instance(); + $redis = $cache->redis(); + $ip = inet_ntop(inet_pton($values['ip'])); + $key = Cache::instance()->getKey("ip:{$ip}:rl"); + $set = $redis->sMembers($key) ?: []; + foreach ($set as $hash) { + $cacheKeyLog = $cache->getKey("rl:{$hash}"); + $redis->del($cacheKeyLog); + $redis->sRem($key, $hash); + } + } - return DB::insert('requests', $values); + return $rows; } public static function getCounts(array|string $type, string $result, ?string $ip = null): array { + $ip ??= get_client_ip(); + if (Cache::isEnabled()) { + $hash = Cache::hash(serialize($type) . $result . $ip); + $cacheKey = "rl:{$hash}"; + $cached = Cache::instance()->get($cacheKey); + if ($cached) { + return $cached; + } + } if (is_array($type)) { - $type_qry = 'request_type IN('; + $whereType = 'request_type IN('; $binds = []; foreach ($type as $i => $singleType) { - $type_qry .= ':rt' . $i . ','; + $whereType .= ':rt' . $i . ','; $binds[':rt' . $i] = $singleType; } - $type_qry = rtrim($type_qry, ',') . ')'; + $whereType = rtrim($whereType, ',') . ')'; } else { - $type_qry = 'request_type=:request_type'; + $whereType = 'request_type=:request_type'; $binds = [ - ':request_type' => $type + ':request_type' => $type, ]; } - + $binds[':request_result'] = $result; + $binds[':request_ip'] = $ip; $db = DB::getInstance(); - $db->query('SELECT - COUNT(IF(request_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 MINUTE), 1, NULL)) AS minute, - COUNT(IF(request_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 HOUR), 1, NULL)) AS hour, - COUNT(IF(request_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 DAY), 1, NULL)) AS day, - COUNT(IF(request_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 WEEK), 1, NULL)) AS week, - COUNT(IF(request_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 MONTH), 1, NULL)) AS month - FROM ' . DB::getTable('requests') . ' WHERE ' . $type_qry . ' AND request_result=:request_result AND request_ip=:request_ip AND request_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 MONTH)'); + $tableRequest = DB::getTable('requests'); + $sql = <<= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 MINUTE), 1, NULL)) AS minute, + COUNT(IF(request_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 HOUR), 1, NULL)) AS hour, + COUNT(IF(request_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 DAY), 1, NULL)) AS day, + COUNT(IF(request_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 WEEK), 1, NULL)) AS week, + COUNT(IF(request_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 MONTH), 1, NULL)) AS month + FROM `{$tableRequest}` WHERE request_result=:request_result + AND {$whereType} + AND request_ip=:request_ip + AND request_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 MONTH) + SQL; + $db->query($sql); foreach ($binds as $k => $v) { $db->bind($k, $v); } - $db->bind(':request_result', $result); - $db->bind(':request_ip', $ip ?: get_client_ip()); + $row = $db->fetchSingle(); + $ttl = (int) (env()['CHEVERETO_CACHE_TIME_MICRO'] ?? 60); + if (Cache::isEnabled()) { + Cache::instance()->set($cacheKey, $row, $ttl); + $redis = Cache::instance()->redis(); + $ip = inet_ntop(inet_pton($ip)); + $inverseKey = Cache::instance()->getKey("ip:{$ip}:rl"); + $redis->sAdd($inverseKey, $hash); + $redis->expire($inverseKey, $ttl); + } - return $db->fetchSingle(); + return $row; } public static function delete($values, $clause = 'AND'): int diff --git a/app/src/Legacy/Classes/Search.php b/app/src/Legacy/Classes/Search.php index ef6ec87..ab542c4 100644 --- a/app/src/Legacy/Classes/Search.php +++ b/app/src/Legacy/Classes/Search.php @@ -20,7 +20,7 @@ class Search public static array $excluded = ['storage', 'ip']; - public string $DBEngine; + public string $DBEngine = 'InnoDB'; public string $wheres; @@ -36,11 +36,6 @@ class Search public array $op; - public function __construct() - { - $this->DBEngine = DB::queryFetchSingle("SHOW TABLE STATUS WHERE Name = '" . DB::getTable('images') . "';")['Engine']; - } - public function build(): void { if (! in_array($this->type, ['images', 'albums', 'users'], true)) { @@ -171,6 +166,11 @@ class Search 'param' => ':q', 'value' => $q_value, ]; + $q_strip = preg_replace('/(-[\S]+|".+?")/u', '', $q_match); + $search_binds[] = [ + 'param' => ':like_q', + 'value' => '%' . $q_strip . '%', + ]; } $this->binds = $search_binds; $this->op = $search_op; @@ -178,7 +178,15 @@ class Search switch ($this->type) { case 'images': if ($q_match !== '') { - $wheres = 'WHERE MATCH(`image_name`,`image_title`,`image_description`,`image_original_filename`) AGAINST (:q IN BOOLEAN MODE)'; + $wheres = << 'WHERE MATCH(`user_name`,`user_username`) AGAINST (:q)', - 'email' => '`user_email` LIKE CONCAT("%", :q, "%")', + 'name_username' => << <<requester['is_content_manager'] ?? false) { $pos = strpos($this->q, '@'); diff --git a/app/src/Legacy/Classes/Settings.php b/app/src/Legacy/Classes/Settings.php index f4622f9..a2ddfbe 100644 --- a/app/src/Legacy/Classes/Settings.php +++ b/app/src/Legacy/Classes/Settings.php @@ -102,6 +102,18 @@ class Settings 'semantics_image' => 'Image', 'semantics_images' => 'Images', ], + [ + 'semantics_video' => 'Video', + 'semantics_videos' => 'Videos', + ], + [ + 'semantics_file' => 'File', + 'semantics_files' => 'Files', + ], + [ + 'semantics_tag' => 'Tag', + 'semantics_tags' => 'Tags', + ], [ 'semantics_user' => 'User', 'semantics_users' => 'Users', @@ -285,6 +297,16 @@ class Settings 'sdk_pup_url' => '', ], ], + 'CHEVERETO_ENABLE_API_USER' => ['0', + [ + 'enable_api_user' => false, + ], + ], + 'CHEVERETO_ENABLE_API_GUEST' => ['0', + [ + 'enable_api_guest' => false, + ], + ], ]; public const STOCK = [ @@ -391,6 +413,9 @@ class Settings 'website_search' => true, 'arachnid_api_username' => '', 'arachnid_api_password' => '', + 'theme_palette_user_select' => true, + 'enable_api_user' => true, + 'enable_api_guest' => false, ]; public const USERNAME_MIN_LENGTH = 3; @@ -438,10 +463,21 @@ class Settings protected static array $typeset = []; - protected static array $decrypted = []; - - public function __construct() + public function __construct(bool $reCache = false) { + if ($reCache === true) { + $cached = false; + } else { + $cached = Cache::instance()->get('settings'); + } + if ($cached) { + self::$settings = $cached['settings']; + self::$defaults = $cached['defaults']; + self::$typeset = $cached['typeset']; + self::$instance = $this; + + return; + } $settings = []; $defaults = []; $typeset = []; @@ -585,6 +621,7 @@ class Settings self::$typeset = $typeset; self::$instance = $this; self::update($db_settings_fix); + self::cache(); } public static function getInstance(): self @@ -655,11 +692,6 @@ class Settings return self::getDefaults($key); } - public static function setValues(array $values): void - { - self::$settings = $values; - } - public static function setValue(string $key, mixed $value): void { self::$settings[$key] = $value ?? null; @@ -674,6 +706,7 @@ class Settings <<bind($bindK, $bindV); } $db->exec(); + new self(reCache: true); return true; } @@ -766,7 +800,12 @@ class Settings $db->bind($bindK, $bindV); } - return $db->exec(); + $return = $db->exec(); + if ($return) { + self::cache(); + } + + return $return; } /** @@ -797,4 +836,16 @@ class Settings { return in_array($key, self::$envRestricted, true); } + + private static function cache(): void + { + Cache::instance()->set( + 'settings', + [ + 'settings' => self::$settings, + 'defaults' => self::$defaults, + 'typeset' => self::$typeset, + ] + ); + } } diff --git a/app/src/Legacy/Classes/Stat.php b/app/src/Legacy/Classes/Stat.php index a23d559..ec0a62c 100644 --- a/app/src/Legacy/Classes/Stat.php +++ b/app/src/Legacy/Classes/Stat.php @@ -23,9 +23,29 @@ class Stat { public static function getTotals(): array { - $res = DB::queryFetchSingle('SELECT * FROM ' . DB::getTable('stats') . ' WHERE stat_type = "total"'); + $columns = implode(', ', [ + 'stat_users', + 'stat_images', + 'stat_albums', + 'stat_tags', + 'stat_cron_runs', + 'stat_cron_time', + 'stat_image_views', + 'stat_album_views', + 'stat_image_likes', + 'stat_album_likes', + 'stat_disk_used', + ]); + $tableStats = DB::getTable('stats'); + $res = DB::queryFetchSingle( + << 'images', @@ -107,7 +132,7 @@ class Stat return; } $count = self::getTotals()[$envToStat[$env]] ?? 0; - if (($count + 1) > $maxLimit) { + if (($count + $add) > $maxLimit) { throw new OverflowException( message( 'Maximum %t% reached (limit %s%).', @@ -121,7 +146,7 @@ class Stat public static function rebuildTotals(): void { - $query = << env()['CHEVERETO_DB_TABLE_PREFIX'], ]); @@ -230,7 +255,7 @@ class Stat if (! isset($args['disk_sum'])) { throw new Exception('Missing disk_sum value', 603); } - $sql_tpl = << DB::getFieldPrefix($args['table']), @@ -292,7 +317,7 @@ class Stat case 'delete': switch ($args['table']) { case 'images': - $sql_tpl = <<query($query); if ($storageId !== 0) { diff --git a/app/src/Legacy/Classes/Tag.php b/app/src/Legacy/Classes/Tag.php index 2f38e8c..5c45de5 100644 --- a/app/src/Legacy/Classes/Tag.php +++ b/app/src/Legacy/Classes/Tag.php @@ -86,7 +86,6 @@ final class Tag $array = explode(',', $tags); $array = array_map(function (string $value) { return trim($value); - // return strip_tags_content($value); }, $array); $array = array_unique($array); $array = array_filter($array); @@ -139,16 +138,12 @@ final class Tag ]); } $select = implode(',', $select); - $query = << $select, - ]); + SQL; $db->query($query); foreach ($binds as $pos => $v) { $db->bind($pos, $v); @@ -162,14 +157,14 @@ final class Tag $limit = max(1, $limit); $db = DB::getInstance(); $tagsTable = DB::getTable('tags'); - $query = <<query($query); $db->bind(':try', $try . '%'); @@ -178,6 +173,24 @@ final class Tag public static function insert(int $user_id, string ...$tag): void { + if ($tag === []) { + return; + } + $maxTags = (int) env()['CHEVERETO_MAX_TAGS']; + if ($maxTags > 0) { + $currentTotalTags = Stat::getTotals()['tags'] ?? 0; + if ($currentTotalTags >= $maxTags) { + return; + } + $count = count($tag); + $newTotalTags = $currentTotalTags + $count; + if ($newTotalTags > $maxTags) { + $excessTags = $newTotalTags - $maxTags; + if ($excessTags > 0) { + $tag = array_slice($tag, 0, $count - $excessTags); + } + } + } if ($tag === []) { return; } @@ -187,7 +200,7 @@ final class Tag $binds = [ ':tag_user_id' => $user_id, ]; - $template = << $name) { static::assert($name); $sql .= str_replace('%', $pos, $template); @@ -294,12 +307,12 @@ final class Tag $tagsTable = DB::getTable('tags'); $statsTable = DB::getTable('stats'); $db = DB::getInstance(); - $sql = <<query($sql); - return $db->exec(); + $return = $db->exec(); + if ($return) { + Listing::deleteTypeIdCache('t', ...$id); + } + + return $return; } public static function formatArray(array $object): array diff --git a/app/src/Legacy/Classes/Tags.php b/app/src/Legacy/Classes/Tags.php new file mode 100644 index 0000000..25a48d3 --- /dev/null +++ b/app/src/Legacy/Classes/Tags.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Chevereto\Legacy\Classes; + +use Throwable; + +final class Tags +{ + public const CACHE_KEY = 'tags_top'; + + public static function top(): array + { + $tagsTop = []; + $cached = Cache::instance()->get(self::CACHE_KEY); + if ($cached) { + return $cached; + } + + try { + $tagsTable = DB::getTable('tags'); + $rows = DB::queryFetchAll( + << $v) { + $tag = array_merge($v, Tag::row($v['name'])); + $tagsTop[] = $tag; + } + } catch (Throwable) { + } + + Cache::instance()->set(self::CACHE_KEY, $tagsTop, 1800); + + return $tagsTop; + } +} diff --git a/app/src/Legacy/Classes/Traits/BinaryTrait.php b/app/src/Legacy/Classes/Traits/BinaryTrait.php new file mode 100644 index 0000000..7020d09 --- /dev/null +++ b/app/src/Legacy/Classes/Traits/BinaryTrait.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Chevereto\Legacy\Classes\Traits; + +use RuntimeException; +use Symfony\Component\Process\ExecutableFinder; + +trait BinaryTrait +{ + private string $path; + + public function __construct( + string $binary + ) { + $name = $this->name(); + if ($binary === '') { + throw new RuntimeException("{$name} binary not provided", 1); + } + $finder = new ExecutableFinder(); + $binary = $finder->find($binary); + if ($binary === null) { + throw new RuntimeException("{$name} binary not found", 2); + } + if (! is_executable($binary)) { + throw new RuntimeException("{$name} binary is not executable", 3); + } + $this->path = $binary; + } + + abstract public function name(): string; + + public function path(): string + { + return $this->path; + } +} diff --git a/app/src/Legacy/Classes/Upload.php b/app/src/Legacy/Classes/Upload.php index 97460cd..b7e5e54 100644 --- a/app/src/Legacy/Classes/Upload.php +++ b/app/src/Legacy/Classes/Upload.php @@ -15,9 +15,14 @@ use Chevereto\Config\Config; use Exception; use Intervention\Image\ImageManagerStatic; use LogicException; +use PHPExif\Adapter\Exiftool as ExifToolAdapter; +use PHPExif\Enum\ReaderType as ExifReaderType; use PHPExif\Exif; +use PHPExif\Reader\Reader as ExifReader; +use RuntimeException; use Throwable; use function Chevere\Message\message; +use function Chevereto\Legacy\G\absolute_to_url; use function Chevereto\Legacy\G\add_ending_slash; use function Chevereto\Legacy\G\ends_with; use function Chevereto\Legacy\G\fetch_url; @@ -96,6 +101,8 @@ class Upload private string $source_filename; + private ?string $checksum = null; + public function uploaded(): array { return $this->uploaded; @@ -183,6 +190,11 @@ class Upload $this->name = $name; } + public function setChecksum(string $checksum): void + { + $this->checksum = $checksum; + } + public function setOptions(array $options): void { $this->options = $options; @@ -212,7 +224,11 @@ class Upload if (! is_array($this->options['allowed_formats'])) { $this->options['allowed_formats'] = explode(',', $this->options['allowed_formats']); } - $this->source_name = get_basename_without_extension($this->type === 'url' ? $this->source : $this->source['name']); + $this->source_name = get_basename_without_extension( + $this->type === 'url' + ? $this->source + : $this->source['name'] + ); $this->extension = $this->source_image_fileinfo['extension']; if ($this->extension === 'jpeg' && $this->source_extension === 'jpg') { $this->extension = 'jpg'; @@ -224,15 +240,45 @@ class Upload if (get_file_extension($this->name) === $this->extension) { $this->name = get_basename_without_extension($this->name); } - $this->fixed_filename = preg_replace('/(.*)\.(th|md|original|lg)\.([\w]+)$/', '$1.$3', $this->name . '.' . $this->extension); + $this->fixed_filename = preg_replace( + '/(.*)\.(th|md|original|lg)\.([\w]+)$/', + '$1.$3', + $this->name . '.' . $this->extension + ); $is_360 = false; if (in_array($this->extension, ['jpg', 'jpeg'], true)) { + $exifToolBinary = env()['CHEVERETO_BINARY_EXIFTOOL'] ?? ''; + if ($exifToolBinary !== '') { + try { + $exifTool = new ExifTool($exifToolBinary); + } catch (RuntimeException) { + } + // ExifTool adapter doesn't work with SONY exif + // Mind to check in the future? + // $adapter = new ExifToolAdapter([ + // 'toolPath' => $exifTool->binary(), + // ]); + // $reader = new ExifReader($adapter); + } + $exifTranBinary = env()['CHEVERETO_BINARY_EXIFTRAN'] ?? ''; + if ($exifTranBinary !== '') { + try { + $exifTran = new ExifTran($exifTranBinary); + } catch (RuntimeException) { + } + } + $reader = ExifReader::factory(ExifReaderType::NATIVE); $xmpDataExtractor = new XmpMetadataExtractor(); $xmpData = $xmpDataExtractor->extractFromFile($this->downstream); - $reader = \PHPExif\Reader\Reader::factory(\PHPExif\Reader\Reader::TYPE_NATIVE); $is_360 = false; - if (isset($xmpData['rdf:RDF']['rdf:Description']['@attributes']['ProjectionType'])) { - $is_360 = $xmpData['rdf:RDF']['rdf:Description']['@attributes']['ProjectionType'] === 'equirectangular'; + if ($xmpData['rdf:RDF']['rdf:Description'] ?? false) { + $projectionType = $xmpData['rdf:RDF']['rdf:Description']['@attributes']['ProjectionType'] + ?? $xmpData['rdf:RDF']['rdf:Description'][0]['GPano:ProjectionType'] + ?? ''; + $usePanoramaViewer = $xmpData['rdf:RDF']['rdf:Description'][0]['GPano:UsePanoramaViewer'] + ?? ''; + $is_360 = strtolower($projectionType) === 'equirectangular' + || strtolower($usePanoramaViewer) === 'true'; } if (array_key_exists('exif', $this->options)) { try { @@ -247,22 +293,30 @@ class Upload $orientation = false; } if ($orientation !== false) { - ImageManagerStatic::make($this->downstream)->orientate()->save(); + if (isset($exifTran)) { + $exifTran->orientate($this->downstream); + } else { + ImageManagerStatic::make($this->downstream)->orientate()->save(); + } } } if (! $this->options['exif']) { $this->source_image_exif = null; - if (ImageManagerStatic::getManager()->config['driver'] === 'imagick') { - $img = ImageManagerStatic::make($this->downstream); - $img->getCore()->stripImage(); - $img->save(); + if (isset($exifTool)) { + $exifTool->strip($this->downstream); } else { - $img = @imagecreatefromjpeg($this->downstream); - if ($img) { - imagejpeg($img, $this->downstream, 90); - imagedestroy($img); + if (ImageManagerStatic::getManager()->config['driver'] === 'imagick') { + $img = ImageManagerStatic::make($this->downstream); + $img->getCore()->stripImage(); + $img->save(); } else { - throw new Exception('Unable to create a new JPEG without Exif data', 644); + $img = @imagecreatefromjpeg($this->downstream); + if ($img) { + imagejpeg($img, $this->downstream, 90); + imagedestroy($img); + } else { + throw new Exception('Unable to create a new JPEG without Exif data', 644); + } } } } @@ -296,7 +350,7 @@ class Upload } try { - $uploaded = rename($this->downstream, $this->uploaded_file); + $uploaded = rename($this->downstream, $this->uploaded_file); // slow: 6s } catch (Throwable) { $uploaded = file_exists($this->uploaded_file); } @@ -312,12 +366,10 @@ class Upload } catch (Throwable) { } } - $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 = $this->source_image_fileinfo; + $fileInfo['filename'] = basename($this->uploaded_file); + $fileInfo['name'] = get_basename_without_extension($this->uploaded_file); + $fileInfo['url'] = absolute_to_url($this->uploaded_file); $fileInfo['is_360'] = $is_360; $frameFile = null; if ($this->mediaType === 'video') { @@ -355,15 +407,18 @@ class Upload ]; } - public static function getTempNam(string $failoverDirectory = ''): string + public static function getTempNam(string $failoverDir = '', string $suffix = ''): string { - if ($failoverDirectory === '') { - $failoverDirectory = sys_get_temp_dir(); + if ($failoverDir === '') { + $failoverDir = sys_get_temp_dir(); } - $prefix = env()['CHEVERETO_ID_HANDLE'] . 'chvtemp_'; - $tempNam = @tempnam(sys_get_temp_dir(), $prefix); + $chvIdPrefix = 'chv' + . env()['CHEVERETO_ID'] + . '_upload_' + . $suffix; + $tempNam = @tempnam(sys_get_temp_dir(), $chvIdPrefix); if (! $tempNam || ! @is_writable($tempNam)) { - $tempNam = @tempnam($failoverDirectory, $prefix); + $tempNam = @tempnam($failoverDir, $chvIdPrefix); if (! $tempNam) { throw new Exception("Can't get a tempnam", 600); } @@ -519,8 +574,8 @@ class Upload ? 'video' : 'image'; $this->source_image_fileinfo = $this->mediaType === 'video' - ? get_video_fileinfo($this->downstream) - : get_image_fileinfo($this->downstream); + ? get_video_fileinfo($this->downstream, $this->checksum) + : get_image_fileinfo($this->downstream, $this->checksum); if ($this->source_image_fileinfo === []) { throw new Exception("Can't get target upload source info", 610); } @@ -547,7 +602,7 @@ class Upload if ($this->source_image_fileinfo['extension'] === 'bmp') { $this->ImageConvert = new ImageConvert($this->downstream, 'png', $this->downstream); $this->downstream = $this->ImageConvert->out(); - $this->source_image_fileinfo = get_image_fileinfo($this->downstream); + $this->source_image_fileinfo = get_image_fileinfo($this->downstream, $this->checksum); } if ($this->source_image_fileinfo['extension'] === 'webp' && is_animated_webp($this->downstream) @@ -555,7 +610,6 @@ class Upload ) { throw new Exception('Animated WebP is not supported', 400); } - if ($this->mediaType === 'video') { return; } diff --git a/app/src/Legacy/Classes/Uploads.php b/app/src/Legacy/Classes/Uploads.php new file mode 100644 index 0000000..14d568c --- /dev/null +++ b/app/src/Legacy/Classes/Uploads.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Chevereto\Legacy\Classes; + +use Exception; +use PDO; +use function Chevereto\Legacy\getVariable; + +final class Uploads +{ + /** + * @return array The file path, and the upload params + */ + public static function join( + string $uploadPath, + int $uploadId, + string $token, + string $hash + ): array { + $calcHash = hash_hmac( + 'sha256', + $uploadId . $token, + getVariable('crypt_salt')->string() + ); + if (! hash_equals($calcHash, $hash)) { + throw new Exception('Invalid hash', 100); + } + $uploadsTable = DB::getTable('uploads'); + $uploadChunksTable = DB::getTable('uploads_chunks'); + $uploadSQL = <<query($uploadSQL); + $db->bind(':upload_id', $uploadId); + $db->bind(':token', $token); + $upload = $db->fetchSingle(); + if ($upload === false) { + throw new Exception('Invalid upload', 403); + } + $params = json_decode($upload['params'], true); + $chunks = $db->query($chunksSQL); + $db->bind(':upload_id', $uploadId); + $chunks = $db->fetchAll(PDO::FETCH_KEY_PAIR); + if ($chunks === false) { + throw new Exception('Missing chunked map', 403); + } + $tempName = Upload::getTempNam($uploadPath); + $chunkedFile = fopen($tempName, 'w'); + if ($chunkedFile === false) { + throw new Exception('Unable to open chunked file', 600); + } + foreach ($chunks as $chunkPath) { + if (! is_file($chunkPath)) { + throw new Exception('Missing chunk', 403); + } + $chunkFile = fopen($chunkPath, 'rb'); + if (! $chunkFile) { + throw new Exception('Unable to open chunk', 600); + } + if (stream_copy_to_stream($chunkFile, $chunkedFile) === false) { + fclose($chunkFile); + + throw new Exception('Failed copying chunk', 600); + } + fclose($chunkFile); + unlink($chunkPath); + } // slow: 4s + fclose($chunkedFile); + DB::delete('uploads', [ + 'id' => $uploadId, + ]); + + return [$tempName, $params]; + } +} diff --git a/app/src/Legacy/Classes/User.php b/app/src/Legacy/Classes/User.php index cf4ada5..99e6054 100644 --- a/app/src/Legacy/Classes/User.php +++ b/app/src/Legacy/Classes/User.php @@ -100,48 +100,81 @@ class User ]; } + public static function getCacheKey(int $id, string ...$locator): string + { + $components = ['u', (string) $id]; + array_push($components, ...$locator); + + return implode(':', $components); + } + + public static function deleteAlbumsCache(int $userId): void + { + $cacheKey = static::getCacheKey($userId, 'albums'); + Cache::instance()->delete($cacheKey); + } + public static function getAlbums(int|array $var): array { $id = is_array($var) ? $var['id'] : $var; - $user_albums = []; - $user_stream = self::getStreamAlbum($var); - if (is_array($user_stream)) { - $user_albums['stream'] = $user_stream; - } - $map = []; - $children = []; - $db = DB::getInstance(); - $db->query( - 'SELECT * FROM ' - . DB::getTable('albums') - . ' WHERE album_user_id=:image_user_id ORDER BY album_parent_id ASC, album_name ASC LIMIT :limit' - ); - $db->bind(':limit', intval(env()['CHEVERETO_MAX_USER_ALBUMS_LIST'] ?? 500)); - $db->bind(':image_user_id', $id); - $user_albums_db = $db->fetchAll(); - if ($user_albums_db) { - $user_albums += $user_albums_db; - } - foreach ($user_albums as $k => &$v) { - $album_id = isset($v['album_id']) - ? $v['album_id'] - : 'stream'; - $map[$album_id] = $k; - $parent_id = $v['album_parent_id'] ?? null; - if (isset($v['album_image_count']) && $v['album_image_count'] < 0) { - $v['album_image_count'] = 0; + $cacheKey = static::getCacheKey($id, 'albums'); + $cached = Cache::instance()->get($cacheKey) ?: []; + if ($cached) { + [$userAlbums, $children, $map] = $cached; + } else { + $userAlbums = []; + $user_stream = self::getStreamAlbum($var); + if ($user_stream === null || $user_stream['user_album_count'] === 0) { + return []; } - $children[$parent_id][$album_id] = $v['album_name']; - if (isset($parent_id)) { - asort($children[$parent_id]); + unset($user_stream['user_album_count']); + $userAlbums['stream'] = $user_stream; + $map = []; + $children = []; + $columns = [ + 'album_id', + 'album_name', + 'album_privacy', + 'album_parent_id', + 'album_image_count', + 'album_cover_id', + ]; + $columnsString = implode(', ', $columns); + $tableAlbums = DB::getTable('albums'); + $db = DB::getInstance(); + $db->query( + <<bind(':limit', intval(env()['CHEVERETO_MAX_USER_ALBUMS_LIST'])); + $db->bind(':image_user_id', $id); + $user_albums_db = $db->fetchAll(); + if ($user_albums_db) { + $userAlbums += $user_albums_db; } - } - if (count($children[''] ?? []) === 0) { - return []; + foreach ($userAlbums as $k => &$v) { + $album_id = isset($v['album_id']) + ? $v['album_id'] + : 'stream'; + $map[$album_id] = $k; + $parent_id = $v['album_parent_id'] ?? null; + if (isset($v['album_image_count']) && $v['album_image_count'] < 0) { + $v['album_image_count'] = 0; + } + $children[$parent_id][$album_id] = $v['album_name']; + if (isset($parent_id)) { + asort($children[$parent_id]); + } + } + Cache::instance()->set($cacheKey, [$userAlbums, $children, $map], 3600); } $list = []; - foreach (array_keys($children['']) as $key) { - self::iterate((string) $key, $children, $list, $user_albums, $map, 0); + foreach (array_keys($children[''] ?? []) as $key) { + self::iterate((string) $key, $children, $list, $userAlbums, $map, 0); } return $list; @@ -160,6 +193,7 @@ class User 'album_user_id' => $user['id'], 'album_privacy' => 'public', 'album_url' => $user['url'], + 'user_album_count' => (int) ($user['album_count'] ?? 0), ]; } @@ -237,7 +271,11 @@ class User ); if (! Login::isAdmin()) { $db = DB::getInstance(); - $db->query('SELECT COUNT(*) c FROM ' . DB::getTable('users') . ' WHERE user_registration_ip=:ip AND user_status != "valid" AND user_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 2 DAY)'); + $db->query( + 'SELECT COUNT(*) c FROM ' + . DB::getTable('users') + . ' WHERE user_registration_ip=:ip AND user_status != "valid" AND user_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 2 DAY)' + ); $db->bind(':ip', $values['registration_ip']); if ($db->fetchSingle()['c'] > 5) { throw new Exception('Flood detected', 666); @@ -546,31 +584,22 @@ class User '%user_id' => $user['id'], ]); DB::queryExecute($sql); - DB::delete('albums', [ - 'user_id' => $user['id'], - ]); - DB::delete('images', [ - 'user_id' => $user['id'], - ]); - DB::delete('login_connections', [ - 'user_id' => $user['id'], - ]); - DB::delete('login_cookies', [ - 'user_id' => $user['id'], - ]); - DB::delete('login_passwords', [ - 'user_id' => $user['id'], - ]); - DB::delete('likes', [ - 'user_id' => $user['id'], - ]); - DB::delete('follows', [ - 'user_id' => $user['id'], - 'followed_user_id' => $user['id'], - ], 'OR'); - DB::delete('users', [ - 'id' => $user['id'], - ]); + $deleteTemplate = <<get('variables'); + } + if ($cached) { + static::$variables = $cached; + static::$instance = $this; + + return; + } + try { $rows = DB::get( table: 'variables', @@ -78,6 +90,7 @@ class Variable ); } static::$instance = $this; + Cache::instance()->set('variables', static::$variables); } public static function getAll(): array @@ -130,6 +143,7 @@ class Variable value: $value, type: $type, ); + Cache::instance()->set('variables', static::$variables); } return $return; @@ -165,6 +179,7 @@ class Variable ] ); unset(static::$variables[$name]); + Cache::instance()->set('variables', static::$variables); return $return; } diff --git a/app/src/Legacy/G/DB.php b/app/src/Legacy/G/DB.php index 91cdd0f..73375d2 100644 --- a/app/src/Legacy/G/DB.php +++ b/app/src/Legacy/G/DB.php @@ -261,7 +261,8 @@ class DB array $sort = [], ?int $limit = null, int $fetch_style = PDO::FETCH_ASSOC, - array $valuesOperators = [] + array $valuesOperators = [], + array $columns = [], ): mixed { if (! is_array($where) && $where !== 'all') { throw new Exception('Expecting array values, ' . gettype($where) . ' given'); @@ -272,7 +273,13 @@ class DB $table = $table['table']; } $table = self::getTable($table); - $query = 'SELECT * FROM ' . $table; + $selectColumns = implode(', ', $columns); + if (empty($selectColumns)) { + $selectColumns = '*'; + } + $query = <<fetchAll($mode) ?: []; } + /** + * @param string $table Table name (no prefix). + * @param string $constraint Foreign key name (no prefix). + */ + public function getSqlDropForeignKey(string $table, string $constraint): string + { + $table = self::getTable($table); + $constraint = self::getTable($constraint); + $stmt = static::$dbh->prepare( + <<execute([$table, $constraint]); + if ($stmt->fetchColumn() > 0) { + return <<prepare( + <<execute([$table, $index]); + if ($stmt->fetchColumn() > 0) { + return <<handled_request; } - public function request_array(): array - { - return $this->request_array; - } - public function template(): string { return $this->template; @@ -209,6 +206,11 @@ class Handler $this->template = $template; } + public function setContent(string $content): void + { + $this->content = $content; + } + public function setPathTheme(string $path): void { $this->path_theme = $path; @@ -477,10 +479,14 @@ class Handler return (bool) preg_match('{index\.php$}', ltrim($this->script_name, '/')); } - private function loadTemplate(?string $template = null): void + private function loadTemplate(): void { - if ($template !== null) { - $this->template = $template; + if (isset($this->content)) { + require_theme_header(); + echo $this->content; + require_theme_footer(); + + return; } $functions_basename = 'functions.php'; $template_functions = [ diff --git a/app/src/Legacy/G/functions.php b/app/src/Legacy/G/functions.php index 2e16987..a2295a6 100644 --- a/app/src/Legacy/G/functions.php +++ b/app/src/Legacy/G/functions.php @@ -23,6 +23,7 @@ use GdImage; use LogicException; use Throwable; use function Chevereto\Legacy\getCheveretoEnv; +use function Chevereto\Legacy\hashFile; use function Chevereto\Vars\env; use function Chevereto\Vars\server; use function Safe\curl_exec; @@ -2453,13 +2454,13 @@ function extension_to_mime(string $ext): string function get_ffmpeg_error(Throwable $e): string { $previous = $e->getPrevious() ? - (': ' . $e->getPrevious()->getMessage()) : + (' [' . $e->getPrevious()->getMessage() . ']') : ''; return $e->getMessage() . $previous; } -function get_video_fileinfo(string $file): array +function get_video_fileinfo(string $file, ?string $checksum = null): array { clearstatcache(true, $file); @@ -2504,12 +2505,12 @@ function get_video_fileinfo(string $file): array 'bits' => $all['bits_per_raw_sample'] ?? 0, 'channels' => '', 'url' => absolute_to_url($file), - 'md5' => md5_file($file), + 'checksum' => $checksum ?? hashFile($file), 'duration' => (int) $duration, ]; } -function get_image_fileinfo(string $file): array +function get_image_fileinfo(string $file, ?string $checksum = null): array { clearstatcache(true, $file); $info = getimagesize($file); @@ -2533,7 +2534,7 @@ function get_image_fileinfo(string $file): array 'bits' => $info['bits'] ?? '', 'channels' => $info['channels'] ?? '', 'url' => absolute_to_url($file), - 'md5' => md5_file($file), + 'checksum' => $checksum ?? hashFile($file), ]; } diff --git a/app/src/Legacy/functions-render.php b/app/src/Legacy/functions-render.php index 922e54c..a6cbbfd 100644 --- a/app/src/Legacy/functions-render.php +++ b/app/src/Legacy/functions-render.php @@ -161,13 +161,14 @@ function get_checkbox_html($options = []) ' ' . "\n" . ''; } -function get_captcha_component($id = 'g-recaptcha') +function get_captcha_component() { return match (getSetting('captcha_api')) { - '2', 'hcaptcha' => [ - 'captcha_html', strtr('
    ', [ - '%id' => $id, - ])], + '2', 'hcaptcha', 'turnstile' => [ + 'captcha_html', strtr('
    ', [ + '%api' => getSetting('captcha_api'), + ]), + ], '3' => ['recaptcha_invisible_html', get_captcha_invisible_html()], default => throw new LogicException(message('Invalid captcha API')), }; @@ -367,7 +368,7 @@ function include_peafowl_foot() { display_cookie_law_banner(); $resources = [ - // 'chevereto' => PATH_PUBLIC_CONTENT_LEGACY_THEMES_PEAFOWL_LIB . 'chevereto-all.js', + 'chevereto' => PATH_PUBLIC_CONTENT_LEGACY_THEMES_PEAFOWL_LIB . 'chevereto-all.js', 'chevereto' => PATH_PUBLIC_CONTENT_LEGACY_THEMES_PEAFOWL_LIB . 'chevereto-all.min.js', ]; foreach ($resources as $k => &$v) { @@ -385,6 +386,13 @@ function include_peafowl_foot() theme: "%t" }); JS, + 'turnstile' => << << '', 'hcaptcha' => '', '3' => '', + 'turnstile' => '', default => throw new LogicException(message('Invalid captcha API')), }; } @@ -1051,7 +1060,9 @@ function arr_printer($arr, $tpl = '', $wrap = []) } function versionize_src($src) { - return $src . '?' . md5(get_chevereto_version()); + return $src + . '?' + . hashString(get_chevereto_version()); } function show_banner($banner, $sfw = true) { diff --git a/app/src/Legacy/functions.php b/app/src/Legacy/functions.php index 68ef509..b54ac52 100644 --- a/app/src/Legacy/functions.php +++ b/app/src/Legacy/functions.php @@ -20,7 +20,10 @@ use Chevere\xrDebug\PHP\Xr; use Chevere\xrDebug\PHP\XrInstance; use Chevereto\Config\Config; use Chevereto\Legacy\Classes\AssetStorage; +use Chevereto\Legacy\Classes\Cache; use Chevereto\Legacy\Classes\DB; +use Chevereto\Legacy\Classes\KeyValue; +use Chevereto\Legacy\Classes\KeyValueNull; use Chevereto\Legacy\Classes\L10n; use Chevereto\Legacy\Classes\Login; use Chevereto\Legacy\Classes\Mailer; @@ -34,6 +37,7 @@ use Chevereto\Vars\EnvVar; use Chevereto\Vars\FilesVar; use Chevereto\Vars\GetVar; use Chevereto\Vars\PostVar; +use Chevereto\Vars\RequestHeadersVar; use Chevereto\Vars\RequestVar; use Chevereto\Vars\ServerVar; use Chevereto\Vars\SessionVar; @@ -45,6 +49,7 @@ use OutOfBoundsException; use OverflowException; use PDO; use PHPMailer\PHPMailer\SMTP; +use Redis; use RuntimeException; use Throwable; use function Chevere\Filesystem\filePhpForPath; @@ -64,7 +69,7 @@ use function Chevereto\Legacy\G\get_bytes; use function Chevereto\Legacy\G\get_client_ip; use function Chevereto\Legacy\G\get_current_url; use function Chevereto\Legacy\G\get_file_extension; -use function Chevereto\Legacy\G\get_image_fileinfo as GGet_image_fileinfo; +use function Chevereto\Legacy\G\get_image_fileinfo; use function Chevereto\Legacy\G\get_ini_bytes; use function Chevereto\Legacy\G\get_public_url; use function Chevereto\Legacy\G\hasEnvDbInfo; @@ -264,28 +269,28 @@ function get_chv_default_setting(string $value = '', bool $safe = false): mixed return $safe ? safe_html($return) : $return; } -function getStorages(): array|bool +function getStoragesFormList(): array { - $where = []; if (version_compare(cheveretoVersionInstalled(), '4.2.0', '>=')) { - $where = [ - 'deleted_at' => null, - ]; - } - $storages = DB::get( - table: 'storages', - where: $where, - ); - if ($storages) { - foreach ($storages as $k => $v) { - $storages[$k] = DB::formatRow($v); - } - $return = $storages; + $where = 'WHERE storage_deleted_at IS NULL'; } else { - $return = false; + $where = ''; + } + $db = DB::getInstance(); + $tableStorages = DB::getTable('storages'); + $db->query( + <<fetchAll() ?: []; + foreach ($rows as &$v) { + $v = DB::formatRow($v); } - return $return; + return $rows; } function get_banner_code(string $banner, bool $safe_html = true): string @@ -376,6 +381,10 @@ function getSystemNotices(): array } if (preg_match('/@chevereto\.example/', getSetting('email_from_email')) || preg_match('/@chevereto\.example/', getSetting('email_incoming_email')) + || ( + env()['CHEVERETO_SERVICING'] !== 'server' + && empty(getSetting('email_smtp_server')) + ) ) { $system_notices[] = _s( "You haven't changed the default email settings. Go to %emailSettings% to fix this.", @@ -395,9 +404,9 @@ function getSystemNotices(): array [ // '%c' => $minActiveStorages, '%s' => '' - . _s('External storage') + . _s('Upload storage') . '', ] ); @@ -413,9 +422,9 @@ function getSystemNotices(): array $system_notices[] = _s( 'You need to configure %s to upload website assets.', '' - . _s('Asset storage') + . _s('Site storage') . '' ); } @@ -457,21 +466,33 @@ function captcha_check(): object { if (getSetting('captcha_api') == '3') { return (object) [ - 'is_valid' => sessionVar()->hasKey('isHuman') + 'is_valid' => sessionVar()->has('isHuman') ? (bool) session()['isHuman'] : false, ]; } - $endpoint = match (getSetting('captcha_api')) { - '2' => 'https://www.recaptcha.net/recaptcha/api/siteverify', - 'hcaptcha' => 'https://hcaptcha.com/siteverify', - default => throw new LogicException(message('Invalid captcha API')), - }; + switch (getSetting('captcha_api')) { + case '2': + $endpoint = 'https://www.recaptcha.net/recaptcha/api/siteverify'; + $response = post()['g-recaptcha-response'] ?? ''; + + break; + case 'hcaptcha': + $endpoint = 'https://hcaptcha.com/siteverify'; + $response = post()['h-captcha-response'] ?? ''; + + break; + case 'turnstile': + $endpoint = 'https://challenges.cloudflare.com/turnstile/v0/siteverify'; + $response = post()['cf-turnstile-response'] ?? ''; + + break; + default: + throw new LogicException(message('Invalid captcha API')); + } $params = [ 'secret' => getSetting('captcha_secret'), - 'response' => post()['g-recaptcha-response'] - ?? post()['h-captcha-response'] - ?? '', + 'response' => $response, 'remoteip' => get_client_ip(), ]; $fetch = fetch_url( @@ -674,7 +695,7 @@ function sessionCrypt(string $string, bool $encrypt = true): string|bool * @var string $iv */ $fn = 'openssl_' . ($encrypt ? 'encrypt' : 'decrypt'); - $secret = md5( + $secret = hashString( getVariable('crypt_salt')->nullString() ?? '' ); @@ -798,7 +819,7 @@ function upload_to_content_images(array $source, string $what): void } $name = $typeArr[$what]['name']; if ($typeArr[$what]['type'] === 'image') { - $fileinfo = GGet_image_fileinfo($source['tmp_name']); + $fileinfo = get_image_fileinfo($source['tmp_name']); switch ($what) { case 'favicon_image': if (! $fileinfo['ratio']) { @@ -920,7 +941,7 @@ function upload_to_content_images(array $source, string $what): void 'key' => $what, ]); $dbArray = [ - 'md5' => md5_file($uploaded['file']), + 'checksum' => hashFile($uploaded['file']), 'filename' => $filename, 'file_path' => $storagePath, 'blob' => $fp, @@ -1091,7 +1112,6 @@ function loaderHandler( $envVar['CHEVERETO_ID_HANDLE'] = ''; $envVar = array_merge($envVar, array ( 'CHEVERETO_EDITION' => 'free', - 'CHEVERETO_ENABLE_API_GUEST' => '0', 'CHEVERETO_ENABLE_BANNERS' => '0', 'CHEVERETO_ENABLE_CAPTCHA' => '0', 'CHEVERETO_ENABLE_CONSENT_SCREEN' => '0', @@ -1135,10 +1155,13 @@ function loaderHandler( 'post_max_size' => 'CHEVERETO_MAX_POST_SIZE', 'session.save_handler' => 'CHEVERETO_SESSION_SAVE_HANDLER', 'session.save_path' => 'CHEVERETO_SESSION_SAVE_PATH', - // 'upload_max_filesize' => 'CHEVERETO_MAX_UPLOAD_SIZE', // INI_PERDIR + // 'upload_max_filesize' => 'CHEVERETO_MAX_UPLOAD_FILE_SIZE', // INI_PERDIR ]; + $isCLI = PHP_SAPI === 'cli'; $iniToSkip = [ - 'max_execution_time' => PHP_SAPI === 'cli', + 'max_execution_time' => $isCLI, + 'session.save_handler' => $isCLI, + 'session.save_path' => $isCLI, ]; foreach ($iniToChevereto as $iniOption => $envName) { if (($iniToSkip[$iniOption] ?? false) === true) { @@ -1185,6 +1208,9 @@ function loaderHandler( $envVar['CHEVERETO_XRDEBUG_HOST'] = 'host.docker.internal'; } } + if ($envVar['CHEVERETO_MAX_LISTING_ITEMS_PER_PAGE'] === '0') { + $envVar['CHEVERETO_MAX_LISTING_ITEMS_PER_PAGE'] = ''; + } foreach ($envVar as $envName => &$envValue) { if (! is_string($envValue)) { try { @@ -1205,8 +1231,35 @@ function loaderHandler( new PostVar($_post); new GetVar($_get); new FilesVar($_files); + new RequestHeadersVar($_server); require_once PATH_APP . 'configurator.php'; - if ($_session === []) { + if (env()['CHEVERETO_CACHE_DRIVER'] === 'redis' + && env()['CHEVERETO_CACHE_HOST'] !== '' + && env()['CHEVERETO_CACHE_PORT'] !== '' + ) { + if (! class_exists('Redis')) { + throw new RuntimeException('Redis extension not loaded', 600); + } + $redis = new Redis(); + $redis->connect(env()['CHEVERETO_CACHE_HOST'], (int) env()['CHEVERETO_CACHE_PORT']); + if (env()['CHEVERETO_CACHE_PASSWORD'] !== '') { + $redis->auth(env()['CHEVERETO_CACHE_PASSWORD']); + } + $keyValue = new KeyValue( + $redis, + env()['CHEVERETO_CACHE_KEY_PREFIX'], + (int) env()['CHEVERETO_MAX_CACHE_TTL'], + ); + } else { + $keyValue = new KeyValueNull( + env()['CHEVERETO_CACHE_KEY_PREFIX'] + ); + } + new Cache($keyValue); + if ($_session === [] + && session_status() === PHP_SESSION_NONE + && ACCESS === 'web' + ) { $session_start = false; $session_options = [ 'save_handler' => Config::system()->sessionSaveHandler(), @@ -1262,6 +1315,20 @@ function loaderHandler( : ''; define('URL_APP_PUBLIC', HTTP_APP_PROTOCOL . '://' . Config::host()->hostname() . $httpPort . Config::host()->hostnamePath()); phpCheck(Config::system()); + + try { + $xrArguments = [ + 'isEnabled' => (bool) (env()['CHEVERETO_ENABLE_XRDEBUG']), + 'isHttps' => (bool) (env()['CHEVERETO_XRDEBUG_HTTPS']), + 'host' => (string) (env()['CHEVERETO_XRDEBUG_HOST']), + 'port' => (int) (env()['CHEVERETO_XRDEBUG_PORT']), + 'key' => (string) (env()['CHEVERETO_XRDEBUG_KEY']), + ]; + + new XrInstance(new Xr(...$xrArguments)); + } catch (Throwable) { + // Silent failover + } if (hasEnvDbInfo()) { DB::fromEnv(); } @@ -1290,20 +1357,6 @@ function loaderHandler( } } } - - try { - $xrArguments = [ - 'isEnabled' => (bool) (env()['CHEVERETO_ENABLE_XRDEBUG']), - 'isHttps' => (bool) (env()['CHEVERETO_XRDEBUG_HTTPS']), - 'host' => (string) (env()['CHEVERETO_XRDEBUG_HOST']), - 'port' => (int) (env()['CHEVERETO_XRDEBUG_PORT']), - 'key' => (string) (env()['CHEVERETO_XRDEBUG_KEY']), - ]; - - new XrInstance(new Xr(...$xrArguments)); - } catch (Throwable) { - // Silent failover - } $uploadImageFolder = cheveretoVersionInstalled() !== '' ? Settings::get('upload_image_path') : 'images'; @@ -1325,22 +1378,38 @@ function loaderHandler( if (is_valid_timezone(Settings::get('default_timezone'))) { date_default_timezone_set(Settings::get('default_timezone')); } - if (ACCESS === 'web') { - $upload_max_filesize_mb_db = Settings::get('upload_max_filesize_mb'); - $upload_max_filesize_mb_bytes = get_bytes($upload_max_filesize_mb_db . 'MB'); - $ini_upload_max_filesize = get_ini_bytes(ini_get('upload_max_filesize')); - $ini_post_max_size = ((int) ini_get('post_max_size')) === 0 - ? $ini_upload_max_filesize - : get_ini_bytes( - ini_get('post_max_size') - ); - Settings::setValue( - 'true_upload_max_filesize', - min($ini_upload_max_filesize, $ini_post_max_size) + $ini_upload_max_filesize = get_ini_bytes(ini_get('upload_max_filesize')); + $ini_post_max_size = ((int) ini_get('post_max_size')) === 0 + ? $ini_upload_max_filesize + : get_ini_bytes( + ini_get('post_max_size') ); - if (Settings::get('true_upload_max_filesize') < $upload_max_filesize_mb_bytes) { + Settings::setValue( + 'true_upload_max_filesize', + min($ini_upload_max_filesize, $ini_post_max_size) + ); + $chunk_size = get_ini_bytes(env()['CHEVERETO_MAX_CHUNK_UPLOAD_SIZE']); + Settings::setValue( + 'chunk_upload_size', + min($chunk_size, Settings::get('true_upload_max_filesize')) + ); + $maxUploadSize = get_ini_bytes(env()['CHEVERETO_MAX_UPLOAD_SIZE']); + if ($maxUploadSize > 0) { + $maxUploadSizeMb = bytes_to_mb($maxUploadSize); + $upload_max_filesize_mb = (float) Settings::get('upload_max_filesize_mb'); + if ($upload_max_filesize_mb == 0 + || $upload_max_filesize_mb > $maxUploadSizeMb + ) { Settings::update([ - 'upload_max_filesize_mb' => bytes_to_mb((int) Settings::get('true_upload_max_filesize')), + 'upload_max_filesize_mb' => $maxUploadSizeMb, + ]); + } + $upload_max_filesize_mb_guest = (float) Settings::get('upload_max_filesize_mb_guest'); + if ($upload_max_filesize_mb_guest == 0 + || $upload_max_filesize_mb_guest > $maxUploadSizeMb + ) { + Settings::update([ + 'upload_max_filesize_mb_guest' => $maxUploadSizeMb, ]); } } @@ -1670,3 +1739,14 @@ function isPublicHost(string $host): bool return $typeName === $typePub; } + +function hashFile(string $file): string +{ + // We use xxh64 as xxh128 is not available (web browser) @ 2025-04-28 + return hash_file('xxh64', $file); +} + +function hashString(string $string): string +{ + return hash('xxh128', $string); +} diff --git a/app/src/Storage/Storage.php b/app/src/Storage/Storage.php deleted file mode 100644 index 8f888eb..0000000 --- a/app/src/Storage/Storage.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Storage; - -use League\Flysystem\FilesystemAdapter; -use League\Flysystem\Local\LocalFilesystemAdapter; - -final class Storage implements StorageInterface -{ - public function __construct(private string $location) - { - } - - final public function adapter(): FilesystemAdapter - { - return new LocalFilesystemAdapter($this->location); - } -} diff --git a/app/src/Storage/StorageInterface.php b/app/src/Storage/StorageInterface.php deleted file mode 100644 index cb94e64..0000000 --- a/app/src/Storage/StorageInterface.php +++ /dev/null @@ -1,21 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Storage; - -use League\Flysystem\FilesystemAdapter; - -interface StorageInterface -{ - public function __construct(string $location); - - public function adapter(): FilesystemAdapter; -} diff --git a/app/src/Vars/EnvVar.php b/app/src/Vars/EnvVar.php index 3f9a857..5152853 100644 --- a/app/src/Vars/EnvVar.php +++ b/app/src/Vars/EnvVar.php @@ -12,9 +12,6 @@ namespace Chevereto\Vars; use Chevereto\Vars\Traits\ImmutableMapTrait; -use Ds\Map; -use function Chevere\Parameter\iterable; -use function Chevere\Parameter\string; final class EnvVar { @@ -40,10 +37,6 @@ final class EnvVar } unset($array[$key]); } - iterable( - V: string(), - K: string(self::REGEX_KEY) - )($array); $this->assertNoInstance(); foreach (self::PUTENV as $putenv) { if (array_key_exists($putenv, $array)) { @@ -51,6 +44,5 @@ final class EnvVar } } static::$array = $array; - static::$map = new Map($array); } } diff --git a/app/src/Vars/RequestHeadersVar.php b/app/src/Vars/RequestHeadersVar.php new file mode 100644 index 0000000..243ed07 --- /dev/null +++ b/app/src/Vars/RequestHeadersVar.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Chevereto\Vars; + +use Chevereto\Vars\Traits\ImmutableMapTrait; + +final class RequestHeadersVar +{ + use ImmutableMapTrait; + + public function __construct(array $array) + { + $this->assertNoInstance(); + $headers = []; + foreach ($array as $key => $value) { + if (str_starts_with($key, 'HTTP_')) { + $header = str_replace('_', '-', ucwords(strtolower(substr($key, 5)), '_')); + $headers[$header] = $value; + } + } + static::$array = $headers; + } +} diff --git a/app/src/Vars/Traits/ImmutableMapTrait.php b/app/src/Vars/Traits/ImmutableMapTrait.php index 2099c6d..dbe62fd 100644 --- a/app/src/Vars/Traits/ImmutableMapTrait.php +++ b/app/src/Vars/Traits/ImmutableMapTrait.php @@ -12,8 +12,6 @@ namespace Chevereto\Vars\Traits; use Chevereto\Traits\Instance\AssertNoInstanceTrait; -use function DeepCopy\deep_copy; -use Ds\Map; trait ImmutableMapTrait { @@ -21,18 +19,10 @@ trait ImmutableMapTrait private static array $array = []; - private static Map $map; - public function __construct(array $array) { $this->assertNoInstance(); static::$array = $array; - static::$map = new Map($array); - } - - public static function map(): Map - { - return deep_copy(static::$map); } public static function toArray(): array diff --git a/app/src/Vars/Traits/MutableMapTrait.php b/app/src/Vars/Traits/MutableMapTrait.php index 5be2f6d..0eaca4f 100644 --- a/app/src/Vars/Traits/MutableMapTrait.php +++ b/app/src/Vars/Traits/MutableMapTrait.php @@ -11,9 +11,10 @@ namespace Chevereto\Vars\Traits; +use Chevere\DataStructure\Interfaces\MapMutableInterface; +use Chevere\DataStructure\MapMutable; use Chevereto\Traits\Instance\AssertNoInstanceTrait; use Chevereto\Traits\Instance\AssertStaticInstanceTrait; -use Ds\Map; trait MutableMapTrait { @@ -21,15 +22,15 @@ trait MutableMapTrait use AssertNoInstanceTrait; - private static Map $map; + private static MapMutableInterface $map; public function __construct(array $array) { $this->assertNoInstance(); - static::$map = new Map($array); + static::$map = new MapMutable(...$array); } - public static function map(): Map + public static function map(): MapMutableInterface { return static::$map; } diff --git a/app/src/Vars/functions.php b/app/src/Vars/functions.php index f452383..393733f 100644 --- a/app/src/Vars/functions.php +++ b/app/src/Vars/functions.php @@ -11,61 +11,91 @@ namespace Chevereto\Vars; -use Ds\Map; +use Chevere\DataStructure\Interfaces\MapMutableInterface; use LogicException; function env(): array { - try { - return EnvVar::toArray(); - } catch (LogicException) { - return []; + static $cache; + if (! isset($cache)) { + try { + $cache = EnvVar::toArray(); + } catch (LogicException) { + $cache = []; + } } + + return $cache; } function request(): array { - try { - return RequestVar::toArray(); - } catch (LogicException) { - return []; + static $cache; + if (! isset($cache)) { + try { + $cache = RequestVar::toArray(); + } catch (LogicException) { + $cache = []; + } } + + return $cache; } function get(): array { - try { - return GetVar::toArray(); - } catch (LogicException) { - return []; + static $cache; + if (! isset($cache)) { + try { + $cache = GetVar::toArray(); + } catch (LogicException) { + $cache = []; + } } + + return $cache; } function post(): array { - try { - return PostVar::toArray(); - } catch (LogicException) { - return []; + static $cache; + if (! isset($cache)) { + try { + $cache = PostVar::toArray(); + } catch (LogicException) { + $cache = []; + } } + + return $cache; } function server(): array { - try { - return ServerVar::toArray(); - } catch (LogicException) { - return []; + static $cache; + if (! isset($cache)) { + try { + $cache = ServerVar::toArray(); + } catch (LogicException) { + $cache = []; + } } + + return $cache; } function files(): array { - try { - return FilesVar::toArray(); - } catch (LogicException) { - return []; + static $cache; + if (! isset($cache)) { + try { + $cache = FilesVar::toArray(); + } catch (LogicException) { + $cache = []; + } } + + return $cache; } function cookie(): array @@ -77,7 +107,7 @@ function cookie(): array } } -function cookieVar(): Map +function cookieVar(): MapMutableInterface { return CookieVar::map(); } @@ -91,7 +121,21 @@ function session(): array } } -function sessionVar(): Map +function sessionVar(): MapMutableInterface { return SessionVar::map(); } + +function requestHeaders(): array +{ + static $cache; + if (! isset($cache)) { + try { + $cache = RequestHeadersVar::toArray(); + } catch (LogicException) { + $cache = []; + } + } + + return $cache; +} diff --git a/app/src/Workflow/functions.php b/app/src/Workflow/functions.php deleted file mode 100644 index 3618fbe..0000000 --- a/app/src/Workflow/functions.php +++ /dev/null @@ -1,45 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflow; - -use Chevere\Workflow\Interfaces\JobInterface; -use function Chevere\Workflow\job; -use Chevereto\Actions\Auth\AuthVerifyRepositoryAccessAction; -use Chevereto\Actions\Auth\AuthVerifyResourceAccessAction; - -function stepVerifyResourceAccess( - string $resource, - string $level, - string $ownerUserId = '', - string $requesterUserId = '${REQUESTER_USER_ID}' -): JobInterface { - return job( - AuthVerifyResourceAccessAction::class, - requesterUserId: $requesterUserId, - resource: $resource, - level: $level, - ownerUserId: $ownerUserId - ); -} - -function stepVerifyRepositoryAccess( - string $repository, - string $level, - string $requesterUserId = '${REQUESTER_USER_ID}' -): JobInterface { - return job( - AuthVerifyRepositoryAccessAction::class, - requesterUserId: $requesterUserId, - repository: $repository, - level: $level, - ); -} diff --git a/app/src/Workflows/Album/AlbumDeleteWorkflow.php b/app/src/Workflows/Album/AlbumDeleteWorkflow.php deleted file mode 100644 index f9af5b9..0000000 --- a/app/src/Workflows/Album/AlbumDeleteWorkflow.php +++ /dev/null @@ -1,30 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\Album; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Workflow\BaseWorkflow; - -final class AlbumDeleteWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - get: job( - 'AlbumDeleteAction', - id: '${id}', - ) - ); - } -} diff --git a/app/src/Workflows/Album/AlbumGetWorkflow.php b/app/src/Workflows/Album/AlbumGetWorkflow.php deleted file mode 100644 index ddf133d..0000000 --- a/app/src/Workflows/Album/AlbumGetWorkflow.php +++ /dev/null @@ -1,30 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\Album; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Workflow\BaseWorkflow; - -final class AlbumGetWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - get: job( - 'AlbumGetAction', - id: '${id}', - ) - ); - } -} diff --git a/app/src/Workflows/Album/AlbumPatchWorkflow.php b/app/src/Workflows/Album/AlbumPatchWorkflow.php deleted file mode 100644 index c362ca6..0000000 --- a/app/src/Workflows/Album/AlbumPatchWorkflow.php +++ /dev/null @@ -1,31 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\Album; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Workflow\BaseWorkflow; - -final class AlbumPatchWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - edit: job( - 'AlbumEditAction', - albumId: '${album_id}', - cover_id: '${cover_id}', - ) - ); - } -} diff --git a/app/src/Workflows/Album/AlbumPostWorkflow.php b/app/src/Workflows/Album/AlbumPostWorkflow.php deleted file mode 100644 index 1f1e9c2..0000000 --- a/app/src/Workflows/Album/AlbumPostWorkflow.php +++ /dev/null @@ -1,31 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\Album; - -use Chevere\Action\Action; -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Workflow\BaseWorkflow; - -final class AlbumPostWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - step: job( - Action::class, - parameter: '${variable}', - ) - ); - } -} diff --git a/app/src/Workflows/Album/Like/AlbumLikeDeleteWorkflow.php b/app/src/Workflows/Album/Like/AlbumLikeDeleteWorkflow.php deleted file mode 100644 index 84fad74..0000000 --- a/app/src/Workflows/Album/Like/AlbumLikeDeleteWorkflow.php +++ /dev/null @@ -1,31 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\Album\Like; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Workflow\BaseWorkflow; - -final class AlbumLikeDeleteWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - delete: job( - 'DeleteAlbumLike', - id: '${id}', - userId: '${user_id}', - ) - ); - } -} diff --git a/app/src/Workflows/Album/Like/AlbumLikePostWorkflow.php b/app/src/Workflows/Album/Like/AlbumLikePostWorkflow.php deleted file mode 100644 index f954436..0000000 --- a/app/src/Workflows/Album/Like/AlbumLikePostWorkflow.php +++ /dev/null @@ -1,31 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\Album\Like; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Workflow\BaseWorkflow; - -final class AlbumLikePostWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - insert: job( - 'InsertAlbumLike', - id: '${id}', - userId: '${user_id}' - ) - ); - } -} diff --git a/app/src/Workflows/Ban/Ip/BanIpDeleteWorkflow.php b/app/src/Workflows/Ban/Ip/BanIpDeleteWorkflow.php deleted file mode 100644 index a5d1428..0000000 --- a/app/src/Workflows/Ban/Ip/BanIpDeleteWorkflow.php +++ /dev/null @@ -1,30 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\Ban\Ip; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Workflow\BaseWorkflow; - -final class BanIpDeleteWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - step: job( - 'ActionName', - parameter: '${variable}', - ) - ); - } -} diff --git a/app/src/Workflows/Ban/Ip/BanIpPatchWorkflow.php b/app/src/Workflows/Ban/Ip/BanIpPatchWorkflow.php deleted file mode 100644 index 857dc9d..0000000 --- a/app/src/Workflows/Ban/Ip/BanIpPatchWorkflow.php +++ /dev/null @@ -1,30 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\Ban\Ip; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Workflow\BaseWorkflow; - -final class BanIpPatchWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - step: job( - 'ActionName', - parameter: '${variable}', - ) - ); - } -} diff --git a/app/src/Workflows/Ban/Ip/BanIpPostWorkflow.php b/app/src/Workflows/Ban/Ip/BanIpPostWorkflow.php deleted file mode 100644 index 5b36b34..0000000 --- a/app/src/Workflows/Ban/Ip/BanIpPostWorkflow.php +++ /dev/null @@ -1,43 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\Ban\Ip; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Workflow\BaseWorkflow; - -final class BanIpPostWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - checkManager: job( - 'CheckManagerAction', - ), - validateIp: job( - 'ValidateIpAction', - ip: '${ip}', - ), - validateIpNotBanned: job( - 'ValidateIpNotBannedAction', - ip: '${ip}', - ), - insert: job( - 'InsertBanIpAction', - ip: '${ip}', - expires: '${expires}', - message: '${message}', - ), - ); - } -} diff --git a/app/src/Workflows/Category/CategoryPostWorkflow.php b/app/src/Workflows/Category/CategoryPostWorkflow.php deleted file mode 100644 index f9d2564..0000000 --- a/app/src/Workflows/Category/CategoryPostWorkflow.php +++ /dev/null @@ -1,39 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\Category; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Workflow\BaseWorkflow; - -final class CategoryPostWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - checkAdmin: job( - 'CheckAdminAction', - ), - checkAvailableUrlKey: job( - 'CheckAvailableUrlKey', - urlKey: '${url_key}', - ), - insert: job( - 'InsertCategoryAction', - name: '${name}', - urlKey: '${url_key}', - description: '${description}', - ) - ); - } -} diff --git a/app/src/Workflows/Image/Bulk/ImageBulkPatchWorkflow.php b/app/src/Workflows/Image/Bulk/ImageBulkPatchWorkflow.php deleted file mode 100644 index 36aaf6f..0000000 --- a/app/src/Workflows/Image/Bulk/ImageBulkPatchWorkflow.php +++ /dev/null @@ -1,31 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\Image\Bulk; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Workflow\BaseWorkflow; - -final class ImageBulkPatchWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - edit: job( - 'ImageBulkPatch', - image_ids: '${image_ids}', - payload: '${payload}', - ) - ); - } -} diff --git a/app/src/Workflows/Image/ImageGetWorkflow.php b/app/src/Workflows/Image/ImageGetWorkflow.php deleted file mode 100644 index d15bb22..0000000 --- a/app/src/Workflows/Image/ImageGetWorkflow.php +++ /dev/null @@ -1,30 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\Image; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Workflow\BaseWorkflow; - -final class ImageGetWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - get: job( - 'ImageGetAction', - id: '${id}', - ) - ); - } -} diff --git a/app/src/Workflows/Image/ImagePatchWorkflow.php b/app/src/Workflows/Image/ImagePatchWorkflow.php deleted file mode 100644 index 582aa06..0000000 --- a/app/src/Workflows/Image/ImagePatchWorkflow.php +++ /dev/null @@ -1,31 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\Image; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Workflow\BaseWorkflow; - -final class ImagePatchWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - edit: job( - 'ImageEditAction', - id: '${id}', - payload: '${payload}' - ) - ); - } -} diff --git a/app/src/Workflows/Image/ImagePostWorkflow.php b/app/src/Workflows/Image/ImagePostWorkflow.php deleted file mode 100644 index 698b0ce..0000000 --- a/app/src/Workflows/Image/ImagePostWorkflow.php +++ /dev/null @@ -1,107 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\Image; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Actions\Database\DatabaseReserveRowAction; -use Chevereto\Actions\File\FileFetchSourceAction; -use Chevereto\Actions\File\FileNamingAction; -use Chevereto\Actions\File\FileUploadAction; -use Chevereto\Actions\File\FileValidateAction; -use Chevereto\Actions\File\FileVerifyNotDuplicateAction; -use Chevereto\Actions\Image\ImageFetchMetaAction; -use Chevereto\Actions\Image\ImageFixOrientationAction; -use Chevereto\Actions\Image\ImageInsertAction; -use Chevereto\Actions\Image\ImageStripMetaAction; -use Chevereto\Actions\Image\ImageVerifyMediaAction; -use Chevereto\Actions\Storage\StorageGetForUserAction; -use Chevereto\Workflow\BaseWorkflow; - -final class ImagePostWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - fetchSource: job( - FileFetchSourceAction::class, - source: '${source}', - ), - validateFile: job( - FileValidateAction::class, - mimes: '${mimes}', - filepath: '${fetchSource:filepath}', - maxBytes: '${max_bytes}', - minBytes: '${min_bytes}', - ), - validateMedia: job( - ImageVerifyMediaAction::class, - filepath: '${fetchSource:filepath}', - maxHeight: '${max_height}', - maxWidth: '${max_width}', - minHeight: '${min_height}', - minWidth: '${min_width}', - ), - assertNotDuplicate: job( - FileVerifyNotDuplicateAction::class, - md5: '${validateFile:md5}', - perceptual: '${validateMedia:perceptual}', - ip: '${ip}', - ipVersion: '${ip_version}', - ), - fixOrientation: job( - ImageFixOrientationAction::class, - image: '${validateMedia:image}' - ), - fetchMeta: job( - ImageFetchMetaAction::class, - image: '${validateMedia:image}' - ), - stripMeta: job( - ImageStripMetaAction::class, - image: '${validateMedia:image}' - ), - storageForUser: job( - StorageGetForUserAction::class, - userId: '${user_id}', - bytesRequired: '${validateFile:bytes}', - ), - reserveId: job( - DatabaseReserveRowAction::class, - table: '${table}', - ), - targetFilename: job( - FileNamingAction::class, - id: '${reserveId:id}', - name: '${name}', - naming: '${naming}', - storage: '${storageForUser:storage}', - path: '${path}' - ), - upload: job( - FileUploadAction::class, - filepath: '${upload_filepath}', - targetFilename: '${targetFilename:name}', - storage: '${storageForUser:storage}', - path: '${path}', - ), - insert: job( - ImageInsertAction::class, - id: '${reserveId:id}', - albumId: '${album_id}', - expires: '${expires}', - userId: '${user_id}', - ), - ); - } -} diff --git a/app/src/Workflows/Image/Like/ImageLikeDeleteWorkflow.php b/app/src/Workflows/Image/Like/ImageLikeDeleteWorkflow.php deleted file mode 100644 index 1263970..0000000 --- a/app/src/Workflows/Image/Like/ImageLikeDeleteWorkflow.php +++ /dev/null @@ -1,31 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\Image\Like; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Workflow\BaseWorkflow; - -final class ImageLikeDeleteWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - delete: job( - 'DeleteImageLike', - id: '${id}', - userId: '${user_id}', - ) - ); - } -} diff --git a/app/src/Workflows/Image/Like/ImageLikePostWorkflow.php b/app/src/Workflows/Image/Like/ImageLikePostWorkflow.php deleted file mode 100644 index 3fd359f..0000000 --- a/app/src/Workflows/Image/Like/ImageLikePostWorkflow.php +++ /dev/null @@ -1,31 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\Image\Like; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Workflow\BaseWorkflow; - -final class ImageLikePostWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - insert: job( - 'InsertImageLike', - id: '${id}', - userId: '${user_id}' - ) - ); - } -} diff --git a/app/src/Workflows/Legacy/LegacyRouteWorkflow.php b/app/src/Workflows/Legacy/LegacyRouteWorkflow.php deleted file mode 100644 index db91f2d..0000000 --- a/app/src/Workflows/Legacy/LegacyRouteWorkflow.php +++ /dev/null @@ -1,31 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\Legacy; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Workflow\BaseWorkflow; - -final class LegacyRouteWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - handler: job( - 'LegacyHandlerAction', - request: '${REQUEST}', - routeBasename: '${LEGACY_ROUTE_DISPATCH}' - ), - ); - } -} diff --git a/app/src/Workflows/Legacy/LegacyUploadPostWorkflow.php b/app/src/Workflows/Legacy/LegacyUploadPostWorkflow.php deleted file mode 100644 index 5c0cf4f..0000000 --- a/app/src/Workflows/Legacy/LegacyUploadPostWorkflow.php +++ /dev/null @@ -1,115 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\Legacy; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Actions\Database\DatabaseReserveRowAction; -use Chevereto\Actions\File\FileFetchSourceAction; -use Chevereto\Actions\File\FileNamingAction; -use Chevereto\Actions\File\FileUploadAction; -use Chevereto\Actions\File\FileValidateAction; -use Chevereto\Actions\File\FileVerifyNotDuplicateAction; -use Chevereto\Actions\Image\ImageFetchMetaAction; -use Chevereto\Actions\Image\ImageFixOrientationAction; -use Chevereto\Actions\Image\ImageStripMetaAction; -use Chevereto\Actions\Image\ImageVerifyMediaAction; -use Chevereto\Actions\Legacy\Api\V1\ImageInsertAction; -use Chevereto\Actions\Legacy\Api\V1\LegacyApiV1OutputAction; -use Chevereto\Actions\Legacy\Api\V1\LegacyApiV1VerifyKeyAction; -use Chevereto\Actions\Storage\StorageGetForUserAction; -use Chevereto\Workflow\BaseWorkflow; - -final class LegacyUploadPostWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - validateApiV1Key: job( - LegacyApiV1VerifyKeyAction::class, - key: '${key}', - apiV1Key: '${api_v1_key}', - ), - fetchSource: job( - FileFetchSourceAction::class, - source: '${source}', - ), - validateFile: job( - FileValidateAction::class, - mimes: '${api_v1_upload_mimes}', - filepath: '${fetchSource:filepath}', - maxBytes: '${api_v1_upload_max_bytes}', - minBytes: '${api_v1_upload_min_bytes}', - ), - validateMedia: job( - ImageVerifyMediaAction::class, - filepath: '${fetchSource:filepath}', - maxHeight: '${api_v1_upload_max_height}', - maxWidth: '${api_v1_upload_max_width}', - minHeight: '${api_v1_upload_min_height}', - minWidth: '${api_v1_upload_min_width}', - ), - assertNotDuplicate: job( - FileVerifyNotDuplicateAction::class, - md5: '${validateFile:md5}', - perceptual: '${validateMedia:perceptual}', - ip: '${requester_ip}', - ipVersion: '${requester_ip_version}', - ), - fixOrientation: job( - ImageFixOrientationAction::class, - image: '${validateMedia:image}' - ), - fetchMeta: job( - ImageFetchMetaAction::class, - image: '${validateMedia:image}' - ), - stripMeta: job( - ImageStripMetaAction::class, - image: '${validateMedia:image}' - ), - storageForUser: job( - StorageGetForUserAction::class, - userId: '${user_id}', - bytesRequired: '${validateFile:bytes}', - ), - reserveId: job( - DatabaseReserveRowAction::class, - table: '${table_image}', - ), - targetFilename: job( - FileNamingAction::class, - id: '${reserveId:id}', - name: '${name}', - naming: '${naming}', - storage: '${storageForUser:storage}', - path: '${api_v1_upload_path}' - ), - upload: job( - FileUploadAction::class, - filepath: '${fetchSource:filepath}', - targetFilename: '${targetFilename:filename}', - storage: '${storageForUser:storage}', - path: '${api_v1_upload_path}', - ), - insert: job( - ImageInsertAction::class, - id: '${reserveId:id}', - ), - output: job( - LegacyApiV1OutputAction::class, - format: '${format}', - ) - ); - } -} diff --git a/app/src/Workflows/Stat/Rebuild/StatRebuildPostWorkflow.php b/app/src/Workflows/Stat/Rebuild/StatRebuildPostWorkflow.php deleted file mode 100644 index 3d113d9..0000000 --- a/app/src/Workflows/Stat/Rebuild/StatRebuildPostWorkflow.php +++ /dev/null @@ -1,29 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\Stat\Rebuild; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Workflow\BaseWorkflow; - -final class StatRebuildPostWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - action: job( - 'StatRebuildAction', - ) - ); - } -} diff --git a/app/src/Workflows/Storage/Migrate/StorageMigratePostWorkflow.php b/app/src/Workflows/Storage/Migrate/StorageMigratePostWorkflow.php deleted file mode 100644 index 19c8460..0000000 --- a/app/src/Workflows/Storage/Migrate/StorageMigratePostWorkflow.php +++ /dev/null @@ -1,32 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\Storage\Migrate; - -use Chevere\Action\Action; -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Workflow\BaseWorkflow; - -final class StorageMigratePostWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - migrate: job( - Action::class, - storageIdFrom: '${id}', - storageIdTo: '${target_storage_id}', - ) - ); - } -} diff --git a/app/src/Workflows/Storage/Stat/Regen/StorageStatsRegenPostWorkflow.php b/app/src/Workflows/Storage/Stat/Regen/StorageStatsRegenPostWorkflow.php deleted file mode 100644 index 65c4e4a..0000000 --- a/app/src/Workflows/Storage/Stat/Regen/StorageStatsRegenPostWorkflow.php +++ /dev/null @@ -1,30 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\Storage\Stat\Regen; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Workflow\BaseWorkflow; - -final class StorageStatsRegenPostWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - regen: job( - 'StorageRegenStatsAction', - storage_id: '${id}', - ) - ); - } -} diff --git a/app/src/Workflows/Storage/StoragePostWorkflow.php b/app/src/Workflows/Storage/StoragePostWorkflow.php deleted file mode 100644 index 954cf3b..0000000 --- a/app/src/Workflows/Storage/StoragePostWorkflow.php +++ /dev/null @@ -1,46 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\Storage; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use Chevereto\Workflow\BaseWorkflow; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; - -final class StoragePostWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - checkAdmin: job( - 'CheckAdminAction', - parameter: '${variable}', - ), - insert: job( - 'StorageInsertAction', - account_id: '${account_id}', - account_name: '${account_name}', - apiId: '${api_id}', - bucket: '${bucket}', - capacity: '${capacity}', - id: '${id}', - key: '${key}', - name: '${name}', - region: '${region}', - secret: '${secret}', - server: '${server}', - service: '${service}', - url: '${url}', - ), - ); - } -} diff --git a/app/src/Workflows/Tool/Id/Decode/ToolDecodeIdGetWorkflow.php b/app/src/Workflows/Tool/Id/Decode/ToolDecodeIdGetWorkflow.php deleted file mode 100644 index 1335e35..0000000 --- a/app/src/Workflows/Tool/Id/Decode/ToolDecodeIdGetWorkflow.php +++ /dev/null @@ -1,30 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\Tool\Id\Decode; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Workflow\BaseWorkflow; - -final class ToolDecodeIdGetWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - output: job( - 'ToolEncodeIdAction', - id: '${id}', - ) - ); - } -} diff --git a/app/src/Workflows/Tool/Id/Encode/ToolEncodeIdGetWorkflow.php b/app/src/Workflows/Tool/Id/Encode/ToolEncodeIdGetWorkflow.php deleted file mode 100644 index 426b059..0000000 --- a/app/src/Workflows/Tool/Id/Encode/ToolEncodeIdGetWorkflow.php +++ /dev/null @@ -1,30 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\Tool\Id\Encode; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Workflow\BaseWorkflow; - -final class ToolEncodeIdGetWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - output: job( - 'ToolEncodeIdAction', - id: '${id}', - ) - ); - } -} diff --git a/app/src/Workflows/Tool/Probe/Email/ToolProbeEmailPostWorkflow.php b/app/src/Workflows/Tool/Probe/Email/ToolProbeEmailPostWorkflow.php deleted file mode 100644 index ebf430f..0000000 --- a/app/src/Workflows/Tool/Probe/Email/ToolProbeEmailPostWorkflow.php +++ /dev/null @@ -1,30 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\Tool\Probe\Email; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Workflow\BaseWorkflow; - -final class ToolProbeEmailPostWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - probe: job( - 'ToolProbeEmailAction', - email: '${email}', - ) - ); - } -} diff --git a/app/src/Workflows/User/Asset/Avatar/UserAssetAvatarDeleteWorkflow.php b/app/src/Workflows/User/Asset/Avatar/UserAssetAvatarDeleteWorkflow.php deleted file mode 100644 index 3d0ce1a..0000000 --- a/app/src/Workflows/User/Asset/Avatar/UserAssetAvatarDeleteWorkflow.php +++ /dev/null @@ -1,40 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\User\Asset\Avatar; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Workflow\BaseWorkflow; -use function Chevereto\Workflow\stepVerifyResourceAccess; - -final class UserAssetAvatarDeleteWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - user: job( - 'UserGetByUsernameAction', - username: '${username}', - ), - checkout: stepVerifyResourceAccess( - resource: 'user_avatar', - level: 'write', - ownerUserId: '${user:id}' - ), - delete: job( - 'DeleteUserAssetAvatarAction', - id: '${user:id}', - ), - ); - } -} diff --git a/app/src/Workflows/User/Asset/Avatar/UserAssetAvatarPostWorkflow.php b/app/src/Workflows/User/Asset/Avatar/UserAssetAvatarPostWorkflow.php deleted file mode 100644 index 5c87efd..0000000 --- a/app/src/Workflows/User/Asset/Avatar/UserAssetAvatarPostWorkflow.php +++ /dev/null @@ -1,82 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\User\Asset\Avatar; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Actions\File\FileFetchSourceAction; -use Chevereto\Actions\File\FileUploadAction; -use Chevereto\Actions\File\FileValidateAction; -use Chevereto\Actions\Image\ImageFixOrientationAction; -use Chevereto\Actions\Image\ImageStripMetaAction; -use Chevereto\Actions\Image\ImageVerifyMediaAction; -use Chevereto\Actions\Storage\StorageGetForAssetAction; -use Chevereto\Workflow\BaseWorkflow; -use function Chevereto\Workflow\stepVerifyResourceAccess; - -final class UserAssetAvatarPostWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - user: job( - 'UserGetByUsernameAction', - username: '${username}', - ), - checkout: stepVerifyResourceAccess( - resource: 'user_avatar', - level: 'write', - ownerUserId: '${user:id}' - ), - fetchSource: job( - FileFetchSourceAction::class, - source: '${source}', - ), - validateFile: job( - FileValidateAction::class, - mimes: '${user_avatar_mimes}', - filepath: '${fetch_source:filepath}', - maxBytes: '${user_avatar_max_bytes}', - minBytes: '${user_avatar_min_bytes}', - ), - validateMedia: job( - ImageVerifyMediaAction::class, - filepath: '${fetch_source:filepath}', - maxHeight: '${user_avatar_max_height}', - maxWidth: '${user_avatar_max_width}', - minHeight: '${user_avatar_min_height}', - minWidth: '${user_avatar_min_width}', - ), - fixOrientation: job( - ImageFixOrientationAction::class, - image: '${validateMedia:image}' - ), - stripMeta: job( - ImageStripMetaAction::class, - image: '${validateMedia:image}' - ), - storageForAsset: job( - StorageGetForAssetAction::class, - userId: '${user_id}', - bytesRequired: '${validate_file:bytes}', - ), - upload: job( - FileUploadAction::class, - filepath: '${upload_filepath}', - targetFilename: '${asset:filename}', - storage: '${storage_for_asset:storage}', - path: '${asset:path}', - ) - ); - } -} diff --git a/app/src/Workflows/User/Asset/Background/UserAssetBackgroundDeleteWorkflow.php b/app/src/Workflows/User/Asset/Background/UserAssetBackgroundDeleteWorkflow.php deleted file mode 100644 index f633a6c..0000000 --- a/app/src/Workflows/User/Asset/Background/UserAssetBackgroundDeleteWorkflow.php +++ /dev/null @@ -1,40 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\User\Asset\Background; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Workflow\BaseWorkflow; -use function Chevereto\Workflow\stepVerifyResourceAccess; - -final class UserAssetBackgroundDeleteWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - user: job( - 'UserGetByUsernameAction', - username: '${username}', - ), - checkout: stepVerifyResourceAccess( - resource: 'user_background', - level: 'write', - ownerUserId: '${user:id}' - ), - delete: job( - 'DeleteUserAssetBackgroundAction', - id: '${user:id}', - ), - ); - } -} diff --git a/app/src/Workflows/User/Asset/Background/UserAssetBackgroundPostWorkflow.php b/app/src/Workflows/User/Asset/Background/UserAssetBackgroundPostWorkflow.php deleted file mode 100644 index 4ba61af..0000000 --- a/app/src/Workflows/User/Asset/Background/UserAssetBackgroundPostWorkflow.php +++ /dev/null @@ -1,81 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\User\Asset\Background; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Actions\File\FileUploadAction; -use Chevereto\Actions\File\FileValidateAction; -use Chevereto\Actions\Image\ImageFixOrientationAction; -use Chevereto\Actions\Image\ImageStripMetaAction; -use Chevereto\Actions\Image\ImageVerifyMediaAction; -use Chevereto\Actions\Storage\StorageGetForAssetAction; -use Chevereto\Workflow\BaseWorkflow; -use function Chevereto\Workflow\stepVerifyResourceAccess; - -final class UserAssetBackgroundPostWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - user: job( - 'UserGetByUsernameAction', - username: '${username}', - ), - checkout: stepVerifyResourceAccess( - resource: 'user_background', - level: 'write', - ownerUserId: '${user:id}' - ), - fetchSource: job( - FileFetchBinaryAction::class, - source: '${source}', - ), - validateFile: job( - FileValidateAction::class, - filepath: '${fetchSource:uploadFilepath}', - maxBytes: '${user_background_max_bytes}', - mimes: '${user_background_mimes}', - minBytes: '${user_background_min_bytes}', - ), - validateMedia: job( - ImageVerifyMediaAction::class, - filepath: '${fetchSource:uploadFilepath}', - maxHeight: '${user_background_max_height}', - maxWidth: '${user_background_max_width}', - minHeight: '${user_background_min_height}', - minWidth: '${user_background_min_width}', - ), - fixOrientation: job( - ImageFixOrientationAction::class, - image: '${validateMedia:image}' - ), - stripMeta: job( - ImageStripMetaAction::class, - image: '${validateMedia:image}' - ), - storageForAsset: job( - StorageGetForAssetAction::class, - bytesRequired: '${validateFile:bytes}', - userId: '${user_id}', - ), - upload: job( - FileUploadAction::class, - filepath: '${upload_filepath}', - path: '${asset:path}', - storage: '${storageForAsset:storage}', - targetFilename: '${asset:filename}', - ) - ); - } -} diff --git a/app/src/Workflows/User/Export/UserExportGetWorkflow.php b/app/src/Workflows/User/Export/UserExportGetWorkflow.php deleted file mode 100644 index 4156e4f..0000000 --- a/app/src/Workflows/User/Export/UserExportGetWorkflow.php +++ /dev/null @@ -1,40 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\User\Export; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Workflow\BaseWorkflow; -use function Chevereto\Workflow\stepVerifyResourceAccess; - -final class UserExportGetWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - user: job( - 'UserGetByUsernameAction', - username: '${username}', - ), - checkout: stepVerifyResourceAccess( - resource: 'user_export', - level: 'read', - ownerUserId: '${user:id}' - ), - export: job( - 'UserExportAction', - userId: '${id}', - ) - ); - } -} diff --git a/app/src/Workflows/User/Follow/UserFollowDeleteWorkflow.php b/app/src/Workflows/User/Follow/UserFollowDeleteWorkflow.php deleted file mode 100644 index c30e359..0000000 --- a/app/src/Workflows/User/Follow/UserFollowDeleteWorkflow.php +++ /dev/null @@ -1,41 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\User\Follow; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Workflow\BaseWorkflow; -use function Chevereto\Workflow\stepVerifyResourceAccess; - -final class UserFollowDeleteWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - user: job( - 'UserGetByUsernameAction', - username: '${username}', - ), - checkout: stepVerifyResourceAccess( - resource: 'user_follow', - level: 'write', - ownerUserId: '${user:id}' - ), - step: job( - 'UserFollowDeleteAction', - userId: '${user_id}', - userIdToFollow: '${user_id_to_follow}', - ) - ); - } -} diff --git a/app/src/Workflows/User/Follow/UserFollowPostWorkflow.php b/app/src/Workflows/User/Follow/UserFollowPostWorkflow.php deleted file mode 100644 index b278f1c..0000000 --- a/app/src/Workflows/User/Follow/UserFollowPostWorkflow.php +++ /dev/null @@ -1,41 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\User\Follow; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Workflow\BaseWorkflow; -use function Chevereto\Workflow\stepVerifyResourceAccess; - -final class UserFollowPostWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - user: job( - 'UserGetByUsernameAction', - username: '${username}', - ), - checkout: stepVerifyResourceAccess( - resource: 'user_follow', - level: 'write', - ownerUserId: '${user:id}' - ), - step: job( - 'UserFollowInsertAction', - userId: '${user_id}', - userIdToFollow: '${user_id_to_follow}', - ) - ); - } -} diff --git a/app/src/Workflows/User/UserPostWorkflow.php b/app/src/Workflows/User/UserPostWorkflow.php deleted file mode 100644 index 7d2f651..0000000 --- a/app/src/Workflows/User/UserPostWorkflow.php +++ /dev/null @@ -1,62 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Chevereto\Workflows\User; - -use Chevere\Workflow\Interfaces\WorkflowInterface; -use function Chevere\Workflow\job; -use function Chevere\Workflow\workflow; -use Chevereto\Workflow\BaseWorkflow; -use function Chevereto\Workflow\stepVerifyRepositoryAccess; - -final class UserPostWorkflow extends BaseWorkflow -{ - public function getWorkflow(): WorkflowInterface - { - return workflow( - checkout: stepVerifyRepositoryAccess( - repository: 'users', - level: 'write', - ), - validateUsername: job( - 'ValidateUsernameAction', - username: '${username}', - ), - validateEmail: job( - 'ValidateEmailAction', - email: '${email}', - ), - validatePassword: job( - 'ValidatePasswordAction', - password: '${password}', - ), - validateRole: job( - 'ValidateRoleAction', - role: '${role}', - ), - validateAvailableUsername: job( - 'ValidateAvailableUsernameAction', - username: '${username}', - ), - validateAvailableEmail: job( - 'ValidateAvailableEmailAction', - email: '${email}' - ), - insert: job( - 'InsertUserAction', - username: '${username}', - email: '${email}', - password: '${password}', - role: '${role}', - ) - ); - } -} diff --git a/app/upgrading.php b/app/upgrading.php index 3739f22..c094257 100644 --- a/app/upgrading.php +++ b/app/upgrading.php @@ -45,7 +45,7 @@ if (! file_exists(LOGGER)) { directoryForPath($loggerDir)->createIfNotExists(); touch(LOGGER); } -ob_start('ob_gzhandler'); +ob_start(); ob_implicit_flush(true); $rootDir = __DIR__ . '/..'; $workingDir = __DIR__ . '/.upgrading'; @@ -213,7 +213,7 @@ if ($singleStep || $action === 'extract') { $safeResult = false; if (passthruEnabled()) { logger('Update command passthru'); - $command = $rootDir . '/app/bin/legacy -C update'; + $command = $rootDir . '/app/bin/cli -C update'; $safeResult = passthru($command); } if ($safeResult === false) { diff --git a/content/images/system/default/home_cover.avif b/content/images/system/default/home_cover.avif deleted file mode 100644 index 52c1d74..0000000 Binary files a/content/images/system/default/home_cover.avif and /dev/null differ diff --git a/content/images/system/default/home_cover.jpg b/content/images/system/default/home_cover.jpg index f6543a1..21dcc8b 100644 Binary files a/content/images/system/default/home_cover.jpg and b/content/images/system/default/home_cover.jpg differ diff --git a/content/legacy/themes/Peafowl/footer.php b/content/legacy/themes/Peafowl/footer.php index 309388c..51b1302 100644 --- a/content/legacy/themes/Peafowl/footer.php +++ b/content/legacy/themes/Peafowl/footer.php @@ -38,7 +38,7 @@ echo $tag('%name%', ''); diff --git a/content/legacy/themes/Peafowl/head.php b/content/legacy/themes/Peafowl/head.php index 898f7b4..9727430 100644 --- a/content/legacy/themes/Peafowl/head.php +++ b/content/legacy/themes/Peafowl/head.php @@ -125,13 +125,14 @@ foreach ($links as $rel => $href) { if (in_array(Handler::var('album')['privacy'], ['public', 'private_but_link']) && Handler::var('listing')->outputCount() ) { - $open_graph_extend = array_merge($open_graph_extend, [ - 'image' => Handler::var('listing')->outputAssoc()[0]['display_url'], - 'image:width' => Handler::var('listing')->outputAssoc()[0]['display_width'], - 'image:height' => Handler::var('listing')->outputAssoc()[0]['display_height'], - ]); + if(Handler::var('listing') && Handler::var('listing')->first()) { + $open_graph_extend = array_merge($open_graph_extend, [ + 'image' => Handler::var('listing')->first()['display_url'], + 'image:width' => Handler::var('listing')->first()['display_width'], + 'image:height' => Handler::var('listing')->first()['display_height'], + ]); + } } - break; case Handler::var('user') !== null && is_route('user'): $open_graph_extend = [ @@ -183,12 +184,9 @@ foreach ($links as $rel => $href) { } else { $twitter_card['creator'] = isset(Handler::var('user')['twitter']) ? Handler::var('user')['twitter']['username'] : ''; } - $list_output = Handler::var('listing') !== null ? (Handler::var('listing')->outputAssoc() ?? null) : null; - if (is_array($list_output) && count($list_output) > 0) { - for ($i = 0; $i < 4; ++$i) { - $twitter_card['image' . $i] = $list_output[$i]['display_url'] ?? ''; - } - } + $twitter_card['image'] = Handler::var('listing') && Handler::var('listing')->first() + ? Handler::var('listing')->first()['display_url'] + : ''; break; } diff --git a/content/legacy/themes/Peafowl/header.php b/content/legacy/themes/Peafowl/header.php index 543d5be..f703dbd 100644 --- a/content/legacy/themes/Peafowl/header.php +++ b/content/legacy/themes/Peafowl/header.php @@ -215,7 +215,7 @@ foreach($tags_top as $k => $v) {
  • - +
  • @@ -304,6 +304,7 @@ foreach($tags_top as $k => $v) { } ?>
  • +
    @@ -321,6 +322,7 @@ foreach($tags_top as $k => $v) { } ?>
    +
    diff --git a/content/legacy/themes/Peafowl/lib/chevereto-all.js b/content/legacy/themes/Peafowl/lib/chevereto-all.js index 705b61a..3fb20dd 100644 --- a/content/legacy/themes/Peafowl/lib/chevereto-all.js +++ b/content/legacy/themes/Peafowl/lib/chevereto-all.js @@ -140,26 +140,26 @@ for(var i=0; ia?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(n.isPlainObject(c)||(b=n.isArray(c)))?(b?(b=!1,f=a&&n.isArray(a)?a:[]):f=a&&n.isPlainObject(a)?a:{},g[d]=n.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray||function(a){return"array"===n.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;try{if(a.constructor&&!k.call(a,"constructor")&&!k.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(!l.ownFirst)for(b in a)return k.call(a,b);for(b in a);return void 0===b||k.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(b){b&&n.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(h)return h.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(f=a[b],b=a,a=f),n.isFunction(a)?(c=e.call(arguments,2),d=function(){return a.apply(b||this,c.concat(e.call(arguments)))},d.guid=a.guid=a.guid||n.guid++,d):void 0},now:function(){return+new Date},support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){i["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=!!a&&"length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ga(),z=ga(),A=ga(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+M+"))|)"+L+"*\\]",O=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+N+")*)|.*)\\)|)",P=new RegExp(L+"+","g"),Q=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),R=new RegExp("^"+L+"*,"+L+"*"),S=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),T=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),U=new RegExp(O),V=new RegExp("^"+M+"$"),W={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M+"|[*])"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},X=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Z=/^[^{]+\{\s*\[native \w/,$=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,_=/[+~]/,aa=/'|\\/g,ba=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),ca=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},da=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(ea){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fa(a,b,d,e){var f,h,j,k,l,o,r,s,w=b&&b.ownerDocument,x=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==x&&9!==x&&11!==x)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==x&&(o=$.exec(a)))if(f=o[1]){if(9===x){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(w&&(j=w.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(o[2])return H.apply(d,b.getElementsByTagName(a)),d;if((f=o[3])&&c.getElementsByClassName&&b.getElementsByClassName)return H.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==x)w=b,s=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(aa,"\\$&"):b.setAttribute("id",k=u),r=g(a),h=r.length,l=V.test(k)?"#"+k:"[id='"+k+"']";while(h--)r[h]=l+" "+qa(r[h]);s=r.join(","),w=_.test(a)&&oa(b.parentNode)||b}if(s)try{return H.apply(d,w.querySelectorAll(s)),d}catch(y){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(Q,"$1"),b,d,e)}function ga(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ha(a){return a[u]=!0,a}function ia(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ja(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function ka(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function la(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function na(a){return ha(function(b){return b=+b,ha(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function oa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=fa.support={},f=fa.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fa.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ia(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ia(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Z.test(n.getElementsByClassName),c.getById=ia(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return"undefined"!=typeof b.getElementsByClassName&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=Z.test(n.querySelectorAll))&&(ia(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ia(function(a){var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Z.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ia(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",O)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Z.test(o.compareDocumentPosition),t=b||Z.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return ka(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?ka(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},fa.matches=function(a,b){return fa(a,null,null,b)},fa.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(T,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fa(b,n,null,[a]).length>0},fa.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fa.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fa.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fa.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fa.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fa.selectors={cacheLength:50,createPseudo:ha,match:W,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ba,ca),a[3]=(a[3]||a[4]||a[5]||"").replace(ba,ca),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fa.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fa.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return W.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&U.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ba,ca).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fa.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(P," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fa.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ha(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ha(function(a){var b=[],c=[],d=h(a.replace(Q,"$1"));return d[u]?ha(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ha(function(a){return function(b){return fa(a,b).length>0}}),contains:ha(function(a){return a=a.replace(ba,ca),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ha(function(a){return V.test(a||"")||fa.error("unsupported lang: "+a),a=a.replace(ba,ca).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Y.test(a.nodeName)},input:function(a){return X.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:na(function(){return[0]}),last:na(function(a,b){return[b-1]}),eq:na(function(a,b,c){return[0>c?c+b:c]}),even:na(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:na(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:na(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:na(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function ra(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j,k=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(j=b[u]||(b[u]={}),i=j[b.uniqueID]||(j[b.uniqueID]={}),(h=i[d])&&h[0]===w&&h[1]===f)return k[2]=h[2];if(i[d]=k,k[2]=a(b,c,g))return!0}}}function sa(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ta(a,b,c){for(var d=0,e=b.length;e>d;d++)fa(a,b[d],c);return c}function ua(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function va(a,b,c,d,e,f){return d&&!d[u]&&(d=va(d)),e&&!e[u]&&(e=va(e,f)),ha(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ta(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ua(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ua(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ua(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function wa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ra(function(a){return a===b},h,!0),l=ra(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[ra(sa(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return va(i>1&&sa(m),i>1&&qa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(Q,"$1"),c,e>i&&wa(a.slice(i,e)),f>e&&wa(a=a.slice(e)),f>e&&qa(a))}m.push(c)}return sa(m)}function xa(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=F.call(i));u=ua(u)}H.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&fa.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ha(f):f}return h=fa.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xa(e,d)),f.selector=a}return f},i=fa.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ba,ca),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=W.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ba,ca),_.test(j[0].type)&&oa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qa(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||_.test(a)&&oa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ia(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ia(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ja("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ia(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ja("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ia(function(a){return null==a.getAttribute("disabled")})||ja(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fa}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.uniqueSort=n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},v=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},w=n.expr.match.needsContext,x=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,y=/^.[^:#\[\.,]*$/;function z(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(y.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return n.inArray(a,b)>-1!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;e>b;b++)if(n.contains(d[b],this))return!0}));for(b=0;e>b;b++)n.find(a,d[b],c);return c=this.pushStack(e>1?n.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(z(this,a||[],!1))},not:function(a){return this.pushStack(z(this,a||[],!0))},is:function(a){return!!z(this,"string"==typeof a&&w.test(a)?n(a):a||[],!1).length}});var A,B=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=n.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||A,"string"==typeof a){if(e="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:B.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),x.test(e[1])&&n.isPlainObject(b))for(e in b)n.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}if(f=d.getElementById(e[2]),f&&f.parentNode){if(f.id!==e[2])return A.find(a);this.length=1,this[0]=f}return this.context=d,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof c.ready?c.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};C.prototype=n.fn,A=n(d);var D=/^(?:parents|prev(?:Until|All))/,E={children:!0,contents:!0,next:!0,prev:!0};n.fn.extend({has:function(a){var b,c=n(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(n.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=w.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?n.inArray(this[0],n(a)):n.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.uniqueSort(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function F(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return u(a,"parentNode")},parentsUntil:function(a,b,c){return u(a,"parentNode",c)},next:function(a){return F(a,"nextSibling")},prev:function(a){return F(a,"previousSibling")},nextAll:function(a){return u(a,"nextSibling")},prevAll:function(a){return u(a,"previousSibling")},nextUntil:function(a,b,c){return u(a,"nextSibling",c)},prevUntil:function(a,b,c){return u(a,"previousSibling",c)},siblings:function(a){return v((a.parentNode||{}).firstChild,a)},children:function(a){return v(a.firstChild)},contents:function(a){return n.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(E[a]||(e=n.uniqueSort(e)),D.test(a)&&(e=e.reverse())),this.pushStack(e)}});var G=/\S+/g;function H(a){var b={};return n.each(a.match(G)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?H(a):n.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),h>=c&&h--}),this},has:function(a){return a?n.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=!0,c||j.disable(),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().progress(c.notify).done(c.resolve).fail(c.reject):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=e.call(arguments),d=c.length,f=1!==d||a&&n.isFunction(a.promise)?d:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?e.call(arguments):d,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(d>1)for(i=new Array(d),j=new Array(d),k=new Array(d);d>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().progress(h(b,j,i)).done(h(b,k,c)).fail(g.reject):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(d,[n]),n.fn.triggerHandler&&(n(d).triggerHandler("ready"),n(d).off("ready"))))}});function J(){d.addEventListener?(d.removeEventListener("DOMContentLoaded",K),a.removeEventListener("load",K)):(d.detachEvent("onreadystatechange",K),a.detachEvent("onload",K))}function K(){(d.addEventListener||"load"===a.event.type||"complete"===d.readyState)&&(J(),n.ready())}n.ready.promise=function(b){if(!I)if(I=n.Deferred(),"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll)a.setTimeout(n.ready);else if(d.addEventListener)d.addEventListener("DOMContentLoaded",K),a.addEventListener("load",K);else{d.attachEvent("onreadystatechange",K),a.attachEvent("onload",K);var c=!1;try{c=null==a.frameElement&&d.documentElement}catch(e){}c&&c.doScroll&&!function f(){if(!n.isReady){try{c.doScroll("left")}catch(b){return a.setTimeout(f,50)}J(),n.ready()}}()}return I.promise(b)},n.ready.promise();var L;for(L in n(l))break;l.ownFirst="0"===L,l.inlineBlockNeedsLayout=!1,n(function(){var a,b,c,e;c=d.getElementsByTagName("body")[0],c&&c.style&&(b=d.createElement("div"),e=d.createElement("div"),e.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(e).appendChild(b),"undefined"!=typeof b.style.zoom&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",l.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(e))}),function(){var a=d.createElement("div");l.deleteExpando=!0;try{delete a.test}catch(b){l.deleteExpando=!1}a=null}();var M=function(a){var b=n.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b},N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(O,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}n.data(a,b,c)}else c=void 0; }return c}function Q(a){var b;for(b in a)if(("data"!==b||!n.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function R(a,b,d,e){if(M(a)){var f,g,h=n.expando,i=a.nodeType,j=i?n.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||n.guid++:h),j[k]||(j[k]=i?{}:{toJSON:n.noop}),"object"!=typeof b&&"function"!=typeof b||(e?j[k]=n.extend(j[k],b):j[k].data=n.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[n.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[n.camelCase(b)])):f=g,f}}function S(a,b,c){if(M(a)){var d,e,f=a.nodeType,g=f?n.cache:a,h=f?a[n.expando]:n.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){n.isArray(b)?b=b.concat(n.map(b,n.camelCase)):b in d?b=[b]:(b=n.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!Q(d):!n.isEmptyObject(d))return}(c||(delete g[h].data,Q(g[h])))&&(f?n.cleanData([a],!0):l.deleteExpando||g!=g.window?delete g[h]:g[h]=void 0)}}}n.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?n.cache[a[n.expando]]:a[n.expando],!!a&&!Q(a)},data:function(a,b,c){return R(a,b,c)},removeData:function(a,b){return S(a,b)},_data:function(a,b,c){return R(a,b,c,!0)},_removeData:function(a,b){return S(a,b,!0)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=n.data(f),1===f.nodeType&&!n._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));n._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){n.data(this,a)}):arguments.length>1?this.each(function(){n.data(this,a,b)}):f?P(f,a,n.data(f,a)):void 0},removeData:function(a){return this.each(function(){n.removeData(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=n._data(a,b),c&&(!d||n.isArray(c)?d=n._data(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return n._data(a,c)||n._data(a,c,{empty:n.Callbacks("once memory").add(function(){n._removeData(a,b+"queue"),n._removeData(a,c)})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},Z=/^(?:checkbox|radio)$/i,$=/<([\w:-]+)/,_=/^$|\/(?:java|ecma)script/i,aa=/^\s+/,ba="abbr|article|aside|audio|bdi|canvas|data|datalist|details|dialog|figcaption|figure|footer|header|hgroup|main|mark|meter|nav|output|picture|progress|section|summary|template|time|video";function ca(a){var b=ba.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}!function(){var a=d.createElement("div"),b=d.createDocumentFragment(),c=d.createElement("input");a.innerHTML="
    a",l.leadingWhitespace=3===a.firstChild.nodeType,l.tbody=!a.getElementsByTagName("tbody").length,l.htmlSerialize=!!a.getElementsByTagName("link").length,l.html5Clone="<:nav>"!==d.createElement("nav").cloneNode(!0).outerHTML,c.type="checkbox",c.checked=!0,b.appendChild(c),l.appendChecked=c.checked,a.innerHTML="",l.noCloneChecked=!!a.cloneNode(!0).lastChild.defaultValue,b.appendChild(a),c=d.createElement("input"),c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),a.appendChild(c),l.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,l.noCloneEvent=!!a.addEventListener,a[n.expando]=1,l.attributes=!a.getAttribute(n.expando)}();var da={option:[1,""],legend:[1,"
    ","
    "],area:[1,"",""],param:[1,"",""],thead:[1,"","
    "],tr:[2,"","
    "],col:[2,"","
    "],td:[3,"","
    "],_default:l.htmlSerialize?[0,"",""]:[1,"X
    ","
    "]};da.optgroup=da.option,da.tbody=da.tfoot=da.colgroup=da.caption=da.thead,da.th=da.td;function ea(a,b){var c,d,e=0,f="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||n.nodeName(d,b)?f.push(d):n.merge(f,ea(d,b));return void 0===b||b&&n.nodeName(a,b)?n.merge([a],f):f}function fa(a,b){for(var c,d=0;null!=(c=a[d]);d++)n._data(c,"globalEval",!b||n._data(b[d],"globalEval"))}var ga=/<|&#?\w+;/,ha=/r;r++)if(g=a[r],g||0===g)if("object"===n.type(g))n.merge(q,g.nodeType?[g]:g);else if(ga.test(g)){i=i||p.appendChild(b.createElement("div")),j=($.exec(g)||["",""])[1].toLowerCase(),m=da[j]||da._default,i.innerHTML=m[1]+n.htmlPrefilter(g)+m[2],f=m[0];while(f--)i=i.lastChild;if(!l.leadingWhitespace&&aa.test(g)&&q.push(b.createTextNode(aa.exec(g)[0])),!l.tbody){g="table"!==j||ha.test(g)?""!==m[1]||ha.test(g)?0:i:i.firstChild,f=g&&g.childNodes.length;while(f--)n.nodeName(k=g.childNodes[f],"tbody")&&!k.childNodes.length&&g.removeChild(k)}n.merge(q,i.childNodes),i.textContent="";while(i.firstChild)i.removeChild(i.firstChild);i=p.lastChild}else q.push(b.createTextNode(g));i&&p.removeChild(i),l.appendChecked||n.grep(ea(q,"input"),ia),r=0;while(g=q[r++])if(d&&n.inArray(g,d)>-1)e&&e.push(g);else if(h=n.contains(g.ownerDocument,g),i=ea(p.appendChild(g),"script"),h&&fa(i),c){f=0;while(g=i[f++])_.test(g.type||"")&&c.push(g)}return i=null,p}!function(){var b,c,e=d.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(l[b]=c in a)||(e.setAttribute(c,"t"),l[b]=e.attributes[c].expando===!1);e=null}();var ka=/^(?:input|select|textarea)$/i,la=/^key/,ma=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,na=/^(?:focusinfocus|focusoutblur)$/,oa=/^([^.]*)(?:\.(.+)|)/;function pa(){return!0}function qa(){return!1}function ra(){try{return d.activeElement}catch(a){}}function sa(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)sa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=qa;else if(!e)return a;return 1===f&&(g=e,e=function(a){return n().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=n.guid++)),a.each(function(){n.event.add(this,b,e,d,c)})}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=n.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return"undefined"==typeof n||a&&n.event.triggered===a.type?void 0:n.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(G)||[""],h=b.length;while(h--)f=oa.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=n.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=n.event.special[o]||{},l=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},i),(m=g[o])||(m=g[o]=[],m.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,l):m.push(l),n.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n.hasData(a)&&n._data(a);if(r&&(k=r.events)){b=(b||"").match(G)||[""],j=b.length;while(j--)if(h=oa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=m.length;while(f--)g=m[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(m.splice(f,1),g.selector&&m.delegateCount--,l.remove&&l.remove.call(a,g));i&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(k)&&(delete r.handle,n._removeData(a,"events"))}},trigger:function(b,c,e,f){var g,h,i,j,l,m,o,p=[e||d],q=k.call(b,"type")?b.type:b,r=k.call(b,"namespace")?b.namespace.split("."):[];if(i=m=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!na.test(q+n.event.triggered)&&(q.indexOf(".")>-1&&(r=q.split("."),q=r.shift(),r.sort()),h=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=r.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:n.makeArray(c,[b]),l=n.event.special[q]||{},f||!l.trigger||l.trigger.apply(e,c)!==!1)){if(!f&&!l.noBubble&&!n.isWindow(e)){for(j=l.delegateType||q,na.test(j+q)||(i=i.parentNode);i;i=i.parentNode)p.push(i),m=i;m===(e.ownerDocument||d)&&p.push(m.defaultView||m.parentWindow||a)}o=0;while((i=p[o++])&&!b.isPropagationStopped())b.type=o>1?j:l.bindType||q,g=(n._data(i,"events")||{})[b.type]&&n._data(i,"handle"),g&&g.apply(i,c),g=h&&i[h],g&&g.apply&&M(i)&&(b.result=g.apply(i,c),b.result===!1&&b.preventDefault());if(b.type=q,!f&&!b.isDefaultPrevented()&&(!l._default||l._default.apply(p.pop(),c)===!1)&&M(e)&&h&&e[q]&&!n.isWindow(e)){m=e[h],m&&(e[h]=null),n.event.triggered=q;try{e[q]()}catch(s){}n.event.triggered=void 0,m&&(e[h]=m)}return b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,d,f,g,h=[],i=e.call(arguments),j=(n._data(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())a.rnamespace&&!a.rnamespace.test(g.namespace)||(a.handleObj=g,a.data=g.data,d=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==d&&(a.result=d)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&("click"!==a.type||isNaN(a.button)||a.button<1))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>-1:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]","i"),va=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,wa=/\s*$/g,Aa=ca(d),Ba=Aa.appendChild(d.createElement("div"));function Ca(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function Da(a){return a.type=(null!==n.find.attr(a,"type"))+"/"+a.type,a}function Ea(a){var b=ya.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Fa(a,b){if(1===b.nodeType&&n.hasData(a)){var c,d,e,f=n._data(a),g=n._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)n.event.add(b,c,h[c][d])}g.data&&(g.data=n.extend({},g.data))}}function Ga(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!l.noCloneEvent&&b[n.expando]){e=n._data(b);for(d in e.events)n.removeEvent(b,d,e.handle);b.removeAttribute(n.expando)}"script"===c&&b.text!==a.text?(Da(b).text=a.text,Ea(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),l.html5Clone&&a.innerHTML&&!n.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&Z.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:"input"!==c&&"textarea"!==c||(b.defaultValue=a.defaultValue)}}function Ha(a,b,c,d){b=f.apply([],b);var e,g,h,i,j,k,m=0,o=a.length,p=o-1,q=b[0],r=n.isFunction(q);if(r||o>1&&"string"==typeof q&&!l.checkClone&&xa.test(q))return a.each(function(e){var f=a.eq(e);r&&(b[0]=q.call(this,e,f.html())),Ha(f,b,c,d)});if(o&&(k=ja(b,a[0].ownerDocument,!1,a,d),e=k.firstChild,1===k.childNodes.length&&(k=e),e||d)){for(i=n.map(ea(k,"script"),Da),h=i.length;o>m;m++)g=k,m!==p&&(g=n.clone(g,!0,!0),h&&n.merge(i,ea(g,"script"))),c.call(a[m],g,m);if(h)for(j=i[i.length-1].ownerDocument,n.map(i,Ea),m=0;h>m;m++)g=i[m],_.test(g.type||"")&&!n._data(g,"globalEval")&&n.contains(j,g)&&(g.src?n._evalUrl&&n._evalUrl(g.src):n.globalEval((g.text||g.textContent||g.innerHTML||"").replace(za,"")));k=e=null}return a}function Ia(a,b,c){for(var d,e=b?n.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||n.cleanData(ea(d)),d.parentNode&&(c&&n.contains(d.ownerDocument,d)&&fa(ea(d,"script")),d.parentNode.removeChild(d));return a}n.extend({htmlPrefilter:function(a){return a.replace(va,"<$1>")},clone:function(a,b,c){var d,e,f,g,h,i=n.contains(a.ownerDocument,a);if(l.html5Clone||n.isXMLDoc(a)||!ua.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(Ba.innerHTML=a.outerHTML,Ba.removeChild(f=Ba.firstChild)),!(l.noCloneEvent&&l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(d=ea(f),h=ea(a),g=0;null!=(e=h[g]);++g)d[g]&&Ga(e,d[g]);if(b)if(c)for(h=h||ea(a),d=d||ea(f),g=0;null!=(e=h[g]);g++)Fa(e,d[g]);else Fa(a,f);return d=ea(f,"script"),d.length>0&&fa(d,!i&&ea(a,"script")),d=h=e=null,f},cleanData:function(a,b){for(var d,e,f,g,h=0,i=n.expando,j=n.cache,k=l.attributes,m=n.event.special;null!=(d=a[h]);h++)if((b||M(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)m[e]?n.event.remove(d,e):n.removeEvent(d,e,g.handle);j[f]&&(delete j[f],k||"undefined"==typeof d.removeAttribute?d[i]=void 0:d.removeAttribute(i),c.push(f))}}}),n.fn.extend({domManip:Ha,detach:function(a){return Ia(this,a,!0)},remove:function(a){return Ia(this,a)},text:function(a){return Y(this,function(a){return void 0===a?n.text(this):this.empty().append((this[0]&&this[0].ownerDocument||d).createTextNode(a))},null,a,arguments.length)},append:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.appendChild(a)}})},prepend:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&n.cleanData(ea(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&n.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return Y(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(ta,""):void 0;if("string"==typeof a&&!wa.test(a)&&(l.htmlSerialize||!ua.test(a))&&(l.leadingWhitespace||!aa.test(a))&&!da[($.exec(a)||["",""])[1].toLowerCase()]){a=n.htmlPrefilter(a);try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(ea(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return Ha(this,arguments,function(b){var c=this.parentNode;n.inArray(this,a)<0&&(n.cleanData(ea(this)),c&&c.replaceChild(b,this))},a)}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=0,e=[],f=n(a),h=f.length-1;h>=d;d++)c=d===h?this:this.clone(!0),n(f[d])[b](c),g.apply(e,c.get());return this.pushStack(e)}});var Ja,Ka={HTML:"block",BODY:"block"};function La(a,b){var c=n(b.createElement(a)).appendTo(b.body),d=n.css(c[0],"display");return c.detach(),d}function Ma(a){var b=d,c=Ka[a];return c||(c=La(a,b),"none"!==c&&c||(Ja=(Ja||n("