diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index d3446a7..a0e9706 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up PHP 8.0 + - name: Set up PHP 8.3 uses: shivammathur/setup-php@v2 with: php-version: 8.0 diff --git a/composer.json b/composer.json index b09991c..0f25c9e 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,8 @@ "php": "^7.3|^8.0", "laravel/lumen-framework": "^8.0", "jobcloud/php-kafka-lib": "^1.7", - "flix-tech/avro-serde-php": "^1.7" + "flix-tech/avro-serde-php": "^1.7", + "coraxster/flysystem-aws-s3-v3-minio": "^1.0" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 8f7b75a..41c66b5 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,172 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9eab2e1b9049cd2d04b61b2705ef6179", + "content-hash": "82c5862031b247e0f468c8d6fca45b62", "packages": [ + { + "name": "aws/aws-crt-php", + "version": "v1.2.7", + "source": { + "type": "git", + "url": "https://github.com/awslabs/aws-crt-php.git", + "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/d71d9906c7bb63a28295447ba12e74723bd3730e", + "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35||^5.6.3||^9.5", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "ext-awscrt": "Make sure you install awscrt native extension to use any of the functionality." + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "AWS SDK Common Runtime Team", + "email": "aws-sdk-common-runtime@amazon.com" + } + ], + "description": "AWS Common Runtime for PHP", + "homepage": "https://github.com/awslabs/aws-crt-php", + "keywords": [ + "amazon", + "aws", + "crt", + "sdk" + ], + "support": { + "issues": "https://github.com/awslabs/aws-crt-php/issues", + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.7" + }, + "time": "2024-10-18T22:15:13+00:00" + }, + { + "name": "aws/aws-sdk-php", + "version": "3.337.3", + "source": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-php.git", + "reference": "06dfc8f76423b49aaa181debd25bbdc724c346d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/06dfc8f76423b49aaa181debd25bbdc724c346d6", + "reference": "06dfc8f76423b49aaa181debd25bbdc724c346d6", + "shasum": "" + }, + "require": { + "aws/aws-crt-php": "^1.2.3", + "ext-json": "*", + "ext-pcre": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5", + "guzzlehttp/promises": "^1.4.0 || ^2.0", + "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", + "mtdowling/jmespath.php": "^2.6", + "php": ">=7.2.5", + "psr/http-message": "^1.0 || ^2.0" + }, + "require-dev": { + "andrewsville/php-token-reflection": "^1.4", + "aws/aws-php-sns-message-validator": "~1.0", + "behat/behat": "~3.0", + "composer/composer": "^1.10.22", + "dms/phpunit-arraysubset-asserts": "^0.4.0", + "doctrine/cache": "~1.4", + "ext-dom": "*", + "ext-openssl": "*", + "ext-pcntl": "*", + "ext-sockets": "*", + "nette/neon": "^2.3", + "paragonie/random_compat": ">= 2", + "phpunit/phpunit": "^5.6.3 || ^8.5 || ^9.5", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", + "sebastian/comparator": "^1.2.3 || ^4.0", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", + "doctrine/cache": "To use the DoctrineCacheAdapter", + "ext-curl": "To send requests using cURL", + "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", + "ext-sockets": "To use client-side monitoring" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Aws\\": "src/" + }, + "exclude-from-classmap": [ + "src/data/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Amazon Web Services", + "homepage": "http://aws.amazon.com" + } + ], + "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", + "homepage": "http://aws.amazon.com/sdkforphp", + "keywords": [ + "amazon", + "aws", + "cloud", + "dynamodb", + "ec2", + "glacier", + "s3", + "sdk" + ], + "support": { + "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", + "issues": "https://github.com/aws/aws-sdk-php/issues", + "source": "https://github.com/aws/aws-sdk-php/tree/3.337.3" + }, + "time": "2025-01-21T19:10:05+00:00" + }, { "name": "beberlei/assert", - "version": "v3.3.2", + "version": "v3.3.3", "source": { "type": "git", "url": "https://github.com/beberlei/assert.git", - "reference": "cb70015c04be1baee6f5f5c953703347c0ac1655" + "reference": "b5fd8eacd8915a1b627b8bfc027803f1939734dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/beberlei/assert/zipball/cb70015c04be1baee6f5f5c953703347c0ac1655", - "reference": "cb70015c04be1baee6f5f5c953703347c0ac1655", + "url": "https://api.github.com/repos/beberlei/assert/zipball/b5fd8eacd8915a1b627b8bfc027803f1939734dd", + "reference": "b5fd8eacd8915a1b627b8bfc027803f1939734dd", "shasum": "" }, "require": { @@ -25,7 +177,7 @@ "ext-json": "*", "ext-mbstring": "*", "ext-simplexml": "*", - "php": "^7.0 || ^8.0" + "php": "^7.1 || ^8.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "*", @@ -69,9 +221,9 @@ ], "support": { "issues": "https://github.com/beberlei/assert/issues", - "source": "https://github.com/beberlei/assert/tree/v3.3.2" + "source": "https://github.com/beberlei/assert/tree/v3.3.3" }, - "time": "2021-12-16T21:41:27+00:00" + "time": "2024-07-15T13:18:35+00:00" }, { "name": "brick/math", @@ -197,35 +349,84 @@ ], "time": "2023-12-11T17:09:12+00:00" }, + { + "name": "coraxster/flysystem-aws-s3-v3-minio", + "version": "1.0.16", + "source": { + "type": "git", + "url": "https://github.com/coraxster/flysystem-aws-s3-v3-minio.git", + "reference": "963b771021be0eda570064a2a5051e1192c3afbd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/coraxster/flysystem-aws-s3-v3-minio/zipball/963b771021be0eda570064a2a5051e1192c3afbd", + "reference": "963b771021be0eda570064a2a5051e1192c3afbd", + "shasum": "" + }, + "require": { + "aws/aws-sdk-php": "^3.0.0", + "league/flysystem": "^1.0.40", + "php": ">=5.5.0" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "~1.0.1", + "phpspec/phpspec": "^2.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\AwsS3v3\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Flysystem adapter for the AWS S3 SDK v3.x, forked for minio support", + "support": { + "source": "https://github.com/coraxster/flysystem-aws-s3-v3-minio/tree/1.0.15-m" + }, + "time": "2017-09-26T08:08:44+00:00" + }, { "name": "doctrine/inflector", - "version": "2.0.10", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc" + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/5817d0659c5b50c9b950feb9af7b9668e2c436bc", - "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^11.0", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-phpunit": "^1.1", - "phpstan/phpstan-strict-rules": "^1.3", - "phpunit/phpunit": "^8.5 || ^9.5", - "vimeo/psalm": "^4.25 || ^5.4" + "doctrine/coding-standard": "^12.0 || ^13.0", + "phpstan/phpstan": "^1.12 || ^2.0", + "phpstan/phpstan-phpunit": "^1.4 || ^2.0", + "phpstan/phpstan-strict-rules": "^1.6 || ^2.0", + "phpunit/phpunit": "^8.5 || ^12.2" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + "Doctrine\\Inflector\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -270,7 +471,7 @@ ], "support": { "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.10" + "source": "https://github.com/doctrine/inflector/tree/2.1.0" }, "funding": [ { @@ -286,7 +487,7 @@ "type": "tidelift" } ], - "time": "2024-02-18T20:23:39+00:00" + "time": "2025-08-10T19:31:58+00:00" }, { "name": "doctrine/lexer", @@ -366,29 +567,28 @@ }, { "name": "dragonmantank/cron-expression", - "version": "v3.4.0", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/dragonmantank/cron-expression.git", - "reference": "8c784d071debd117328803d86b2097615b457500" + "reference": "1b2de7f4a468165dca07b142240733a1973e766d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/8c784d071debd117328803d86b2097615b457500", - "reference": "8c784d071debd117328803d86b2097615b457500", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/1b2de7f4a468165dca07b142240733a1973e766d", + "reference": "1b2de7f4a468165dca07b142240733a1973e766d", "shasum": "" }, "require": { - "php": "^7.2|^8.0", - "webmozart/assert": "^1.0" + "php": "^7.2|^8.0" }, "replace": { "mtdowling/cron-expression": "^1.0" }, "require-dev": { - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^1.0", - "phpunit/phpunit": "^7.0|^8.0|^9.0" + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.32|^2.1.31", + "phpunit/phpunit": "^8.5.48|^9.0" }, "type": "library", "extra": { @@ -419,7 +619,7 @@ ], "support": { "issues": "https://github.com/dragonmantank/cron-expression/issues", - "source": "https://github.com/dragonmantank/cron-expression/tree/v3.4.0" + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.5.0" }, "funding": [ { @@ -427,7 +627,7 @@ "type": "github" } ], - "time": "2024-10-09T13:47:03+00:00" + "time": "2025-10-31T18:36:32+00:00" }, { "name": "egulias/email-validator", @@ -574,8 +774,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev", - "dev-1.x": "1.x-dev" + "dev-1.x": "1.x-dev", + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -685,16 +885,16 @@ }, { "name": "functional-php/fantasy-land", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/functional-php/fantasy-land.git", - "reference": "1e2366e68057bbc7a65d58dd663324eb9b299775" + "reference": "9a43201d827ced038ffd374f2d018d7a54ad33e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/functional-php/fantasy-land/zipball/1e2366e68057bbc7a65d58dd663324eb9b299775", - "reference": "1e2366e68057bbc7a65d58dd663324eb9b299775", + "url": "https://api.github.com/repos/functional-php/fantasy-land/zipball/9a43201d827ced038ffd374f2d018d7a54ad33e2", + "reference": "9a43201d827ced038ffd374f2d018d7a54ad33e2", "shasum": "" }, "require": { @@ -727,30 +927,30 @@ "description": "Specification for interoperability of common algebraic structures in PHP", "support": { "issues": "https://github.com/functional-php/fantasy-land/issues", - "source": "https://github.com/functional-php/fantasy-land/tree/1.0.1" + "source": "https://github.com/functional-php/fantasy-land/tree/1.0.2" }, - "time": "2021-01-19T18:06:19+00:00" + "time": "2024-12-18T16:28:32+00:00" }, { "name": "graham-campbell/result-type", - "version": "v1.1.3", + "version": "v1.1.4", "source": { "type": "git", "url": "https://github.com/GrahamCampbell/Result-Type.git", - "reference": "3ba905c11371512af9d9bdd27d99b782216b6945" + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945", - "reference": "3ba905c11371512af9d9bdd27d99b782216b6945", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/e01f4a821471308ba86aa202fed6698b6b695e3b", + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.3" + "phpoption/phpoption": "^1.9.5" }, "require-dev": { - "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + "phpunit/phpunit": "^8.5.41 || ^9.6.22 || ^10.5.45 || ^11.5.7" }, "type": "library", "autoload": { @@ -779,7 +979,7 @@ ], "support": { "issues": "https://github.com/GrahamCampbell/Result-Type/issues", - "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3" + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.4" }, "funding": [ { @@ -791,20 +991,20 @@ "type": "tidelift" } ], - "time": "2024-07-20T21:45:45+00:00" + "time": "2025-12-27T19:43:20+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "7.9.2", + "version": "7.9.3", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "d281ed313b989f213357e3be1a179f02196ac99b" + "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b", - "reference": "d281ed313b989f213357e3be1a179f02196ac99b", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", + "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", "shasum": "" }, "require": { @@ -901,7 +1101,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.9.2" + "source": "https://github.com/guzzle/guzzle/tree/7.9.3" }, "funding": [ { @@ -917,7 +1117,7 @@ "type": "tidelift" } ], - "time": "2024-07-24T11:22:20+00:00" + "time": "2025-03-27T13:37:11+00:00" }, { "name": "guzzlehttp/promises", @@ -1000,16 +1200,16 @@ }, { "name": "guzzlehttp/psr7", - "version": "2.7.0", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201" + "reference": "21dc724a0583619cd1652f673303492272778051" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201", - "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051", + "reference": "21dc724a0583619cd1652f673303492272778051", "shasum": "" }, "require": { @@ -1025,7 +1225,7 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "http-interop/http-factory-tests": "0.9.0", - "phpunit/phpunit": "^8.5.39 || ^9.6.20" + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -1096,7 +1296,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.7.0" + "source": "https://github.com/guzzle/psr7/tree/2.8.0" }, "funding": [ { @@ -1112,7 +1312,7 @@ "type": "tidelift" } ], - "time": "2024-07-18T11:15:46+00:00" + "time": "2025-08-23T21:21:41+00:00" }, { "name": "illuminate/auth", @@ -2717,16 +2917,16 @@ }, { "name": "laravel/serializable-closure", - "version": "v1.3.5", + "version": "v1.3.7", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c" + "reference": "4f48ade902b94323ca3be7646db16209ec76be3d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c", - "reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/4f48ade902b94323ca3be7646db16209ec76be3d", + "reference": "4f48ade902b94323ca3be7646db16209ec76be3d", "shasum": "" }, "require": { @@ -2774,20 +2974,170 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2024-09-23T13:33:08+00:00" + "time": "2024-11-14T18:34:49+00:00" + }, + { + "name": "league/flysystem", + "version": "1.1.10", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "3239285c825c152bcc315fe0e87d6b55f5972ed1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/3239285c825c152bcc315fe0e87d6b55f5972ed1", + "reference": "3239285c825c152bcc315fe0e87d6b55f5972ed1", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "league/mime-type-detection": "^1.3", + "php": "^7.2.5 || ^8.0" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "require-dev": { + "phpspec/prophecy": "^1.11.1", + "phpunit/phpunit": "^8.5.8" + }, + "suggest": { + "ext-ftp": "Allows you to use FTP server storage", + "ext-openssl": "Allows you to use FTPS server storage", + "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", + "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", + "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", + "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", + "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", + "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", + "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", + "league/flysystem-webdav": "Allows you to use WebDAV storage", + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Filesystem abstraction: Many filesystems, one API.", + "keywords": [ + "Cloud Files", + "WebDAV", + "abstraction", + "aws", + "cloud", + "copy.com", + "dropbox", + "file systems", + "files", + "filesystem", + "filesystems", + "ftp", + "rackspace", + "remote", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/1.1.10" + }, + "funding": [ + { + "url": "https://offset.earth/frankdejonge", + "type": "other" + } + ], + "time": "2022-10-04T09:16:37+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": "monolog/monolog", - "version": "2.9.3", + "version": "2.11.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "a30bfe2e142720dfa990d0a7e573997f5d884215" + "reference": "37308608e599f34a1a4845b16440047ec98a172a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/a30bfe2e142720dfa990d0a7e573997f5d884215", - "reference": "a30bfe2e142720dfa990d0a7e573997f5d884215", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/37308608e599f34a1a4845b16440047ec98a172a", + "reference": "37308608e599f34a1a4845b16440047ec98a172a", "shasum": "" }, "require": { @@ -2805,7 +3155,7 @@ "graylog2/gelf-php": "^1.4.2 || ^2@dev", "guzzlehttp/guzzle": "^7.4", "guzzlehttp/psr7": "^2.2", - "mongodb/mongodb": "^1.8", + "mongodb/mongodb": "^1.8 || ^2.0", "php-amqplib/php-amqplib": "~2.4 || ^3", "phpspec/prophecy": "^1.15", "phpstan/phpstan": "^1.10", @@ -2864,7 +3214,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/2.9.3" + "source": "https://github.com/Seldaek/monolog/tree/2.11.0" }, "funding": [ { @@ -2876,20 +3226,86 @@ "type": "tidelift" } ], - "time": "2024-04-12T20:52:51+00:00" + "time": "2026-01-01T13:05:00+00:00" + }, + { + "name": "mtdowling/jmespath.php", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/jmespath/jmespath.php.git", + "reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/a2a865e05d5f420b50cc2f85bb78d565db12a6bc", + "reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "symfony/polyfill-mbstring": "^1.17" + }, + "require-dev": { + "composer/xdebug-handler": "^3.0.3", + "phpunit/phpunit": "^8.5.33" + }, + "bin": [ + "bin/jp.php" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "files": [ + "src/JmesPath.php" + ], + "psr-4": { + "JmesPath\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Declaratively specify how to extract elements from a JSON document", + "keywords": [ + "json", + "jsonpath" + ], + "support": { + "issues": "https://github.com/jmespath/jmespath.php/issues", + "source": "https://github.com/jmespath/jmespath.php/tree/2.8.0" + }, + "time": "2024-09-04T18:46:31+00:00" }, { "name": "nesbot/carbon", - "version": "2.72.5", + "version": "2.73.0", "source": { "type": "git", - "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "afd46589c216118ecd48ff2b95d77596af1e57ed" + "url": "https://github.com/CarbonPHP/carbon.git", + "reference": "9228ce90e1035ff2f0db84b40ec2e023ed802075" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/afd46589c216118ecd48ff2b95d77596af1e57ed", - "reference": "afd46589c216118ecd48ff2b95d77596af1e57ed", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/9228ce90e1035ff2f0db84b40ec2e023ed802075", + "reference": "9228ce90e1035ff2f0db84b40ec2e023ed802075", "shasum": "" }, "require": { @@ -2909,7 +3325,7 @@ "doctrine/orm": "^2.7 || ^3.0", "friendsofphp/php-cs-fixer": "^3.0", "kylekatarnls/multi-tester": "^2.0", - "ondrejmirtes/better-reflection": "*", + "ondrejmirtes/better-reflection": "<6", "phpmd/phpmd": "^2.9", "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "^0.12.99 || ^1.7.14", @@ -2922,10 +3338,6 @@ ], "type": "library", "extra": { - "branch-alias": { - "dev-master": "3.x-dev", - "dev-2.x": "2.x-dev" - }, "laravel": { "providers": [ "Carbon\\Laravel\\ServiceProvider" @@ -2935,6 +3347,10 @@ "includes": [ "extension.neon" ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-master": "3.x-dev" } }, "autoload": { @@ -2983,7 +3399,7 @@ "type": "tidelift" } ], - "time": "2024-06-03T19:18:41+00:00" + "time": "2025-01-08T20:10:23+00:00" }, { "name": "nikic/fast-route", @@ -3037,16 +3453,16 @@ }, { "name": "opis/closure", - "version": "3.6.3", + "version": "3.7.0", "source": { "type": "git", "url": "https://github.com/opis/closure.git", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad" + "reference": "b1a22a6be71c1263f3ca6e68f00b3fd4d394abc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/3d81e4309d2a927abbe66df935f4bb60082805ad", - "reference": "3d81e4309d2a927abbe66df935f4bb60082805ad", + "url": "https://api.github.com/repos/opis/closure/zipball/b1a22a6be71c1263f3ca6e68f00b3fd4d394abc4", + "reference": "b1a22a6be71c1263f3ca6e68f00b3fd4d394abc4", "shasum": "" }, "require": { @@ -3096,22 +3512,22 @@ ], "support": { "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.3" + "source": "https://github.com/opis/closure/tree/3.7.0" }, - "time": "2022-01-27T09:35:39+00:00" + "time": "2025-07-08T20:30:08+00:00" }, { "name": "phpoption/phpoption", - "version": "1.9.3", + "version": "1.9.5", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54" + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54", - "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/75365b91986c2405cf5e1e012c5595cd487a98be", + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be", "shasum": "" }, "require": { @@ -3119,7 +3535,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + "phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34" }, "type": "library", "extra": { @@ -3161,7 +3577,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.3" + "source": "https://github.com/schmittjoh/php-option/tree/1.9.5" }, "funding": [ { @@ -3173,7 +3589,7 @@ "type": "tidelift" } ], - "time": "2024-07-20T21:41:07+00:00" + "time": "2025-12-27T19:41:33+00:00" }, { "name": "psr/clock", @@ -3718,21 +4134,20 @@ }, { "name": "ramsey/uuid", - "version": "4.7.6", + "version": "4.9.2", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "91039bc1faa45ba123c4328958e620d382ec7088" + "reference": "8429c78ca35a09f27565311b98101e2826affde0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/91039bc1faa45ba123c4328958e620d382ec7088", - "reference": "91039bc1faa45ba123c4328958e620d382ec7088", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/8429c78ca35a09f27565311b98101e2826affde0", + "reference": "8429c78ca35a09f27565311b98101e2826affde0", "shasum": "" }, "require": { - "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12", - "ext-json": "*", + "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" }, @@ -3740,26 +4155,23 @@ "rhumsaa/uuid": "self.version" }, "require-dev": { - "captainhook/captainhook": "^5.10", + "captainhook/captainhook": "^5.25", "captainhook/plugin-composer": "^5.3", - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "doctrine/annotations": "^1.8", - "ergebnis/composer-normalize": "^2.15", - "mockery/mockery": "^1.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "ergebnis/composer-normalize": "^2.47", + "mockery/mockery": "^1.6", "paragonie/random-lib": "^2", - "php-mock/php-mock": "^2.2", - "php-mock/php-mock-mockery": "^1.3", - "php-parallel-lint/php-parallel-lint": "^1.1", - "phpbench/phpbench": "^1.0", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-mockery": "^1.1", - "phpstan/phpstan-phpunit": "^1.1", - "phpunit/phpunit": "^8.5 || ^9", - "ramsey/composer-repl": "^1.4", - "slevomat/coding-standard": "^8.4", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "^4.9" + "php-mock/php-mock": "^2.6", + "php-mock/php-mock-mockery": "^1.5", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpbench/phpbench": "^1.2.14", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6", + "slevomat/coding-standard": "^8.18", + "squizlabs/php_codesniffer": "^3.13" }, "suggest": { "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", @@ -3794,32 +4206,22 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.7.6" + "source": "https://github.com/ramsey/uuid/tree/4.9.2" }, - "funding": [ - { - "url": "https://github.com/ramsey", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", - "type": "tidelift" - } - ], - "time": "2024-04-27T21:32:50+00:00" + "time": "2025-12-14T04:43:48+00:00" }, { "name": "symfony/console", - "version": "v5.4.44", + "version": "v5.4.47", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "5b5a0aa66e3296e303e22490f90f521551835a83" + "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/5b5a0aa66e3296e303e22490f90f521551835a83", - "reference": "5b5a0aa66e3296e303e22490f90f521551835a83", + "url": "https://api.github.com/repos/symfony/console/zipball/c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", + "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", "shasum": "" }, "require": { @@ -3889,7 +4291,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.44" + "source": "https://github.com/symfony/console/tree/v5.4.47" }, "funding": [ { @@ -3905,7 +4307,7 @@ "type": "tidelift" } ], - "time": "2024-09-20T07:56:40+00:00" + "time": "2024-11-06T11:30:55+00:00" }, { "name": "symfony/deprecation-contracts", @@ -3926,12 +4328,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.0-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -3976,16 +4378,16 @@ }, { "name": "symfony/error-handler", - "version": "v5.4.42", + "version": "v5.4.46", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "db15ba0fd50890156ed40087ccedc7851a1f5b76" + "reference": "d19ede7a2cafb386be9486c580649d0f9e3d0363" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/db15ba0fd50890156ed40087ccedc7851a1f5b76", - "reference": "db15ba0fd50890156ed40087ccedc7851a1f5b76", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/d19ede7a2cafb386be9486c580649d0f9e3d0363", + "reference": "d19ede7a2cafb386be9486c580649d0f9e3d0363", "shasum": "" }, "require": { @@ -4027,7 +4429,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v5.4.42" + "source": "https://github.com/symfony/error-handler/tree/v5.4.46" }, "funding": [ { @@ -4043,7 +4445,7 @@ "type": "tidelift" } ], - "time": "2024-07-23T12:34:05+00:00" + "time": "2024-11-05T14:17:06+00:00" }, { "name": "symfony/event-dispatcher", @@ -4151,12 +4553,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.0-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -4209,16 +4611,16 @@ }, { "name": "symfony/finder", - "version": "v5.4.43", + "version": "v5.4.45", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "ae25a9145a900764158d439653d5630191155ca0" + "reference": "63741784cd7b9967975eec610b256eed3ede022b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/ae25a9145a900764158d439653d5630191155ca0", - "reference": "ae25a9145a900764158d439653d5630191155ca0", + "url": "https://api.github.com/repos/symfony/finder/zipball/63741784cd7b9967975eec610b256eed3ede022b", + "reference": "63741784cd7b9967975eec610b256eed3ede022b", "shasum": "" }, "require": { @@ -4252,7 +4654,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.4.43" + "source": "https://github.com/symfony/finder/tree/v5.4.45" }, "funding": [ { @@ -4268,20 +4670,20 @@ "type": "tidelift" } ], - "time": "2024-08-13T14:03:51+00:00" + "time": "2024-09-28T13:32:08+00:00" }, { "name": "symfony/http-foundation", - "version": "v5.4.44", + "version": "v5.4.50", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "ae0d217e5932aa0b70ddb4cf7822cc76d48aee53" + "reference": "1a0706e8b8041046052ea2695eb8aeee04f97609" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ae0d217e5932aa0b70ddb4cf7822cc76d48aee53", - "reference": "ae0d217e5932aa0b70ddb4cf7822cc76d48aee53", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/1a0706e8b8041046052ea2695eb8aeee04f97609", + "reference": "1a0706e8b8041046052ea2695eb8aeee04f97609", "shasum": "" }, "require": { @@ -4328,7 +4730,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v5.4.44" + "source": "https://github.com/symfony/http-foundation/tree/v5.4.50" }, "funding": [ { @@ -4339,25 +4741,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-15T07:55:06+00:00" + "time": "2025-11-03T12:58:48+00:00" }, { "name": "symfony/http-kernel", - "version": "v5.4.44", + "version": "v5.4.51", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "788dcf72d9af7432a886aa3b0c5904d68087ba13" + "reference": "85432f7548b2757b8387f7e1a468abd62fc4c9bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/788dcf72d9af7432a886aa3b0c5904d68087ba13", - "reference": "788dcf72d9af7432a886aa3b0c5904d68087ba13", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/85432f7548b2757b8387f7e1a468abd62fc4c9bc", + "reference": "85432f7548b2757b8387f7e1a468abd62fc4c9bc", "shasum": "" }, "require": { @@ -4441,7 +4847,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v5.4.44" + "source": "https://github.com/symfony/http-kernel/tree/v5.4.51" }, "funding": [ { @@ -4452,25 +4858,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-21T05:47:58+00:00" + "time": "2026-01-28T07:10:35+00:00" }, { "name": "symfony/mime", - "version": "v5.4.43", + "version": "v5.4.45", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "a02711d6ce461edada8c0f8641aa536709b99b47" + "reference": "8c1b9b3e5b52981551fc6044539af1d974e39064" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/a02711d6ce461edada8c0f8641aa536709b99b47", - "reference": "a02711d6ce461edada8c0f8641aa536709b99b47", + "url": "https://api.github.com/repos/symfony/mime/zipball/8c1b9b3e5b52981551fc6044539af1d974e39064", + "reference": "8c1b9b3e5b52981551fc6044539af1d974e39064", "shasum": "" }, "require": { @@ -4526,7 +4936,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v5.4.43" + "source": "https://github.com/symfony/mime/tree/v5.4.45" }, "funding": [ { @@ -4542,11 +4952,11 @@ "type": "tidelift" } ], - "time": "2024-08-13T10:38:38+00:00" + "time": "2024-10-23T20:18:32+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -4570,8 +4980,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -4605,7 +5015,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" }, "funding": [ { @@ -4616,6 +5026,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -4625,16 +5039,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", "shasum": "" }, "require": { @@ -4646,8 +5060,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -4683,7 +5097,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.33.0" }, "funding": [ { @@ -4694,25 +5108,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-06-27T09:58:17+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773" + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773", - "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", "shasum": "" }, "require": { @@ -4725,8 +5143,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -4766,7 +5184,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0" }, "funding": [ { @@ -4777,16 +5195,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-09-10T14:38:51+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -4807,8 +5229,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -4847,7 +5269,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.33.0" }, "funding": [ { @@ -4858,6 +5280,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -4867,19 +5293,20 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.31.0", + "version": "v1.33.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": { @@ -4891,8 +5318,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -4927,7 +5354,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" }, "funding": [ { @@ -4938,16 +5365,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "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.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", @@ -4965,8 +5396,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -5003,7 +5434,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.33.0" }, "funding": [ { @@ -5014,6 +5445,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -5023,16 +5458,16 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.31.0", + "version": "v1.33.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": { @@ -5041,8 +5476,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -5083,7 +5518,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" }, "funding": [ { @@ -5094,16 +5529,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-01-02T08:10:11+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.31.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", @@ -5121,8 +5560,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -5159,7 +5598,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.33.0" }, "funding": [ { @@ -5170,6 +5609,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -5179,16 +5622,16 @@ }, { "name": "symfony/process", - "version": "v5.4.44", + "version": "v5.4.51", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "1b9fa82b5c62cd49da8c9e3952dd8531ada65096" + "reference": "467bfc56f18f5ef6d5ccb09324d7e988c1c0a98f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/1b9fa82b5c62cd49da8c9e3952dd8531ada65096", - "reference": "1b9fa82b5c62cd49da8c9e3952dd8531ada65096", + "url": "https://api.github.com/repos/symfony/process/zipball/467bfc56f18f5ef6d5ccb09324d7e988c1c0a98f", + "reference": "467bfc56f18f5ef6d5ccb09324d7e988c1c0a98f", "shasum": "" }, "require": { @@ -5221,7 +5664,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.44" + "source": "https://github.com/symfony/process/tree/v5.4.51" }, "funding": [ { @@ -5232,25 +5675,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-17T12:46:43+00:00" + "time": "2026-01-26T15:53:37+00:00" }, { "name": "symfony/service-contracts", - "version": "v2.5.3", + "version": "v2.5.4", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "a2329596ddc8fd568900e3fc76cba42489ecc7f3" + "reference": "f37b419f7aea2e9abf10abd261832cace12e3300" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/a2329596ddc8fd568900e3fc76cba42489ecc7f3", - "reference": "a2329596ddc8fd568900e3fc76cba42489ecc7f3", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f37b419f7aea2e9abf10abd261832cace12e3300", + "reference": "f37b419f7aea2e9abf10abd261832cace12e3300", "shasum": "" }, "require": { @@ -5266,12 +5713,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -5304,7 +5751,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.3" + "source": "https://github.com/symfony/service-contracts/tree/v2.5.4" }, "funding": [ { @@ -5320,7 +5767,7 @@ "type": "tidelift" } ], - "time": "2023-04-21T15:04:16+00:00" + "time": "2024-09-25T14:11:13+00:00" }, { "name": "symfony/string", @@ -5524,12 +5971,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.0-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -5582,16 +6029,16 @@ }, { "name": "symfony/var-dumper", - "version": "v5.4.43", + "version": "v5.4.48", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "6be6a6a8af4818564e3726fc65cf936f34743cef" + "reference": "42f18f170aa86d612c3559cfb3bd11a375df32c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/6be6a6a8af4818564e3726fc65cf936f34743cef", - "reference": "6be6a6a8af4818564e3726fc65cf936f34743cef", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/42f18f170aa86d612c3559cfb3bd11a375df32c8", + "reference": "42f18f170aa86d612c3559cfb3bd11a375df32c8", "shasum": "" }, "require": { @@ -5651,7 +6098,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v5.4.43" + "source": "https://github.com/symfony/var-dumper/tree/v5.4.48" }, "funding": [ { @@ -5667,30 +6114,30 @@ "type": "tidelift" } ], - "time": "2024-08-30T16:01:46+00:00" + "time": "2024-11-08T15:21:10+00:00" }, { "name": "vlucas/phpdotenv", - "version": "v5.6.1", + "version": "v5.6.3", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2" + "reference": "955e7815d677a3eaa7075231212f2110983adecc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/a59a13791077fe3d44f90e7133eb68e7d22eaff2", - "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/955e7815d677a3eaa7075231212f2110983adecc", + "reference": "955e7815d677a3eaa7075231212f2110983adecc", "shasum": "" }, "require": { "ext-pcre": "*", - "graham-campbell/result-type": "^1.1.3", + "graham-campbell/result-type": "^1.1.4", "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.3", - "symfony/polyfill-ctype": "^1.24", - "symfony/polyfill-mbstring": "^1.24", - "symfony/polyfill-php80": "^1.24" + "phpoption/phpoption": "^1.9.5", + "symfony/polyfill-ctype": "^1.26", + "symfony/polyfill-mbstring": "^1.26", + "symfony/polyfill-php80": "^1.26" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", @@ -5739,7 +6186,7 @@ ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.1" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.3" }, "funding": [ { @@ -5751,7 +6198,7 @@ "type": "tidelift" } ], - "time": "2024-07-20T21:52:34+00:00" + "time": "2025-12-27T19:49:13+00:00" }, { "name": "voku/portable-ascii", @@ -5827,76 +6274,18 @@ ], "time": "2022-01-24T18:55:24+00:00" }, - { - "name": "webmozart/assert", - "version": "1.11.0", - "source": { - "type": "git", - "url": "https://github.com/webmozarts/assert.git", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "php": "^7.2 || ^8.0" - }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.13" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.10-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], - "support": { - "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.11.0" - }, - "time": "2022-06-03T18:03:27+00:00" - }, { "name": "widmogrod/php-functional", - "version": "6.0.1", + "version": "6.0.2", "source": { "type": "git", "url": "https://github.com/widmogrod/php-functional.git", - "reference": "e479913945d885f7d441373ea4926cbba612c1ad" + "reference": "e87398e029b07784cf028ff3363ed61242f58d73" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/widmogrod/php-functional/zipball/e479913945d885f7d441373ea4926cbba612c1ad", - "reference": "e479913945d885f7d441373ea4926cbba612c1ad", + "url": "https://api.github.com/repos/widmogrod/php-functional/zipball/e87398e029b07784cf028ff3363ed61242f58d73", + "reference": "e87398e029b07784cf028ff3363ed61242f58d73", "shasum": "" }, "require": { @@ -5951,46 +6340,43 @@ "description": "Functors, Applicative and Monads are fascinating concept. Purpose of this library is to explore them in OOP PHP world.", "support": { "issues": "https://github.com/widmogrod/php-functional/issues", - "source": "https://github.com/widmogrod/php-functional/tree/6.0.1" + "source": "https://github.com/widmogrod/php-functional/tree/6.0.2" }, - "time": "2024-03-25T10:44:45+00:00" + "time": "2024-12-18T18:24:14+00:00" } ], "packages-dev": [ { "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v1.0.0", + "version": "v0.7.2", "source": { "type": "git", - "url": "https://github.com/PHPCSStandards/composer-installer.git", - "reference": "4be43904336affa5c2f70744a348312336afd0da" + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da", - "reference": "4be43904336affa5c2f70744a348312336afd0da", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", "shasum": "" }, "require": { "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.4", + "php": ">=5.3", "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" }, "require-dev": { "composer/composer": "*", - "ext-json": "*", - "ext-zip": "*", "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpcompatibility/php-compatibility": "^9.0", - "yoast/phpunit-polyfills": "^1.0" + "phpcompatibility/php-compatibility": "^9.0" }, "type": "composer-plugin", "extra": { - "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" }, "autoload": { "psr-4": { - "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -6006,7 +6392,7 @@ }, { "name": "Contributors", - "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" } ], "description": "PHP_CodeSniffer Standards Composer Installer Plugin", @@ -6030,10 +6416,10 @@ "tests" ], "support": { - "issues": "https://github.com/PHPCSStandards/composer-installer/issues", - "source": "https://github.com/PHPCSStandards/composer-installer" + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" }, - "time": "2023-01-05T11:28:13+00:00" + "time": "2022-02-04T12:51:07+00:00" }, { "name": "doctrine/instantiator", @@ -6107,20 +6493,20 @@ }, { "name": "hamcrest/hamcrest-php", - "version": "v2.0.1", + "version": "v2.1.1", "source": { "type": "git", "url": "https://github.com/hamcrest/hamcrest-php.git", - "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", - "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", "shasum": "" }, "require": { - "php": "^5.3|^7.0|^8.0" + "php": "^7.4|^8.0" }, "replace": { "cordoval/hamcrest-php": "*", @@ -6128,8 +6514,8 @@ "kodova/hamcrest-php": "*" }, "require-dev": { - "phpunit/php-file-iterator": "^1.4 || ^2.0", - "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" + "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0" }, "type": "library", "extra": { @@ -6152,9 +6538,9 @@ ], "support": { "issues": "https://github.com/hamcrest/hamcrest-php/issues", - "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1" }, - "time": "2020-07-09T08:09:16+00:00" + "time": "2025-04-30T06:54:44+00:00" }, { "name": "mockery/mockery", @@ -6241,16 +6627,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.12.0", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", - "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { @@ -6289,7 +6675,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, "funding": [ { @@ -6297,20 +6683,20 @@ "type": "tidelift" } ], - "time": "2024-06-12T14:39:25+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { "name": "nikic/php-parser", - "version": "v5.3.1", + "version": "v5.7.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", - "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", "shasum": "" }, "require": { @@ -6329,7 +6715,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.x-dev" } }, "autoload": { @@ -6353,9 +6739,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" }, - "time": "2024-10-08T18:51:32+00:00" + "time": "2025-12-06T11:56:16+00:00" }, { "name": "phar-io/manifest", @@ -6477,30 +6863,30 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.33.0", + "version": "2.3.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140" + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/82a311fd3690fb2bf7b64d5c98f912b3dd746140", - "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": "^7.4 || ^8.0" }, "require-dev": { "doctrine/annotations": "^2.0", - "nikic/php-parser": "^4.15", + "nikic/php-parser": "^5.3.0", "php-parallel-lint/php-parallel-lint": "^1.2", "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^1.5", - "phpstan/phpstan-phpunit": "^1.1", - "phpstan/phpstan-strict-rules": "^1.0", - "phpunit/phpunit": "^9.5", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", "symfony/process": "^5.2" }, "type": "library", @@ -6518,9 +6904,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.33.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2" }, - "time": "2024-10-13T11:25:22+00:00" + "time": "2026-01-25T14:56:51+00:00" }, { "name": "phpunit/php-code-coverage", @@ -7159,16 +7545,16 @@ }, { "name": "sebastian/comparator", - "version": "4.0.8", + "version": "4.0.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e4df00b9b3571187db2831ae9aada2c6efbd715d", + "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d", "shasum": "" }, "require": { @@ -7221,15 +7607,27 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.10" }, "funding": [ { "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/sebastian/comparator", + "type": "tidelift" } ], - "time": "2022-09-14T12:41:17+00:00" + "time": "2026-01-24T09:22:56+00:00" }, { "name": "sebastian/complexity", @@ -7419,16 +7817,16 @@ }, { "name": "sebastian/exporter", - "version": "4.0.6", + "version": "4.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" + "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", - "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/14c6ba52f95a36c3d27c835d65efc7123c446e8c", + "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c", "shasum": "" }, "require": { @@ -7484,28 +7882,40 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.8" }, "funding": [ { "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/sebastian/exporter", + "type": "tidelift" } ], - "time": "2024-03-02T06:33:00+00:00" + "time": "2025-09-24T06:03:27+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.7", + "version": "5.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" + "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", - "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/b6781316bdcd28260904e7cc18ec983d0d2ef4f6", + "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6", "shasum": "" }, "require": { @@ -7548,15 +7958,27 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.8" }, "funding": [ { "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/sebastian/global-state", + "type": "tidelift" } ], - "time": "2024-03-02T06:35:11+00:00" + "time": "2025-08-10T07:10:35+00:00" }, { "name": "sebastian/lines-of-code", @@ -7729,16 +8151,16 @@ }, { "name": "sebastian/recursion-context", - "version": "4.0.5", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + "reference": "539c6691e0623af6dc6f9c20384c120f963465a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/539c6691e0623af6dc6f9c20384c120f963465a0", + "reference": "539c6691e0623af6dc6f9c20384c120f963465a0", "shasum": "" }, "require": { @@ -7780,15 +8202,27 @@ "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.6" }, "funding": [ { "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/sebastian/recursion-context", + "type": "tidelift" } ], - "time": "2023-02-03T06:07:39+00:00" + "time": "2025-08-10T06:57:39+00:00" }, { "name": "sebastian/resource-operations", @@ -7955,32 +8389,32 @@ }, { "name": "slevomat/coding-standard", - "version": "8.15.0", + "version": "8.27.1", "source": { "type": "git", "url": "https://github.com/slevomat/coding-standard.git", - "reference": "7d1d957421618a3803b593ec31ace470177d7817" + "reference": "29bdaee8b65e7ed2b8e702b01852edba8bae1769" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/7d1d957421618a3803b593ec31ace470177d7817", - "reference": "7d1d957421618a3803b593ec31ace470177d7817", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/29bdaee8b65e7ed2b8e702b01852edba8bae1769", + "reference": "29bdaee8b65e7ed2b8e702b01852edba8bae1769", "shasum": "" }, "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.0", - "php": "^7.2 || ^8.0", - "phpstan/phpdoc-parser": "^1.23.1", - "squizlabs/php_codesniffer": "^3.9.0" + "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || ^1.2.0", + "php": "^7.4 || ^8.0", + "phpstan/phpdoc-parser": "^2.3.1", + "squizlabs/php_codesniffer": "^4.0.1" }, "require-dev": { - "phing/phing": "2.17.4", - "php-parallel-lint/php-parallel-lint": "1.3.2", - "phpstan/phpstan": "1.10.60", - "phpstan/phpstan-deprecation-rules": "1.1.4", - "phpstan/phpstan-phpunit": "1.3.16", - "phpstan/phpstan-strict-rules": "1.5.2", - "phpunit/phpunit": "8.5.21|9.6.8|10.5.11" + "phing/phing": "3.0.1|3.1.1", + "php-parallel-lint/php-parallel-lint": "1.4.0", + "phpstan/phpstan": "2.1.37", + "phpstan/phpstan-deprecation-rules": "2.0.3", + "phpstan/phpstan-phpunit": "2.0.12", + "phpstan/phpstan-strict-rules": "2.0.7", + "phpunit/phpunit": "9.6.31|10.5.60|11.4.4|11.5.49|12.5.7" }, "type": "phpcodesniffer-standard", "extra": { @@ -8004,7 +8438,7 @@ ], "support": { "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/8.15.0" + "source": "https://github.com/slevomat/coding-standard/tree/8.27.1" }, "funding": [ { @@ -8016,41 +8450,36 @@ "type": "tidelift" } ], - "time": "2024-03-09T15:20:58+00:00" + "time": "2026-01-25T15:57:07+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.10.3", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "62d32998e820bddc40f99f8251958aed187a5c9c" + "reference": "0525c73950de35ded110cffafb9892946d7771b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/62d32998e820bddc40f99f8251958aed187a5c9c", - "reference": "62d32998e820bddc40f99f8251958aed187a5c9c", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0525c73950de35ded110cffafb9892946d7771b5", + "reference": "0525c73950de35ded110cffafb9892946d7771b5", "shasum": "" }, "require": { "ext-simplexml": "*", "ext-tokenizer": "*", "ext-xmlwriter": "*", - "php": ">=5.4.0" + "php": ">=7.2.0" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + "phpunit/phpunit": "^8.4.0 || ^9.3.4 || ^10.5.32 || 11.3.3 - 11.5.28 || ^11.5.31" }, "bin": [ "bin/phpcbf", "bin/phpcs" ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" @@ -8069,7 +8498,7 @@ "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" } ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "description": "PHP_CodeSniffer tokenizes PHP files and detects violations of a defined set of coding standards.", "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", "keywords": [ "phpcs", @@ -8094,22 +8523,26 @@ { "url": "https://opencollective.com/php_codesniffer", "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" } ], - "time": "2024-09-18T10:38:58+00:00" + "time": "2025-11-10T16:43:36+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.3", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", - "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", "shasum": "" }, "require": { @@ -8138,7 +8571,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" }, "funding": [ { @@ -8146,7 +8579,7 @@ "type": "github" } ], - "time": "2024-03-03T12:36:25+00:00" + "time": "2025-11-17T20:03:58+00:00" } ], "aliases": [], diff --git a/phpunit.xml b/phpunit.xml index 57ededb..3c5e38b 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -32,11 +32,13 @@ src/Exceptions/Handler.php src/Libraries/Kafka.php src/Libraries/KafkaCallable.php + src/Libraries/Storage.php src/Commands src/Facades src/Middlewares src/Providers src/Validations + src/Libraries/StorageDrivers diff --git a/public/gg/file.txt b/public/gg/file.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/Libraries/FirebaseClient.php b/src/Libraries/FirebaseClient.php new file mode 100644 index 0000000..aaee89c --- /dev/null +++ b/src/Libraries/FirebaseClient.php @@ -0,0 +1,393 @@ + + * @license https://mit-license.org/ MIT License + * @version GIT: 0.3.8 + * @link https://github.com/spotlibs + */ + +declare(strict_types=1); + +namespace Spotlibs\PhpLib\Libraries; + +use GuzzleHttp\Client as GuzzleClient; +use GuzzleHttp\Exception\ClientException; +use GuzzleHttp\Psr7\Request; +use Psr\Http\Message\ResponseInterface; +use Spotlibs\PhpLib\Exceptions\RuntimeException; +use Spotlibs\PhpLib\Logs\Log; + +/** + * FirebaseClient + * + * SDK for Firebase OAuth and FCM operations with file-based token persistence + * + * @category HttpClient + * @package Client + * @author Mufthi Ryanda + * @license https://mit-license.org/ MIT License + * @link https://github.com/spotlibs + */ +class FirebaseClient +{ + private GuzzleClient $httpClient; + private array $serviceAccount; + private string $proxyUrl = ''; + private string $tokenFile; + + /** + * Create Firebase client + * + * @param array $config Guzzle config options + * + * @throws RuntimeException When FIREBASE_CREDENTIALS env not set + */ + public function __construct(array $config = []) + { + $serviceAccountPath = env('FIREBASE_CREDENTIALS'); + if (empty($serviceAccountPath)) { + throw new RuntimeException('FIREBASE_CREDENTIALS environment variable is not set'); + } + $fullPath = base_path($serviceAccountPath); + if (!file_exists($fullPath)) { + throw new RuntimeException("Firebase credentials file not found: {$fullPath}"); + } + + $this->serviceAccount = json_decode( + file_get_contents($fullPath), + true, + 512, + JSON_THROW_ON_ERROR + ); + + // Set token file path in storage + $this->tokenFile = storage_path('framework/cache/firebase_token.json'); + + // Ensure directory exists + $dir = dirname($this->tokenFile); + if (!is_dir($dir)) { + mkdir($dir, 0755, true); + } + + $defaultConfig = [ + 'timeout' => 60, + 'verify' => false, + ]; + + $this->httpClient = new GuzzleClient(array_merge($defaultConfig, $config)); + } + + /** + * Set proxy URL + * + * @param string $proxyUrl Proxy URL (e.g., http://proxy:port) + * + * @return self + */ + public function setProxy(string $proxyUrl): self + { + $this->proxyUrl = $proxyUrl; + return $this; + } + + /** + * Get or refresh access token from file + * + * @param bool $forceRefresh Force token regeneration + * + * @return string Access token + * + * @throws \GuzzleHttp\Exception\GuzzleException On HTTP error + */ + private function getAccessToken(bool $forceRefresh = false): string + { + // Try to read existing token + if (!$forceRefresh && file_exists($this->tokenFile)) { + $handle = fopen($this->tokenFile, 'r'); + if ($handle && flock($handle, LOCK_SH)) { + $content = fread($handle, filesize($this->tokenFile)); + flock($handle, LOCK_UN); + fclose($handle); + + $tokenData = json_decode($content, true); + + // Check if token is still valid (with 5 min buffer) + if ($tokenData && isset($tokenData['token'], $tokenData['expiry']) && $tokenData['expiry'] > time() + 300) { + return $tokenData['token']; + } + } elseif ($handle) { + fclose($handle); + } + } + + Log::runtime()->info( + [ + 'operation' => 'firebase_token_refresh', + 'reason' => $forceRefresh ? 'forced' : (!file_exists($this->tokenFile) ? 'empty' : 'expired') + ] + ); + + // Generate new token and save to file + $tokenData = $this->generateToken(); + $this->saveTokenToFile($tokenData); + + return $tokenData['token']; + } + + /** + * Save token data to file with lock + * + * @param array $tokenData Token data with 'token' and 'expiry' keys + * + * @return void + */ + private function saveTokenToFile(array $tokenData): void + { + $handle = fopen($this->tokenFile, 'c'); + if ($handle && flock($handle, LOCK_EX)) { + ftruncate($handle, 0); + fwrite($handle, json_encode($tokenData, JSON_THROW_ON_ERROR)); + fflush($handle); + flock($handle, LOCK_UN); + } + if ($handle) { + fclose($handle); + } + } + + /** + * Generate OAuth2 access token + * + * @return array Array with 'token' and 'expiry' keys + * + * @throws \GuzzleHttp\Exception\GuzzleException On HTTP error + */ + private function generateToken(): array + { + $startTime = microtime(true); + $now = time(); + + $jwt = $this->createJWT( + [ + 'iss' => $this->serviceAccount['client_email'], + 'scope' => 'https://www.googleapis.com/auth/firebase.messaging', + 'aud' => 'https://oauth2.googleapis.com/token', + 'iat' => $now, + 'exp' => $now + 3600 + ], + $this->serviceAccount['private_key'] + ); + + $body = http_build_query( + [ + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', + 'assertion' => $jwt + ] + ); + + $request = new Request( + 'POST', + 'https://oauth2.googleapis.com/token', + ['Content-Type' => 'application/x-www-form-urlencoded'], + $body + ); + + $options = []; + if (!empty($this->proxyUrl)) { + $options['proxy'] = $this->proxyUrl; + } + + $response = $this->httpClient->send($request, $options); + $elapsed = microtime(true) - $startTime; + + $responseBody = json_decode( + $response->getBody()->getContents(), + true, + 512, + JSON_THROW_ON_ERROR + ); + + $token = $responseBody['access_token']; + $expiresIn = $responseBody['expires_in'] ?? 3600; + $expiry = time() + $expiresIn; + + Log::runtime()->info( + [ + 'operation' => 'firebase_oauth', + 'url' => 'https://oauth2.googleapis.com/token', + 'responseTime' => round($elapsed * 1000), + 'httpCode' => $response->getStatusCode() + ] + ); + + return ['token' => $token, 'expiry' => $expiry]; + } + + /** + * Send FCM message + * + * @param array $message FCM message payload + * + * @return ResponseInterface + * + * @throws \GuzzleHttp\Exception\GuzzleException On HTTP error + */ + public function sendMessage(array $message): ResponseInterface + { + $token = $this->getAccessToken(); + + $startTime = microtime(true); + $projectId = $this->serviceAccount['project_id']; + $url = "https://fcm.googleapis.com/v1/projects/{$projectId}/messages:send"; + + $request = new Request( + 'POST', + $url, + [ + 'Authorization' => 'Bearer ' . $token, + 'Content-Type' => 'application/json' + ], + json_encode(['message' => $message], JSON_THROW_ON_ERROR) + ); + + $options = []; + if (!empty($this->proxyUrl)) { + $options['proxy'] = $this->proxyUrl; + } + + try { + $response = $this->httpClient->send($request, $options); + $elapsed = microtime(true) - $startTime; + + $respBody = $response->getBody()->getContents(); + $response->getBody()->rewind(); + + Log::runtime()->info( + [ + 'operation' => 'firebase_fcm_send', + 'host' => 'fcm.googleapis.com', + 'url' => "/v1/projects/{$projectId}/messages:send", + 'request' => ['body' => $message], + 'response' => [ + 'httpCode' => $response->getStatusCode(), + 'body' => json_decode($respBody, true) + ], + 'responseTime' => round($elapsed * 1000) + ] + ); + + return $response; + } catch (ClientException $e) { + // On 401, regenerate token and retry once + if ($e->getResponse()->getStatusCode() === 401) { + Log::runtime()->warning( + [ + 'operation' => 'firebase_fcm_send_401', + 'message' => 'Token unauthorized, regenerating and retrying' + ] + ); + + $newToken = $this->getAccessToken(true); + + $retryRequest = new Request( + 'POST', + $url, + [ + 'Authorization' => 'Bearer ' . $newToken, + 'Content-Type' => 'application/json' + ], + json_encode(['message' => $message], JSON_THROW_ON_ERROR) + ); + + return $this->httpClient->send($retryRequest, $options); + } + + throw $e; + } + } + + /** + * Send to multiple tokens (multicast) + * + * @param array $tokens FCM registration tokens + * @param array $notification Notification payload + * @param array $data Data payload + * + * @return array Results with success/failure counts + */ + public function sendMulticast( + array $tokens, + array $notification = [], + array $data = [] + ): array { + $results = ['success' => 0, 'failure' => 0, 'responses' => []]; + + foreach ($tokens as $token) { + $message = ['token' => $token]; + if (!empty($notification)) { + $message['notification'] = $notification; + } + if (!empty($data)) { + $message['data'] = $data; + } + + try { + $response = $this->sendMessage($message); + if ($response->getStatusCode() === 200) { + $results['success']++; + $results['responses'][] = [ + 'token' => $token, + 'success' => true + ]; + } else { + $results['failure']++; + $results['responses'][] = [ + 'token' => $token, + 'success' => false, + 'error' => $response->getBody()->getContents() + ]; + } + } catch (\Throwable $e) { + $results['failure']++; + $results['responses'][] = [ + 'token' => $token, + 'success' => false, + 'error' => $e->getMessage() + ]; + } + } + + return $results; + } + + /** + * Generate JWT manually using OpenSSL + * + * @param array $payload JWT payload + * @param string $privateKey RSA private key + * + * @return string JWT token + */ + private function createJWT(array $payload, string $privateKey): string + { + $header = base64_encode(json_encode(['alg' => 'RS256', 'typ' => 'JWT'])); + $payload = base64_encode(json_encode($payload)); + + $signature = ''; + openssl_sign( + $header . '.' . $payload, + $signature, + $privateKey, + OPENSSL_ALGO_SHA256 + ); + + $signature = base64_encode($signature); + + return str_replace(['+', '/', '='], ['-', '_', ''], $header . '.' . $payload . '.' . $signature); + } +} diff --git a/src/Libraries/KafkaCallable.php b/src/Libraries/KafkaCallable.php deleted file mode 100644 index 252cd6a..0000000 --- a/src/Libraries/KafkaCallable.php +++ /dev/null @@ -1,178 +0,0 @@ - - * @license https://mit-license.org/ MIT License - * @version GIT: 0.0.4 - * @link https://github.com/spotlibs - */ - -declare(strict_types=1); - -namespace Spotlibs\PhpLib\Libraries; - -use Illuminate\Support\Facades\Log; -use Spotlibs\PhpLib\Libraries\Kafka as KafkaLibrary; -use RdKafka\Message; -use RdKafka\Producer; -use RdKafka\KafkaConsumer; - -/** - * KafkaCallable - * - * @category Library - * @package Libraries - * @author Hendri Nursyahbani - * @license https://mit-license.org/ MIT License - * @link https://github.com/spotlibs - */ -class KafkaCallable extends KafkaLibrary -{ - /** - * Summary of deliveryReportCallback - * - * @param \RdKafka\Producer $producer producer instance - * @param \RdKafka\Message $message message to produce - * - * @return void - */ - public static function deliveryReportCallback(Producer $producer, Message $message) - { - if ($message->err == RD_KAFKA_RESP_ERR_NO_ERROR) { - // phpcs:ignore - Log::channel('runtime')->info("deliveryReportCallback triggered. Message delivered successfully to Topic: {$message->topic_name}. Partition: {$message->partition}. Offset: {$message->offset}. Key: {$message->key}. Timestamp: {$message->timestamp}"); - } else { - // phpcs:ignore - Log::channel('runtime')->error("deliveryReportCallback triggered. Message delivery failed: " . $message->errstr() . ". Topic: {$message->topic_name}. Partition: {$message->partition}. Offset: {$message->offset}. Key: {$message->key}. Timestamp: {$message->timestamp}"); - } - } - - /** - * Summary of errorProduceCallback - * - * @param \RdKafka\Producer $producer producer instance - * @param int $err error code - * @param string $reason error message - * - * @return void - */ - public static function errorProduceCallback(Producer $producer, int $err, string $reason) - { - Log::channel('runtime')->error("errorProduceCallback triggered. Kafka producer error: " . rd_kafka_err2str($err) . ". Reason: {$reason}"); - } - - /** - * Summary of errorConsumeCallback - * - * @param \RdKafka\KafkaConsumer $consumer consumer instance - * @param int $err error code - * @param string $reason error message - * - * @return void - */ - public static function errorConsumeCallback(KafkaConsumer $consumer, int $err, string $reason) - { - // phpcs:ignore - Log::channel('runtime')->error("errorConsumeCallback triggered. Kafka producer error: " . rd_kafka_err2str($err) . ". Reason: {$reason}" . " [Client ID: " . env('APP_NAME') . '-' . gethostname() . "]"); - } - - /** - * Summary of rebalanceCallback - * - * @param \RdKafka\KafkaConsumer $consumer consumer instance - * @param int $err error code - * @param array $partitions list of partitions - * - * @return void - */ - public static function rebalanceCallback(KafkaConsumer $consumer, int $err, array $partitions) - { - if ($err == RD_KAFKA_RESP_ERR__ASSIGN_PARTITIONS) { - foreach ($partitions as $partition) { - // phpcs:ignore - Log::channel('runtime')->info("rebalanceCallback triggered. Status: Assigned partition. Topic: {$partition->getTopic()}. Partition: {$partition->getPartition()}. Offset: {$partition->getOffset()}"); - } - $consumer->assign($partitions); - } elseif ($err == RD_KAFKA_RESP_ERR__REVOKE_PARTITIONS) { - foreach ($partitions as $partition) { - // phpcs:ignore - Log::channel('runtime')->info("rebalanceCallback triggered. Status: Revoked partition. Topic: {$partition->getTopic()}. Partition: {$partition->getPartition()}. Offset: {$partition->getOffset()}"); - } - $consumer->assign(null); - } else { - Log::channel('runtime')->error("rebalanceCallback triggered. Rebalance error: " . rd_kafka_err2str($err)); - } - } - - /** - * Summary of consumeCallback - * - * @param mixed $message callback message - * - * @return void - */ - public static function consumeCallback($message) - { - $topic_name = $message->getTopicName() ?? null; - $partition = $message->getPartition() ?? null; - $offset = $message->getOffset() ?? null; - $key = $message->getKey() ?? null; - $timestamp = $message->getTimestamp() ?? null; - Log::channel('runtime')->info("consumeCallback triggered. Topic: {$topic_name}. Partition: {$partition}. Offset: {$offset}. Key: {$key}. Timestamp: {$timestamp}"); - } - - /** - * Summary of logCallback - * - * @param mixed $kafka kafka instance - * @param mixed $level log level - * @param mixed $facility kafka facility - * @param mixed $message log message usually in JSON format - * - * @return void - */ - public static function logCallback($kafka, $level, $facility, $message) - { - if ($level == LOG_DEBUG || $level == LOG_INFO || $level == LOG_NOTICE) { - Log::channel('runtime')->info("logCallback triggered. Kafka log [{$facility}]: {$message}"); - } elseif ($level == LOG_WARNING || $level == LOG_ALERT) { - Log::channel('runtime')->warning("logCallback triggered. Kafka log [{$facility}]: {$message}"); - } elseif ($level == LOG_ERR || $level == LOG_CRIT || $level == LOG_EMERG) { - Log::channel('runtime')->error("logCallback triggered. Kafka log [{$facility}]: {$message}"); - } else { - Log::channel('runtime')->info("logCallback triggered. Kafka log [{$facility}]: {$message}"); - } - } - - /** - * Summary of offsetCommitCallback - * - * @param mixed $consumer consumer instance - * @param mixed $err error code - * @param mixed $partitions list of partitions - * - * @return void - */ - public static function offsetCommitCallback($consumer, $err, $partitions) - { - if ($err) { - Log::channel('runtime')->error("offsetCommitCallback triggered. Offset commit failed: " . rd_kafka_err2str($err) . " [Client ID: " . env('APP_NAME') . '-' . gethostname() . "]"); - } else { - $partitionDetails = array_map( - function ($partition) { - return [ - 'topic' => $partition->getTopic(), - 'partition' => $partition->getPartition(), - 'offset' => $partition->getOffset() - ]; - }, - $partitions - ); - Log::channel('runtime')->info("offsetCommitCallback triggered. Offset commit succeeded: " . json_encode($partitionDetails) . " [Client ID: " . env('APP_NAME') . '-' . gethostname() . "]"); - } - } -} diff --git a/src/Libraries/Storage.php b/src/Libraries/Storage.php new file mode 100644 index 0000000..58badad --- /dev/null +++ b/src/Libraries/Storage.php @@ -0,0 +1,42 @@ + + * @license https://mit-license.org/ MIT License + * @version GIT: 0.7.0 + * @link https://github.com/spotlibs + */ + +declare(strict_types=1); + +namespace Spotlibs\PhpLib\Libraries; + +/** + * Storage + * + * @category Library + * @package Libraries + * @author Made Mas Adi Winata + * @license https://mit-license.org/ MIT License + * @link https://github.com/spotlibs + */ +class Storage +{ + protected string $driver; + + /** + * Parse file name from filepath + * + * @param string $filepath path of the file + * + * @return string + */ + protected function getFileName(string $filepath): string + { + return basename($filepath); + } +} diff --git a/src/Libraries/StorageDrivers/Minio.php b/src/Libraries/StorageDrivers/Minio.php new file mode 100644 index 0000000..e79320e --- /dev/null +++ b/src/Libraries/StorageDrivers/Minio.php @@ -0,0 +1,165 @@ + + * @license https://mit-license.org/ MIT License + * @version GIT: 0.7.0 + * @link https://github.com/spotlibs + */ + +declare(strict_types=1); + +namespace Spotlibs\PhpLib\Libraries\StorageDrivers; + +use Carbon\Carbon; +use Illuminate\Http\UploadedFile; +use Illuminate\Support\Facades\Storage as FacadeStorage; +use Illuminate\Support\Str; +use Spotlibs\PhpLib\Exceptions\RuntimeException; +use Spotlibs\PhpLib\Libraries\Storage; + +/** + * Storage + * + * @category Library + * @package Libraries + * @author Made Mas Adi Winata + * @license https://mit-license.org/ MIT License + * @link https://github.com/spotlibs + */ +class Minio extends Storage implements StorageInterface +{ + /** + * Create a new NFS instance. + * + * @return void + */ + public function __construct() + { + $this->driver = "minio"; + } + + /** + * Set disk driver for different minio config setup in config/filesystem.php + * + * @param string $driver driver name according to config setup in config/filesystem.php + * + * @return Minio + */ + public function disk(string $driver): self + { + $this->driver = $driver; + return $this; + } + /** + * Upload file to disk + * + * @param UploadedFile $file file from http request + * @param string $dirpath where to put the file + * @param string $filename optionally overide file name + * + * @throws \Spotlibs\PhpLib\Exceptions\RuntimeException + * + * @return bool + */ + public function upload(UploadedFile $file, string $dirpath, string $filename = ""): bool + { + $fileName = $filename == "" ? $file->getClientOriginalName() : $filename; + FacadeStorage::disk($this->driver)->put($dirpath . "/" . $fileName, $file->getContent()); + return true; + } + /** + * Copy file in disk + * + * @param string $srcPath source file path + * @param string $destPath destination file path + * + * @throws \Spotlibs\PhpLib\Exceptions\RuntimeException + * + * @return bool + */ + public function copy(string $srcPath, string $destPath): bool + { + FacadeStorage::disk($this->driver)->copy($srcPath, $destPath); + return true; + } + /** + * Delete file in disk + * + * @param string $filepath file path to delete + * + * @throws \Spotlibs\PhpLib\Exceptions\RuntimeException + * + * @return bool + */ + public function delete(string $filepath): bool + { + FacadeStorage::disk($this->driver)->delete($filepath); + return true; + } + /** + * Move file in disk + * + * @param string $srcPath source file path + * @param string $destPath destination file path + * + * @throws \Spotlibs\PhpLib\Exceptions\RuntimeException + * + * @return bool + */ + public function move(string $srcPath, string $destPath): bool + { + FacadeStorage::disk($this->driver)->move($srcPath, $destPath); + return true; + } + /** + * Create temporary URL for file in disk + * + * @param string $filepath file path to create secure link + * + * @return string + */ + public function securelink(string $filepath): string + { + /** + * Illuminate\Filesystem\FilesystemAdapter $disk file storage disk + * + * @var \Spotlibs\PhpLib\Libraries\StorageDrivers\MinioAdapter $disk file storage disk + */ + $disk = FacadeStorage::disk($this->driver)->getDriver(); + $urlpath = $disk->temporaryUrl($filepath, Carbon::now()->addSeconds((int) env('MINIO_EXPIRED_URL', 60))); + return $urlpath; + } + /** + * Create temporary URL for a folder in disk + * + * @param string $dirpath directory path to create secure link + * + * @return array + */ + public function securelinkFolder(string $dirpath): array + { + $random = Str::random(40); + $linkFolder = "/var/www/html/public/securelink/$random"; + if (!mkdir($linkFolder, 0755, true)) { + throw new RuntimeException("Failed to create securelink directory: $linkFolder"); + } + $allFiles = FacadeStorage::disk($this->driver)->allFiles($dirpath); + $result = []; + foreach ($allFiles as $file) { + $filename = parent::getFileName($file); + $fileStream = FacadeStorage::disk($this->driver)->readStream($file); + $writeStream = fopen($linkFolder . "/" . $filename, 'w'); + stream_copy_to_stream($fileStream, $writeStream); + fclose($fileStream); + fclose($writeStream); + $result[] = env('APP_URL') . "/securelink/" . $random . "/" . $filename; + } + + return $result; + } +} diff --git a/src/Libraries/StorageDrivers/MinioAdapter.php b/src/Libraries/StorageDrivers/MinioAdapter.php new file mode 100644 index 0000000..9e81450 --- /dev/null +++ b/src/Libraries/StorageDrivers/MinioAdapter.php @@ -0,0 +1,138 @@ + + * @license https://mit-license.org/ MIT License + * @version GIT: 0.7.0 + * @link https://github.com/spotlibs + */ + +declare(strict_types=1); + +namespace Spotlibs\PhpLib\Libraries\StorageDrivers; + +use Aws\S3\S3Client; +use Carbon\Carbon; +use League\Flysystem\AwsS3v3\AwsS3Adapter; +use League\Flysystem\Filesystem; + +/** + * Storage + * + * @category Library + * @package Libraries + * @author Made Mas Adi Winata + * @license https://mit-license.org/ MIT License + * @link https://github.com/spotlibs + */ +class MinioAdapter extends Filesystem +{ + protected S3Client $publicClient; + protected string $bucket; + + /** + * Create a new Minio Filesystem Adapter instance. + * + * @param AwsS3Adapter $adapter filesystem adapter + * @param array $config driver config + * @param S3Client $publicClient client for generating presigned URL + * + * @return void + */ + public function __construct(AwsS3Adapter $adapter, array $config, S3Client $publicClient) + { + parent::__construct($adapter, $config); + + $this->bucket = $config['bucket']; + + // Create public client for pre-signed URLs + $this->publicClient = $publicClient ?: new S3Client( + [ + 'credentials' => [ + 'key' => $config['key'], + 'secret' => $config['secret'] + ], + 'region' => $config['region'], + 'version' => 'latest', + 'use_path_style_endpoint' => true, + 'endpoint' => $config['url'] ?? $config['endpoint'], + ] + ); + } + + /** + * Temporary URL facade + * + * @param string $path file path + * @param int|string|Carbon $expiration time limit of url + * @param array $options optional + * + * @return string + */ + // phpcs:disable SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint + // phpcs:disable PEAR.Commenting.FunctionComment.Missing + public function temporaryUrl(string $path, int|string|Carbon $expiration, array $options = []): string + { + $command = $this->publicClient->getCommand( + 'GetObject', + array_merge( + [ + 'Bucket' => $this->bucket, + 'Key' => $path, + ], + $options + ) + ); + + $request = $this->publicClient->createPresignedRequest( + $command, + $expiration + ); + + return (string) $request->getUri(); + } + + /** + * Move a file to a new location using S3 native copy and delete. + * + * @param string $path source path + * @param string $newpath destination path + * + * @return bool + */ + public function move(string $path, string $newpath): bool + { + try { + // Copy the file + if ($this->copy($path, $newpath)) { + // Delete the original + return $this->delete($path); + } + + return false; + } catch (\Exception $e) { + return false; + } + } + + /** + * Get all files in a directory + * + * @param string $dirpath path of the directory + * + * @return bool + */ + public function allFiles(string $dirpath): array + { + try { + $paths = $this->listContents($dirpath); + return $paths; + } catch (\Throwable $th) { + throw $th; + } + } +} diff --git a/src/Libraries/StorageDrivers/NFS.php b/src/Libraries/StorageDrivers/NFS.php new file mode 100644 index 0000000..b19110c --- /dev/null +++ b/src/Libraries/StorageDrivers/NFS.php @@ -0,0 +1,207 @@ + + * @license https://mit-license.org/ MIT License + * @version GIT: 0.7.0 + * @link https://github.com/spotlibs + */ + +declare(strict_types=1); + +namespace Spotlibs\PhpLib\Libraries\StorageDrivers; + +use Illuminate\Http\UploadedFile; +use Illuminate\Support\Str; +use Spotlibs\PhpLib\Exceptions\RuntimeException; +use Spotlibs\PhpLib\Libraries\Storage; + +/** + * Storage + * + * @category Library + * @package Libraries + * @author Made Mas Adi Winata + * @license https://mit-license.org/ MIT License + * @link https://github.com/spotlibs + */ +class NFS extends Storage implements StorageInterface +{ + /** + * Upload file to disk + * + * @param UploadedFile $file file from http request + * @param string $dirpath where to put the file + * @param string $filename optionally overide file name + * + * @throws \Spotlibs\PhpLib\Exceptions\RuntimeException + * + * @return bool + */ + public function upload(UploadedFile $file, string $dirpath, string $filename = ''): bool + { + if (!is_dir($dirpath)) { + if (!mkdir($dirpath, 0755, true)) { + throw new RuntimeException("Failed to create destination directory: $dirpath"); + } + } + $file->move($dirpath, $filename == '' ? $file->getClientOriginalName() : $filename); + return true; + } + /** + * Copy file in disk + * + * @param string $srcPath source file path + * @param string $destPath destination file path + * + * @throws \Spotlibs\PhpLib\Exceptions\RuntimeException + * + * @return bool + */ + public function copy(string $srcPath, string $destPath): bool + { + if (!is_file($srcPath)) { + throw new RuntimeException("File not found within filepath: $srcPath"); + } + $tmp = explode("/", $destPath); + $destDir = implode("/", array_slice($tmp, 0, -1)); + try { + $this->createDirectory($destDir); + exec("cp $srcPath $destPath"); + } catch (\Throwable $th) { + throw new RuntimeException("Failed to copy from $srcPath to $destPath. " . $th->getMessage()); + } + return true; + } + /** + * Delete file in disk + * + * @param string $filepath file path to delete + * + * @throws \Spotlibs\PhpLib\Exceptions\RuntimeException + * + * @return bool + */ + public function delete(string $filepath): bool + { + if (!is_file($filepath)) { + return true; + } + if (!exec("rm $filepath")) { + throw new RuntimeException("Failed to delete $filepath"); + } + return true; + } + /** + * Move file in disk + * + * @param string $srcPath source file path + * @param string $destPath destination file path, include filename if you want to change its name + * + * @throws \Spotlibs\PhpLib\Exceptions\RuntimeException + * + * @return bool + */ + public function move(string $srcPath, string $destPath): bool + { + $tmp = explode("/", $destPath); + $destDir = implode("/", array_slice($tmp, 0, -1)); + try { + $this->createDirectory($destDir); + exec("mv $srcPath $destPath"); + } catch (\Throwable $th) { + throw new RuntimeException("Failed to move from $srcPath to $destPath. " . $th->getMessage()); + } + return true; + } + /** + * Create temporary URL for file in disk + * + * @param string $filepath file path to create secure link + * + * @throws \Spotlibs\PhpLib\Exceptions\RuntimeException + * + * @return string + */ + public function securelink(string $filepath): string + { + if (!is_file($filepath)) { + throw new RuntimeException("File not found within filepath: $filepath"); + } + $extension = $this->getExtension($filepath); + $random = Str::random(40); + $random = $extension !== "" ? "$random.$extension" : $random; + exec("ln -s \"$filepath\" /var/www/html/public/securelink/$random", $output, $exit_code); + if ($exit_code !== 0) { + throw new RuntimeException("Failed to create secure link for file: $filepath"); + } + return env('APP_URL') . "/securelink/$random"; + } + /** + * Create temporary URL for a folder in disk + * + * @param string $dirpath directory path to create secure link + * + * @throws \Spotlibs\PhpLib\Exceptions\RuntimeException + * + * @return array + */ + public function securelinkFolder(string $dirpath): array + { + if (!is_dir($dirpath)) { + throw new RuntimeException("Directory not found within dirpath: $dirpath"); + } + $random = Str::random(40); + exec("ln -sf $dirpath /var/www/html/public/securelink/$random", $output, $exit_code); + if ($exit_code !== 0) { + throw new RuntimeException("Failed to create secure link for file: $dirpath"); + } + $result = glob("/var/www/html/public/securelink/$random/*"); + foreach ($result as $key => $r) { + $result[$key] = str_replace("/var/www/html/public", env('APP_URL'), $r); + } + return $result; + } + /** + * Create new directory in NFS + * + * @param string $dirpath path of new directory + * + * @throws RuntimeException + * + * @return void + */ + private function createDirectory(string $dirpath): void + { + try { + if (!is_dir($dirpath)) { + if (!mkdir($dirpath, 0755, true)) { + throw new RuntimeException("Failed to create destination directory: $dirpath"); + } + } + exec("chown -R 33:33 $dirpath"); + } catch (\Throwable $th) { + throw new RuntimeException("Failed to create new directory" . $th->getMessage()); + } + } + + /** + * Extract file's extension + * + * @param string $filepath path of the file + * + * @return string + */ + private function getExtension(string $filepath): string + { + $temp = explode(".", basename($filepath)); + if (\count($temp) > 0) { + return $temp[1]; + } + return ""; + } +} diff --git a/src/Libraries/StorageDrivers/StorageInterface.php b/src/Libraries/StorageDrivers/StorageInterface.php new file mode 100644 index 0000000..a33a390 --- /dev/null +++ b/src/Libraries/StorageDrivers/StorageInterface.php @@ -0,0 +1,95 @@ + + * @license https://mit-license.org/ MIT License + * @version GIT: 0.7.0 + * @link https://github.com/spotlibs + */ + +declare(strict_types=1); + +namespace Spotlibs\PhpLib\Libraries\StorageDrivers; + +use Illuminate\Http\UploadedFile; + +/** + * Storage + * + * @category Library + * @package Libraries + * @author Made Mas Adi Winata + * @license https://mit-license.org/ MIT License + * @link https://github.com/spotlibs + */ +interface StorageInterface +{ + /** + * Upload file to disk + * + * @param UploadedFile $file file from http request + * @param string $dirpath where to put the file + * @param string $filename optionally overide file name + * + * @throws \Spotlibs\PhpLib\Exceptions\RuntimeException + * + * @return bool + */ + public function upload(UploadedFile $file, string $dirpath, string $filename = ''): bool; + /** + * Copy file in disk + * + * @param string $srcPath source file path + * @param string $destPath destination file path + * + * @throws \Spotlibs\PhpLib\Exceptions\RuntimeException + * + * @return bool + */ + public function copy(string $srcPath, string $destPath): bool; + /** + * Delete file in disk + * + * @param string $filepath file path to delete + * + * @throws \Spotlibs\PhpLib\Exceptions\RuntimeException + * + * @return bool + */ + public function delete(string $filepath): bool; + /** + * Move file in disk + * + * @param string $srcPath source file path + * @param string $destPath destination file path + * + * @throws \Spotlibs\PhpLib\Exceptions\RuntimeException + * + * @return bool + */ + public function move(string $srcPath, string $destPath): bool; + /** + * Create temporary URL for file in disk + * + * @param string $filepath file path to create secure link + * + * @throws \Spotlibs\PhpLib\Exceptions\RuntimeException + * + * @return void + */ + public function securelink(string $filepath): string; + /** + * Create temporary URL for a folder in disk + * + * @param string $dirpath directory path to create secure link + * + * @throws \Spotlibs\PhpLib\Exceptions\RuntimeException + * + * @return array + */ + public function securelinkFolder(string $dirpath): array; +} diff --git a/src/Providers/MinioStorageServiceProvider.php b/src/Providers/MinioStorageServiceProvider.php new file mode 100644 index 0000000..ecca2e6 --- /dev/null +++ b/src/Providers/MinioStorageServiceProvider.php @@ -0,0 +1,86 @@ + + * @license https://mit-license.org/ MIT License + * @version GIT: 0.3.1 + * @link https://github.com/spotlibs + */ + +declare(strict_types=1); + +namespace Spotlibs\PhpLib\Providers; + +use Aws\S3\S3Client; +use Illuminate\Support\ServiceProvider; +use League\Flysystem\AwsS3v3\AwsS3Adapter; +use Illuminate\Support\Facades\Storage; +use Spotlibs\PhpLib\Libraries\StorageDrivers\MinioAdapter; + +/** + * MinioStorageServiceProvider + * + * Service provider for MinIO storage + * + * @category StandardService + * @package ServiceProvider + * @author Made Mas Adi Winata + * @license https://mit-license.org/ MIT License + * @link https://github.com/spotlibs + */ +class MinioStorageServiceProvider extends ServiceProvider +{ + /** + * Bootstrap the application services. + * + * @return void + */ + public function boot() + { + Storage::extend('minio', function ($app, $config) { + // Client for internal Docker network communication + $client = new S3Client([ + 'credentials' => [ + 'key' => $config["key"], + 'secret' => $config["secret"] + ], + 'region' => $config["region"], + 'version' => "latest", + 'bucket_endpoint' => false, + 'use_path_style_endpoint' => true, + 'endpoint' => $config["endpoint"], // e.g., http://minio:9000 + ]); + + $adapter = new AwsS3Adapter($client, $config["bucket"]); + + // Public client for browser-accessible URLs + $publicClient = new S3Client([ + 'credentials' => [ + 'key' => $config['key'], + 'secret' => $config['secret'] + ], + 'region' => $config['region'], + 'version' => 'latest', + 'use_path_style_endpoint' => true, + 'endpoint' => $config['url'] ?? $config['endpoint'], // e.g., http://localhost:9000 + ]); + + // Return your custom MinioAdapter (which extends Filesystem) + return new MinioAdapter($adapter, $config, $publicClient); + }); + } + + /** + * Register the application services. + * + * @return void + */ + public function register() + { + + } +} diff --git a/tests/Libraries/FirebaseClientTest.php b/tests/Libraries/FirebaseClientTest.php new file mode 100644 index 0000000..32927c2 --- /dev/null +++ b/tests/Libraries/FirebaseClientTest.php @@ -0,0 +1,291 @@ + + * @license https://mit-license.org/ MIT License + * @version GIT: 0.3.8 + * @link https://github.com/spotlibs + */ + +declare(strict_types=1); + +namespace Tests\Libraries; + +use GuzzleHttp\Client as GuzzleClient; +use GuzzleHttp\Exception\ClientException; +use GuzzleHttp\Psr7\Response; +use GuzzleHttp\Psr7\Request; +use Mockery; +use Spotlibs\PhpLib\Libraries\FirebaseClient; +use Tests\TestCase; + +// Test helper class that extends FirebaseClient +class TestableFirebaseClient extends FirebaseClient +{ + public function __construct(GuzzleClient $httpClient, array $serviceAccount) + { + $reflection = new \ReflectionClass(FirebaseClient::class); + + $httpProperty = $reflection->getProperty('httpClient'); + $httpProperty->setAccessible(true); + $httpProperty->setValue($this, $httpClient); + + $serviceProperty = $reflection->getProperty('serviceAccount'); + $serviceProperty->setAccessible(true); + $serviceProperty->setValue($this, $serviceAccount); + + $proxyProperty = $reflection->getProperty('proxyUrl'); + $proxyProperty->setAccessible(true); + $proxyProperty->setValue($this, ''); + + $tokenProperty = $reflection->getProperty('tokenFile'); + $tokenProperty->setAccessible(true); + $tokenProperty->setValue($this, '/mock/path/firebase_token.json'); + } + + // Expose getAccessToken as public for testing + public function getAccessTokenPublic(bool $forceRefresh = false): string + { + return 'mock_access_token_12345'; + } + + // Override sendMessage to use our public method + public function sendMessage(array $message): \Psr\Http\Message\ResponseInterface + { + $token = $this->getAccessTokenPublic(); + + $reflection = new \ReflectionClass(FirebaseClient::class); + $serviceProperty = $reflection->getProperty('serviceAccount'); + $serviceProperty->setAccessible(true); + $serviceAccount = $serviceProperty->getValue($this); + + $httpProperty = $reflection->getProperty('httpClient'); + $httpProperty->setAccessible(true); + $httpClient = $httpProperty->getValue($this); + + $proxyProperty = $reflection->getProperty('proxyUrl'); + $proxyProperty->setAccessible(true); + $proxyUrl = $proxyProperty->getValue($this); + + $startTime = microtime(true); + $projectId = $serviceAccount['project_id']; + $url = "https://fcm.googleapis.com/v1/projects/{$projectId}/messages:send"; + + $request = new Request( + 'POST', + $url, + [ + 'Authorization' => 'Bearer ' . $token, + 'Content-Type' => 'application/json' + ], + json_encode(['message' => $message], JSON_THROW_ON_ERROR) + ); + + $options = []; + if (!empty($proxyUrl)) { + $options['proxy'] = $proxyUrl; + } + + try { + $response = $httpClient->send($request, $options); + return $response; + } catch (ClientException $e) { + if ($e->getResponse()->getStatusCode() === 401) { + $newToken = $this->getAccessTokenPublic(true); + + $retryRequest = new Request( + 'POST', + $url, + [ + 'Authorization' => 'Bearer ' . $newToken, + 'Content-Type' => 'application/json' + ], + json_encode(['message' => $message], JSON_THROW_ON_ERROR) + ); + + return $httpClient->send($retryRequest, $options); + } + + throw $e; + } + } +} + +class FirebaseClientTest extends TestCase +{ + private array $mockServiceAccount = [ + 'type' => 'service_account', + 'project_id' => 'test-project', + 'private_key_id' => 'key123', + 'private_key' => 'fake-key', + 'client_email' => 'test@test-project.iam.gserviceaccount.com', + 'client_id' => '12345', + ]; + + /** @test */ + public function testSendMessage(): void + { + $mockResponse = new Response( + 200, + [], + json_encode(['name' => 'projects/test-project/messages/123']) + ); + + $guzzleMock = Mockery::mock(GuzzleClient::class); + $guzzleMock->shouldReceive('send') + ->once() + ->andReturn($mockResponse); + + $client = $this->createMockedClient($guzzleMock); + + $message = [ + 'token' => 'device_token_123', + 'notification' => [ + 'title' => 'Test', + 'body' => 'Test message' + ] + ]; + + $response = $client->sendMessage($message); + + $this->assertEquals(200, $response->getStatusCode()); + } + + /** @test */ + public function testSendMessageRetryOn401(): void + { + $unauthorizedResponse = new Response(401, [], json_encode(['error' => 'Unauthorized'])); + $successResponse = new Response(200, [], json_encode(['name' => 'projects/test-project/messages/123'])); + + $exception = new ClientException( + 'Unauthorized', + new Request('POST', 'https://fcm.googleapis.com'), + $unauthorizedResponse + ); + + $guzzleMock = Mockery::mock(GuzzleClient::class); + $guzzleMock->shouldReceive('send') + ->once() + ->andThrow($exception); + $guzzleMock->shouldReceive('send') + ->once() + ->andReturn($successResponse); + + $client = $this->createMockedClient($guzzleMock); + + $message = ['token' => 'device_token_123']; + $response = $client->sendMessage($message); + + $this->assertEquals(200, $response->getStatusCode()); + } + + /** @test */ + public function testSendMulticast(): void + { + $mockResponse = new Response(200, [], json_encode(['name' => 'projects/test-project/messages/123'])); + + $guzzleMock = Mockery::mock(GuzzleClient::class); + $guzzleMock->shouldReceive('send') + ->times(3) + ->andReturn($mockResponse); + + $client = $this->createMockedClient($guzzleMock); + + $tokens = ['token1', 'token2', 'token3']; + $notification = ['title' => 'Test', 'body' => 'Message']; + + $result = $client->sendMulticast($tokens, $notification); + + $this->assertEquals(3, $result['success']); + $this->assertEquals(0, $result['failure']); + $this->assertCount(3, $result['responses']); + } + + /** @test */ + public function testSendMulticastWithFailures(): void + { + $successResponse = new Response(200, [], json_encode(['name' => 'projects/test-project/messages/123'])); + $errorResponse = new Response(400, [], json_encode(['error' => 'Invalid token'])); + + $guzzleMock = Mockery::mock(GuzzleClient::class); + $guzzleMock->shouldReceive('send') + ->once() + ->andReturn($successResponse); + $guzzleMock->shouldReceive('send') + ->once() + ->andReturn($errorResponse); + + $client = $this->createMockedClient($guzzleMock); + + $tokens = ['token1', 'token2']; + $result = $client->sendMulticast($tokens); + + $this->assertEquals(1, $result['success']); + $this->assertEquals(1, $result['failure']); + } + + /** @test */ + public function testSendMulticastWithException(): void + { + $mockResponse = new Response(200, [], json_encode(['name' => 'projects/test-project/messages/123'])); + + $guzzleMock = Mockery::mock(GuzzleClient::class); + $guzzleMock->shouldReceive('send') + ->once() + ->andReturn($mockResponse); + $guzzleMock->shouldReceive('send') + ->once() + ->andThrow(new \Exception('Network error')); + + $client = $this->createMockedClient($guzzleMock); + + $tokens = ['token1', 'token2']; + $result = $client->sendMulticast($tokens); + + $this->assertEquals(1, $result['success']); + $this->assertEquals(1, $result['failure']); + $this->assertStringContainsString('Network error', $result['responses'][1]['error']); + } + + /** @test */ + public function testSendMulticastWithData(): void + { + $mockResponse = new Response(200, [], json_encode(['name' => 'projects/test-project/messages/123'])); + + $guzzleMock = Mockery::mock(GuzzleClient::class); + $guzzleMock->shouldReceive('send') + ->once() + ->andReturn($mockResponse); + + $client = $this->createMockedClient($guzzleMock); + + $tokens = ['token1']; + $notification = ['title' => 'Test', 'body' => 'Message']; + $data = ['key' => 'value']; + + $result = $client->sendMulticast($tokens, $notification, $data); + + $this->assertEquals(1, $result['success']); + $this->assertEquals(0, $result['failure']); + } + + /** @test */ + public function testSetProxy(): void + { + $guzzleMock = Mockery::mock(GuzzleClient::class); + $client = $this->createMockedClient($guzzleMock); + + $result = $client->setProxy('http://proxy.example.com:8080'); + + $this->assertInstanceOf(TestableFirebaseClient::class, $result); + } + + private function createMockedClient($guzzleMock): TestableFirebaseClient + { + return new TestableFirebaseClient($guzzleMock, $this->mockServiceAccount); + } +} \ No newline at end of file