diff --git a/composer.json b/composer.json index 01bc50600..793a8e709 100644 --- a/composer.json +++ b/composer.json @@ -13,6 +13,8 @@ "ext-json": "*", "ext-pdo": "*", "ext-pdo_sqlite": "*", + "symfony/string": "^6.0", + "symfony/translation-contracts": "^2.5", "teamtnt/tntsearch": "^4.2" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 285207ad3..0291166bb 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "868fdee6714fe65b8bc4358ee8a694ab", + "content-hash": "85a6171c06f9fbdbe53275ecec2b99b3", "packages": [ { "name": "predis/predis", @@ -67,6 +67,487 @@ ], "time": "2023-09-13T16:42:03+00:00" }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "0424dff1c58f028c451efff2045f5d92410bd540" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", + "reference": "0424dff1c58f028c451efff2045f5d92410bd540", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T15:07:36+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a", + "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T15:07:36+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T15:07:36+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-06-19T12:30:46+00:00" + }, + { + "name": "symfony/string", + "version": "v6.0.19", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "d9e72497367c23e08bf94176d2be45b00a9d232a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/d9e72497367c23e08bf94176d2be45b00a9d232a", + "reference": "d9e72497367c23e08bf94176d2be45b00a9d232a", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.0" + }, + "require-dev": { + "symfony/error-handler": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", + "symfony/translation-contracts": "^2.0|^3.0", + "symfony/var-exporter": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v6.0.19" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-01T08:36:10+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v2.5.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "b0073a77ac0b7ea55131020e87b1e3af540f4664" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/b0073a77ac0b7ea55131020e87b1e3af540f4664", + "reference": "b0073a77ac0b7ea55131020e87b1e3af540f4664", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "suggest": { + "symfony/translation-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v2.5.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-23T13:51:25+00:00" + }, { "name": "teamtnt/tntsearch", "version": "v4.3.0", @@ -2569,251 +3050,18 @@ }, { "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "https://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-02T06:33:00+00:00" - }, - { - "name": "sebastian/global-state", - "version": "5.0.7", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", - "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" - }, - "require-dev": { - "ext-dom": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-uopz": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-02T06:35:11+00:00" - }, - { - "name": "sebastian/lines-of-code", - "version": "1.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", - "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for counting the lines of code in PHP source code", - "homepage": "https://github.com/sebastianbergmann/lines-of-code", - "support": { - "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-12-22T06:20:34+00:00" - }, - { - "name": "sebastian/object-enumerator", - "version": "4.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:12:34+00:00" - }, - { - "name": "sebastian/object-reflector", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "bschussek@gmail.com" } ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], "support": { - "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" }, "funding": [ { @@ -2821,32 +3069,38 @@ "type": "github" } ], - "time": "2020-10-26T13:14:26+00:00" + "time": "2024-03-02T06:33:00+00:00" }, { - "name": "sebastian/recursion-context", - "version": "4.0.5", + "name": "sebastian/global-state", + "version": "5.0.7", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" }, "require-dev": { + "ext-dom": "*", "phpunit/phpunit": "^9.3" }, + "suggest": { + "ext-uopz": "*" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -2862,21 +3116,16 @@ { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" } ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "https://github.com/sebastianbergmann/recursion-context", + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], "support": { - "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" }, "funding": [ { @@ -2884,32 +3133,33 @@ "type": "github" } ], - "time": "2023-02-03T06:07:39+00:00" + "time": "2024-03-02T06:35:11+00:00" }, { - "name": "sebastian/resource-operations", - "version": "3.0.4", + "name": "sebastian/lines-of-code", + "version": "1.0.4", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", - "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", "shasum": "" }, "require": { + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-master": "1.0-dev" } }, "autoload": { @@ -2924,13 +3174,15 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" }, "funding": [ { @@ -2938,32 +3190,34 @@ "type": "github" } ], - "time": "2024-03-14T16:00:52+00:00" + "time": "2023-12-22T06:20:34+00:00" }, { - "name": "sebastian/type", - "version": "3.2.1", + "name": "sebastian/object-enumerator", + "version": "4.0.4", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -2978,15 +3232,14 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "email": "sebastian@phpunit.de" } ], - "description": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { - "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" }, "funding": [ { @@ -2994,29 +3247,32 @@ "type": "github" } ], - "time": "2023-02-03T06:13:03+00:00" + "time": "2020-10-26T13:12:34+00:00" }, { - "name": "sebastian/version", - "version": "3.0.2", + "name": "sebastian/object-reflector", + "version": "2.0.4", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c6c1022351a901512170118436c764e473f6de8c" + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", - "reference": "c6c1022351a901512170118436c764e473f6de8c", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", "shasum": "" }, "require": { "php": ">=7.3" }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -3031,15 +3287,14 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "email": "sebastian@phpunit.de" } ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" }, "funding": [ { @@ -3047,386 +3302,273 @@ "type": "github" } ], - "time": "2020-09-28T06:39:44+00:00" + "time": "2020-10-26T13:14:26+00:00" }, { - "name": "symfony/config", - "version": "v5.4.40", + "name": "sebastian/recursion-context", + "version": "4.0.5", "source": { "type": "git", - "url": "https://github.com/symfony/config.git", - "reference": "d4e1db78421163b98dd9971d247fd0df4a57ee5e" + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/d4e1db78421163b98dd9971d247fd0df4a57ee5e", - "reference": "d4e1db78421163b98dd9971d247fd0df4a57ee5e", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/filesystem": "^4.4|^5.0|^6.0", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-php80": "^1.16", - "symfony/polyfill-php81": "^1.22" - }, - "conflict": { - "symfony/finder": "<4.4" + "php": ">=7.3" }, "require-dev": { - "symfony/event-dispatcher": "^4.4|^5.0|^6.0", - "symfony/finder": "^4.4|^5.0|^6.0", - "symfony/messenger": "^4.4|^5.0|^6.0", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/yaml": "^4.4|^5.0|^6.0" - }, - "suggest": { - "symfony/yaml": "To use the yaml reference dumper" + "phpunit/phpunit": "^9.3" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, "autoload": { - "psr-4": { - "Symfony\\Component\\Config\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" } ], - "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", - "homepage": "https://symfony.com", + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { - "source": "https://github.com/symfony/config/tree/v5.4.40" + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2024-05-31T14:33:22+00:00" + "time": "2023-02-03T06:07:39+00:00" }, { - "name": "symfony/console", - "version": "v5.4.41", + "name": "sebastian/resource-operations", + "version": "3.0.4", "source": { "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "6473d441a913cb997123b59ff2dbe3d1cf9e11ba" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/6473d441a913cb997123b59ff2dbe3d1cf9e11ba", - "reference": "6473d441a913cb997123b59ff2dbe3d1cf9e11ba", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/string": "^5.1|^6.0" - }, - "conflict": { - "psr/log": ">=3", - "symfony/dependency-injection": "<4.4", - "symfony/dotenv": "<5.1", - "symfony/event-dispatcher": "<4.4", - "symfony/lock": "<4.4", - "symfony/process": "<4.4" + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" }, - "provide": { - "psr/log-implementation": "1.0|2.0" + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "shasum": "" }, - "require-dev": { - "psr/log": "^1|^2", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/event-dispatcher": "^4.4|^5.0|^6.0", - "symfony/lock": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0" + "require": { + "php": ">=7.3" }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" + "require-dev": { + "phpunit/phpunit": "^9.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, "autoload": { - "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" } ], - "description": "Eases the creation of beautiful and testable command line interfaces", - "homepage": "https://symfony.com", - "keywords": [ - "cli", - "command-line", - "console", - "terminal" - ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "support": { - "source": "https://github.com/symfony/console/tree/v5.4.41" + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2024-06-28T07:48:55+00:00" + "time": "2024-03-14T16:00:52+00:00" }, { - "name": "symfony/dependency-injection", - "version": "v6.0.20", + "name": "sebastian/type", + "version": "3.2.1", "source": { "type": "git", - "url": "https://github.com/symfony/dependency-injection.git", - "reference": "359806e1adebd1c43e18e5ea22acd14bef7fcf8c" + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/359806e1adebd1c43e18e5ea22acd14bef7fcf8c", - "reference": "359806e1adebd1c43e18e5ea22acd14bef7fcf8c", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", "shasum": "" }, "require": { - "php": ">=8.0.2", - "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php81": "^1.22", - "symfony/service-contracts": "^1.1.6|^2.0|^3.0" - }, - "conflict": { - "ext-psr": "<1.1|>=2", - "symfony/config": "<5.4", - "symfony/finder": "<5.4", - "symfony/proxy-manager-bridge": "<5.4", - "symfony/yaml": "<5.4" - }, - "provide": { - "psr/container-implementation": "1.1|2.0", - "symfony/service-implementation": "1.1|2.0|3.0" + "php": ">=7.3" }, "require-dev": { - "symfony/config": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/yaml": "^5.4|^6.0" - }, - "suggest": { - "symfony/config": "", - "symfony/expression-language": "For using expressions in service container configuration", - "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", - "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", - "symfony/yaml": "" + "phpunit/phpunit": "^9.5" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, "autoload": { - "psr-4": { - "Symfony\\Component\\DependencyInjection\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Allows you to standardize and centralize the way objects are constructed in your application", - "homepage": "https://symfony.com", + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v6.0.20" + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2023-01-30T15:41:07+00:00" + "time": "2023-02-03T06:13:03+00:00" }, { - "name": "symfony/deprecation-contracts", - "version": "v3.0.2", + "name": "sebastian/version", + "version": "3.0.2", "source": { "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", "shasum": "" }, "require": { - "php": ">=8.0.2" + "php": ">=7.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "dev-master": "3.0-dev" } }, "autoload": { - "files": [ - "function.php" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.2" + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2022-01-02T09:55:41+00:00" + "time": "2020-09-28T06:39:44+00:00" }, { - "name": "symfony/event-dispatcher", + "name": "symfony/config", "version": "v5.4.40", "source": { "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "a54e2a8a114065f31020d6a89ede83e34c3b27a4" + "url": "https://github.com/symfony/config.git", + "reference": "d4e1db78421163b98dd9971d247fd0df4a57ee5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a54e2a8a114065f31020d6a89ede83e34c3b27a4", - "reference": "a54e2a8a114065f31020d6a89ede83e34c3b27a4", + "url": "https://api.github.com/repos/symfony/config/zipball/d4e1db78421163b98dd9971d247fd0df4a57ee5e", + "reference": "d4e1db78421163b98dd9971d247fd0df4a57ee5e", "shasum": "" }, "require": { "php": ">=7.2.5", "symfony/deprecation-contracts": "^2.1|^3", - "symfony/event-dispatcher-contracts": "^2|^3", - "symfony/polyfill-php80": "^1.16" + "symfony/filesystem": "^4.4|^5.0|^6.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php80": "^1.16", + "symfony/polyfill-php81": "^1.22" }, "conflict": { - "symfony/dependency-injection": "<4.4" - }, - "provide": { - "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "2.0" + "symfony/finder": "<4.4" }, "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/messenger": "^4.4|^5.0|^6.0", "symfony/service-contracts": "^1.1|^2|^3", - "symfony/stopwatch": "^4.4|^5.0|^6.0" + "symfony/yaml": "^4.4|^5.0|^6.0" }, "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" + "symfony/yaml": "To use the yaml reference dumper" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" + "Symfony\\Component\\Config\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -3446,10 +3588,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.40" + "source": "https://github.com/symfony/config/tree/v5.4.40" }, "funding": [ { @@ -3468,40 +3610,62 @@ "time": "2024-05-31T14:33:22+00:00" }, { - "name": "symfony/event-dispatcher-contracts", - "version": "v3.0.2", + "name": "symfony/console", + "version": "v5.4.41", "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "7bc61cc2db649b4637d331240c5346dcc7708051" + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "6473d441a913cb997123b59ff2dbe3d1cf9e11ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7bc61cc2db649b4637d331240c5346dcc7708051", - "reference": "7bc61cc2db649b4637d331240c5346dcc7708051", + "url": "https://api.github.com/repos/symfony/console/zipball/6473d441a913cb997123b59ff2dbe3d1cf9e11ba", + "reference": "6473d441a913cb997123b59ff2dbe3d1cf9e11ba", "shasum": "" }, "require": { - "php": ">=8.0.2", - "psr/event-dispatcher": "^1" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.1|^6.0" + }, + "conflict": { + "psr/log": ">=3", + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0" + }, + "require-dev": { + "psr/log": "^1|^2", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" }, "suggest": { - "symfony/event-dispatcher-implementation": "" + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, "autoload": { "psr-4": { - "Symfony\\Contracts\\EventDispatcher\\": "" - } + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3509,26 +3673,24 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Generic abstractions related to dispatching event", + "description": "Eases the creation of beautiful and testable command line interfaces", "homepage": "https://symfony.com", "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" + "cli", + "command-line", + "console", + "terminal" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.0.2" + "source": "https://github.com/symfony/console/tree/v5.4.41" }, "funding": [ { @@ -3544,35 +3706,56 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:55:41+00:00" + "time": "2024-06-28T07:48:55+00:00" }, { - "name": "symfony/filesystem", - "version": "v5.4.41", + "name": "symfony/dependency-injection", + "version": "v6.0.20", "source": { "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "6d29dd9340b372fa603f04e6df4dd76bb808591e" + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "359806e1adebd1c43e18e5ea22acd14bef7fcf8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/6d29dd9340b372fa603f04e6df4dd76bb808591e", - "reference": "6d29dd9340b372fa603f04e6df4dd76bb808591e", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/359806e1adebd1c43e18e5ea22acd14bef7fcf8c", + "reference": "359806e1adebd1c43e18e5ea22acd14bef7fcf8c", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.0.2", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php81": "^1.22", + "symfony/service-contracts": "^1.1.6|^2.0|^3.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2", + "symfony/config": "<5.4", + "symfony/finder": "<5.4", + "symfony/proxy-manager-bridge": "<5.4", + "symfony/yaml": "<5.4" + }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "symfony/service-implementation": "1.1|2.0|3.0" }, "require-dev": { - "symfony/process": "^5.4|^6.4" + "symfony/config": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0" + }, + "suggest": { + "symfony/config": "", + "symfony/expression-language": "For using expressions in service container configuration", + "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", + "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", + "symfony/yaml": "" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Filesystem\\": "" + "Symfony\\Component\\DependencyInjection\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -3592,10 +3775,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides basic utilities for the filesystem", + "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.4.41" + "source": "https://github.com/symfony/dependency-injection/tree/v6.0.20" }, "funding": [ { @@ -3611,45 +3794,39 @@ "type": "tidelift" } ], - "time": "2024-06-28T09:36:24+00:00" + "time": "2023-01-30T15:41:07+00:00" }, { - "name": "symfony/polyfill-ctype", - "version": "v1.30.0", + "name": "symfony/deprecation-contracts", + "version": "v3.0.2", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540" + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", + "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", "shasum": "" }, "require": { - "php": ">=7.1" - }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" + "php": ">=8.0.2" }, "type": "library", "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" } }, "autoload": { "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } + "function.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3657,24 +3834,18 @@ ], "authors": [ { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for ctype functions", + "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.2" }, "funding": [ { @@ -3690,42 +3861,57 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2022-01-02T09:55:41+00:00" }, { - "name": "symfony/polyfill-intl-grapheme", - "version": "v1.30.0", + "name": "symfony/event-dispatcher", + "version": "v5.4.40", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "a54e2a8a114065f31020d6a89ede83e34c3b27a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a", - "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a54e2a8a114065f31020d6a89ede83e34c3b27a4", + "reference": "a54e2a8a114065f31020d6a89ede83e34c3b27a4", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/event-dispatcher-contracts": "^2|^3", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "symfony/dependency-injection": "<4.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/http-foundation": "^4.4|^5.0|^6.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/stopwatch": "^4.4|^5.0|^6.0" }, "suggest": { - "ext-intl": "For best performance" + "symfony/dependency-injection": "", + "symfony/http-kernel": "" }, "type": "library", - "extra": { - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - } + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3733,26 +3919,18 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's grapheme_* functions", + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "grapheme", - "intl", - "polyfill", - "portable", - "shim" - ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.40" }, "funding": [ { @@ -3768,45 +3946,43 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-05-31T14:33:22+00:00" }, { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.30.0", + "name": "symfony/event-dispatcher-contracts", + "version": "v3.0.2", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "7bc61cc2db649b4637d331240c5346dcc7708051" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", - "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7bc61cc2db649b4637d331240c5346dcc7708051", + "reference": "7bc61cc2db649b4637d331240c5346dcc7708051", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=8.0.2", + "psr/event-dispatcher": "^1" }, "suggest": { - "ext-intl": "For best performance" + "symfony/event-dispatcher-implementation": "" }, "type": "library", "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" } }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, - "classmap": [ - "Resources/stubs" - ] + "Symfony\\Contracts\\EventDispatcher\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3822,18 +3998,18 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", + "description": "Generic abstractions related to dispatching event", "homepage": "https://symfony.com", "keywords": [ - "compatibility", - "intl", - "normalizer", - "polyfill", - "portable", - "shim" + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.0.2" }, "funding": [ { @@ -3849,45 +4025,39 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2022-01-02T09:55:41+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.30.0", + "name": "symfony/filesystem", + "version": "v5.4.41", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" + "url": "https://github.com/symfony/filesystem.git", + "reference": "6d29dd9340b372fa603f04e6df4dd76bb808591e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/6d29dd9340b372fa603f04e6df4dd76bb808591e", + "reference": "6d29dd9340b372fa603f04e6df4dd76bb808591e", "shasum": "" }, "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8", + "symfony/polyfill-php80": "^1.16" }, - "suggest": { - "ext-mbstring": "For best performance" + "require-dev": { + "symfony/process": "^5.4|^6.4" }, "type": "library", - "extra": { - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3895,25 +4065,18 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for the Mbstring extension", + "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" + "source": "https://github.com/symfony/filesystem/tree/v5.4.41" }, "funding": [ { @@ -3929,7 +4092,7 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:30:46+00:00" + "time": "2024-06-28T09:36:24+00:00" }, { "name": "symfony/polyfill-php73", @@ -4245,92 +4408,6 @@ ], "time": "2022-05-30T19:17:58+00:00" }, - { - "name": "symfony/string", - "version": "v5.4.41", - "source": { - "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "065a9611e0b1fd2197a867e1fb7f2238191b7096" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/065a9611e0b1fd2197a867e1fb7f2238191b7096", - "reference": "065a9611e0b1fd2197a867e1fb7f2238191b7096", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "~1.15" - }, - "conflict": { - "symfony/translation-contracts": ">=3.0" - }, - "require-dev": { - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/http-client": "^4.4|^5.0|^6.0", - "symfony/translation-contracts": "^1.1|^2", - "symfony/var-exporter": "^4.4|^5.0|^6.0" - }, - "type": "library", - "autoload": { - "files": [ - "Resources/functions.php" - ], - "psr-4": { - "Symfony\\Component\\String\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", - "homepage": "https://symfony.com", - "keywords": [ - "grapheme", - "i18n", - "string", - "unicode", - "utf-8", - "utf8" - ], - "support": { - "source": "https://github.com/symfony/string/tree/v5.4.41" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-06-28T09:20:55+00:00" - }, { "name": "symfony/translation", "version": "v5.4.40", @@ -4428,84 +4505,6 @@ ], "time": "2024-05-31T14:33:22+00:00" }, - { - "name": "symfony/translation-contracts", - "version": "v2.5.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/translation-contracts.git", - "reference": "b0073a77ac0b7ea55131020e87b1e3af540f4664" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/b0073a77ac0b7ea55131020e87b1e3af540f4664", - "reference": "b0073a77ac0b7ea55131020e87b1e3af540f4664", - "shasum": "" - }, - "require": { - "php": ">=7.2.5" - }, - "suggest": { - "symfony/translation-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Translation\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to translation", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v2.5.3" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-01-23T13:51:25+00:00" - }, { "name": "symfony/yaml", "version": "v5.4.40", diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 27650ab3a..cc100b356 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -51,6 +51,8 @@ use OCP\Share\Events\ShareDeletedEvent; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; +use Symfony\Component\String\Slugger\AsciiSlugger; +use Symfony\Component\String\Slugger\SluggerInterface; class Application extends App implements IBootstrap { public const APP_NAME = 'collectives'; @@ -128,6 +130,10 @@ public function register(IRegistrationContext $context): void { /** @psalm-suppress MissingDependency */ $context->registerSetupCheck(CirclesAppIsEnableCheck::class); } + + $context->registerService(SluggerInterface::class, function (ContainerInterface $c) { + return new AsciiSlugger(); + }); } public function boot(IBootcontext $context): void { diff --git a/lib/Command/CreateCollective.php b/lib/Command/CreateCollective.php index 4e058ad90..48c491541 100644 --- a/lib/Command/CreateCollective.php +++ b/lib/Command/CreateCollective.php @@ -10,7 +10,6 @@ namespace OCA\Collectives\Command; use OC\Core\Command\Base; -use OCA\Collectives\Fs\NodeHelper; use OCA\Collectives\Service\CollectiveService; use OCP\IUserManager; use OCP\IUserSession; @@ -22,7 +21,6 @@ class CreateCollective extends Base { public function __construct(private CollectiveService $collectiveService, - private NodeHelper $nodeHelper, private IUserManager $userManager, private IUserSession $userSession, private IFactory $l10nFactory) { @@ -50,11 +48,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int $user = $this->userManager->get($userId); $this->userSession->setUser($user); $lang = $this->l10nFactory->getUserLanguage($this->userSession->getUser()); - $safeName = $this->nodeHelper->sanitiseFilename($name); $output->write('Creating new collective ' . $name . ' ... '); - [$collective, $info] = $this->collectiveService->createCollective($userId, $lang, $safeName); + [, $info] = $this->collectiveService->createCollective($userId, $lang, $name); $output->writeln('' . $info ?: 'done.' . ''); return 0; diff --git a/lib/Controller/CollectiveController.php b/lib/Controller/CollectiveController.php index 1d9766d46..63e359eb9 100644 --- a/lib/Controller/CollectiveController.php +++ b/lib/Controller/CollectiveController.php @@ -10,9 +10,7 @@ namespace OCA\Collectives\Controller; use Closure; - use OCA\Collectives\Db\Collective; -use OCA\Collectives\Fs\NodeHelper; use OCA\Collectives\Service\CollectiveService; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\Attribute\NoAdminRequired; @@ -31,8 +29,7 @@ public function __construct(string $AppName, private CollectiveService $service, private IUserSession $userSession, private IFactory $l10nFactory, - private LoggerInterface $logger, - private NodeHelper $nodeHelper) { + private LoggerInterface $logger, ) { parent::__construct($AppName, $request); } @@ -58,11 +55,10 @@ public function index(): DataResponse { #[NoAdminRequired] public function create(string $name, ?string $emoji = null): DataResponse { return $this->prepareResponse(function () use ($name, $emoji): array { - $safeName = $this->nodeHelper->sanitiseFilename($name); [$collective, $info] = $this->service->createCollective( $this->getUserId(), $this->getUserLang(), - $safeName, + $name, $emoji, ); return [ diff --git a/lib/Db/Collective.php b/lib/Db/Collective.php index c906fb0ca..227a79e42 100644 --- a/lib/Db/Collective.php +++ b/lib/Db/Collective.php @@ -17,9 +17,10 @@ use RuntimeException; /** - * Class Collective * @method int getId() * @method void setId(int $value) + * @method string getSlug() + * @method void setSlug(?string $value) * @method string getCircleUniqueId() * @method void setCircleUniqueId(string $circleUniqueId) * @method int getPermissions() @@ -59,6 +60,7 @@ class Collective extends Entity implements JsonSerializable { protected ?string $circleUniqueId = null; protected int $permissions = self::defaultPermissions; + protected ?string $slug = null; protected ?string $emoji = null; protected ?int $trashTimestamp = null; protected int $pageMode = self::defaultPageMode; @@ -253,6 +255,7 @@ public function canShare(): bool { public function jsonSerialize(): array { return [ 'id' => $this->id, + 'slug' => $this->slug, 'circleId' => $this->circleUniqueId, 'emoji' => $this->emoji, 'trashTimestamp' => $this->trashTimestamp, diff --git a/lib/Db/Page.php b/lib/Db/Page.php index b0279912f..d3c169b0f 100644 --- a/lib/Db/Page.php +++ b/lib/Db/Page.php @@ -18,6 +18,8 @@ * @method void setId(int $value) * @method int getFileId() * @method void setFileId(int $value) + * @method string getSlug() + * @method void setSlug(?string $value) * @method string getLastUserId() * @method void setLastUserId(string $value) * @method string getEmoji() @@ -31,6 +33,7 @@ */ class Page extends Entity implements JsonSerializable { protected ?int $fileId = null; + protected ?string $slug = null; protected ?string $lastUserId = null; protected ?string $emoji = null; protected ?string $subpageOrder = null; diff --git a/lib/Migration/Version021200Date20240820000000.php b/lib/Migration/Version021200Date20240820000000.php new file mode 100644 index 000000000..46ea40560 --- /dev/null +++ b/lib/Migration/Version021200Date20240820000000.php @@ -0,0 +1,36 @@ +getTable('collectives'); + if (!$table->hasColumn('slug')) { + $table->addColumn('slug', Types::STRING, [ + 'notnull' => false, + 'default' => false, + 'length' => 255, + ]); + + return $schema; + } + + return null; + } +} diff --git a/lib/Migration/Version021200Date20240820000001.php b/lib/Migration/Version021200Date20240820000001.php new file mode 100644 index 000000000..602d9793b --- /dev/null +++ b/lib/Migration/Version021200Date20240820000001.php @@ -0,0 +1,36 @@ +getTable('collectives_pages'); + if (!$table->hasColumn('slug')) { + $table->addColumn('slug', Types::STRING, [ + 'notnull' => false, + 'default' => false, + 'length' => 255, + ]); + + return $schema; + } + + return null; + } +} diff --git a/lib/Model/PageInfo.php b/lib/Model/PageInfo.php index a4b2cfeee..044d41372 100644 --- a/lib/Model/PageInfo.php +++ b/lib/Model/PageInfo.php @@ -21,6 +21,7 @@ class PageInfo implements JsonSerializable { public const SUFFIX = '.md'; private int $id; + private ?string $slug = null; private ?string $lastUserId = null; private ?string $lastUserDisplayName = null; private ?string $emoji = null; @@ -44,6 +45,14 @@ public function setId(int $id): void { $this->id = $id; } + public function getSlug(): ?string { + return $this->slug; + } + + public function setSlug(?string $slug): void { + $this->slug = $slug; + } + public function getLastUserId(): ?string { return $this->lastUserId; } @@ -159,6 +168,7 @@ public function setShareToken(string $shareToken): void { public function jsonSerialize(): array { return [ 'id' => $this->id, + 'slug' => $this->slug, 'lastUserId' => $this->lastUserId, 'lastUserDisplayName' => $this->lastUserDisplayName, 'emoji' => $this->emoji, @@ -180,7 +190,7 @@ public function jsonSerialize(): array { * @throws InvalidPathException * @throws NotFoundException */ - public function fromFile(File $file, int $parentId, ?string $lastUserId = null, ?string $lastUserDisplayName = null, ?string $emoji = null, ?string $subpageOrder = null, bool $fullWidth = false): void { + public function fromFile(File $file, int $parentId, ?string $lastUserId = null, ?string $lastUserDisplayName = null, ?string $emoji = null, ?string $subpageOrder = null, bool $fullWidth = false, ?string $slug = null): void { $this->setId($file->getId()); // Set folder name as title for all index pages except the collective landing page $dirName = dirname($file->getInternalPath()); @@ -213,6 +223,9 @@ public function fromFile(File $file, int $parentId, ?string $lastUserId = null, if ($subpageOrder !== null) { $this->setSubpageOrder($subpageOrder); } + if ($slug !== null) { + $this->setSlug($slug); + } $this->setParentId($parentId); } } diff --git a/lib/Service/CollectiveService.php b/lib/Service/CollectiveService.php index 1fdc9a1f7..1b9d7e7cc 100644 --- a/lib/Service/CollectiveService.php +++ b/lib/Service/CollectiveService.php @@ -17,6 +17,7 @@ use OCA\Collectives\Db\CollectiveUserSettingsMapper; use OCA\Collectives\Db\Page; use OCA\Collectives\Db\PageMapper; +use OCA\Collectives\Fs\NodeHelper; use OCA\Collectives\Model\PageInfo; use OCA\Collectives\Mount\CollectiveFolderManager; use OCA\Collectives\Trash\PageTrashBackend; @@ -28,6 +29,7 @@ use OCP\Files\NotFoundException as FilesNotFoundException; use OCP\Files\NotPermittedException as FilesNotPermittedException; use OCP\IL10N; +use Symfony\Component\String\Slugger\SluggerInterface; class CollectiveService extends CollectiveServiceBase { private ?PageTrashBackend $pageTrashBackend = null; @@ -43,7 +45,9 @@ public function __construct( private CollectiveUserSettingsMapper $collectiveUserSettingsMapper, private PageMapper $pageMapper, private IL10N $l10n, - private IEventDispatcher $eventDispatcher) { + private IEventDispatcher $eventDispatcher, + private NodeHelper $nodeHelper, + private SluggerInterface $slugger, ) { parent::__construct($collectiveMapper, $circleHelper); } @@ -168,8 +172,10 @@ public function getCollectiveNameWithEmoji(Collective $collective): string { */ public function createCollective(string $userId, string $userLang, - string $safeName, + string $name, ?string $emoji = null): array { + $safeName = $this->nodeHelper->sanitiseFilename($name); + if ($safeName === '') { throw new UnprocessableEntityException('Empty collective name is not allowed'); } @@ -201,7 +207,7 @@ public function createCollective(string $userId, $this->eventDispatcher->dispatchTyped(new InvalidateMountCacheEvent(null)); } - // Create collective object + // Create a collective object $collective = new Collective(); $collective->setCircleId($circle->getSingleId()); $collective->setPermissions(Collective::defaultPermissions); @@ -210,7 +216,11 @@ public function createCollective(string $userId, } $collective = $this->collectiveMapper->insert($collective); - // Decorate collective object + $slug = $this->slugger->slug($name, locale: $userLang)->toString().'-'.$collective->getId(); + $collective->setSlug($slug); + $this->collectiveMapper->update($collective); + + // Decorate a collective object $collective->setName($circle->getSanitizedName()); $collective->setLevel($this->circleHelper->getLevel($circle->getSingleId(), $userId)); @@ -247,6 +257,7 @@ public function createCollective(string $userId, public function updateCollective(int $id, string $userId, ?string $emoji = null): Collective { + //fixme: should we update slug? $collective = $this->getCollective($id, $userId); if (!$this->circleHelper->isAdmin($collective->getCircleId(), $userId)) { diff --git a/lib/Service/PageService.php b/lib/Service/PageService.php index 343f559c6..d55ac0627 100644 --- a/lib/Service/PageService.php +++ b/lib/Service/PageService.php @@ -31,6 +31,7 @@ use OCP\IUserManager; use OCP\Lock\LockedException; use Psr\Container\ContainerInterface; +use Symfony\Component\String\Slugger\SluggerInterface; class PageService { private const DEFAULT_PAGE_TITLE = 'New Page'; @@ -47,7 +48,8 @@ public function __construct(private IAppManager $appManager, private IUserManager $userManager, private IConfig $config, ContainerInterface $container, - private SessionService $sessionService) { + private SessionService $sessionService, + private SluggerInterface $slugger, ) { try { $this->pushQueue = $container->get(IQueue::class); } catch (Exception) { @@ -198,7 +200,8 @@ private function getPageByFile(File $file, ?Node $parent = null): PageInfo { $lastUserId = ($page !== null) ? $page->getLastUserId() : null; $emoji = ($page !== null) ? $page->getEmoji() : null; $subpageOrder = ($page !== null) ? $page->getSubpageOrder() : null; - $fullWidth = $page !== null && $page->getFullWidth(); + $fullWidth = ($page !== null) ? $page->getFullWidth() : false; + $slug = ($page !== null) ? $page->getSlug() : null; $pageInfo = new PageInfo(); try { $pageInfo->fromFile($file, @@ -207,7 +210,8 @@ private function getPageByFile(File $file, ?Node $parent = null): PageInfo { $lastUserId ? $this->userManager->getDisplayName($lastUserId) : null, $emoji, $subpageOrder, - $fullWidth); + $fullWidth, + $slug); } catch (FilesNotFoundException|InvalidPathException $e) { throw new NotFoundException($e->getMessage(), 0, $e); } @@ -228,6 +232,7 @@ private function getTrashPageByFile(File $file, string $filename, string $timest $emoji = ($page !== null) ? $page->getEmoji() : null; $subpageOrder = ($page !== null) ? $page->getSubpageOrder() : null; $trashTimestamp = ($page !== null) ? $page->getTrashTimestamp(): (int)$timestamp; + $slug = ($page !== null) ? $page->getSlug() : null; $pageInfo = new PageInfo(); try { $pageInfo->fromFile($file, @@ -236,7 +241,8 @@ private function getTrashPageByFile(File $file, string $filename, string $timest $lastUserId ? $this->userManager->getDisplayName($lastUserId) : null, $emoji, $subpageOrder, - $page && $page->getFullWidth()); + $page->getFullWidth(), + $slug); $pageInfo->setTrashTimestamp($trashTimestamp); $pageInfo->setFilePath(''); $pageInfo->setTitle(basename($filename, PageInfo::SUFFIX)); @@ -261,7 +267,7 @@ private function notifyPush(int $collectiveId): void { } } - private function updatePage(int $collectiveId, int $fileId, string $userId, ?string $emoji = null, ?bool $fullWidth = null): void { + private function updatePage(int $collectiveId, int $fileId, string $userId, ?string $emoji = null, ?bool $fullWidth = null, ?string $slug = null): void { $page = new Page(); $page->setFileId($fileId); $page->setLastUserId($userId); @@ -271,6 +277,9 @@ private function updatePage(int $collectiveId, int $fileId, string $userId, ?str if ($fullWidth !== null) { $page->setFullWidth($fullWidth); } + if ($slug !== null) { + $page->setSlug($slug); + } $this->pageMapper->updateOrInsert($page); $this->notifyPush($collectiveId); } @@ -294,7 +303,7 @@ private function updateSubpageOrder(int $collectiveId, int $fileId, string $user * @throws NotFoundException * @throws NotPermittedException */ - private function newPage(int $collectiveId, Folder $folder, string $filename, string $userId): PageInfo { + private function newPage(int $collectiveId, Folder $folder, string $filename, string $userId, ?string $title): PageInfo { $hasTemplate = NodeHelper::folderHasSubPage($folder, PageInfo::TEMPLATE_PAGE_TITLE); try { if ($hasTemplate === 1) { @@ -323,7 +332,9 @@ private function newPage(int $collectiveId, Folder $folder, string $filename, st $this->getParentPageId($newFile), $userId, $this->userManager->getDisplayName($userId)); - $this->updatePage($collectiveId, $newFile->getId(), $userId); + $slug = $title ? $this->generateSlugForPage($title, $newFile) : null; + $this->updatePage($collectiveId, $newFile->getId(), $userId, slug: $slug); + $pageInfo->setSlug($slug); } catch (FilesNotFoundException|InvalidPathException $e) { throw new NotFoundException($e->getMessage(), 0, $e); } @@ -409,7 +420,7 @@ public function getPagesFromFolder(int $collectiveId, Folder $folder, string $us if (count($pageInfos) === 0) { return []; } - $indexPage = $this->newPage($collectiveId, $folder, PageInfo::INDEX_PAGE_TITLE, $userId); + $indexPage = $this->newPage($collectiveId, $folder, PageInfo::INDEX_PAGE_TITLE, $userId, null); } return array_merge([$indexPage], $pageInfos); @@ -596,7 +607,7 @@ public function create(int $collectiveId, int $parentId, string $title, string $ $safeTitle = $this->nodeHelper->sanitiseFilename($title, self::DEFAULT_PAGE_TITLE); $filename = NodeHelper::generateFilename($folder, $safeTitle, PageInfo::SUFFIX); - $pageInfo = $this->newPage($collectiveId, $folder, $filename, $userId); + $pageInfo = $this->newPage($collectiveId, $folder, $filename, $userId, $title); $this->addToSubpageOrder($collectiveId, $parentId, $pageInfo->getId(), 0, $userId); return $pageInfo; } @@ -738,8 +749,9 @@ public function copy(int $collectiveId, int $id, ?int $parentId, ?string $title, if (null !== $newFile = $this->moveOrCopyPage($collectiveFolder, $file, $parentId, $title, true)) { $file = $newFile; } + $slug = $title ? $this->generateSlugForPage($title, $file) : $this->generateSlugForPage($page->getTitle(), $file); try { - $this->updatePage($collectiveId, $file->getId(), $userId, $page->getEmoji()); + $this->updatePage($collectiveId, $file->getId(), $userId, $page->getEmoji(), slug: $slug); } catch (InvalidPathException|FilesNotFoundException $e) { throw new NotFoundException($e->getMessage(), 0, $e); } @@ -765,9 +777,10 @@ public function move(int $collectiveId, int $id, ?int $parentId, ?string $title, if (null !== $newFile = $this->moveOrCopyPage($collectiveFolder, $file, $parentId, $title, false)) { $file = $newFile; } + $slug = $title ? $this->generateSlugForPage($title, $file) : null; try { - $this->updatePage($collectiveId, $file->getId(), $userId); + $this->updatePage($collectiveId, $file->getId(), $userId, slug: $slug); } catch (InvalidPathException|FilesNotFoundException $e) { throw new NotFoundException($e->getMessage(), 0, $e); } @@ -1077,4 +1090,12 @@ public function getBacklinks(int $collectiveId, int $id, string $userId): array return $backlinks; } + + private function generateSlugForPage(string $title, ?File $file): ?string { + if (!$file) { + return null; + } + + return $this->slugger->slug($title)->toString() . '-' . $file->getId(); + } } diff --git a/src/Collectives.vue b/src/Collectives.vue index d65b06b15..c2cf33d94 100644 --- a/src/Collectives.vue +++ b/src/Collectives.vue @@ -71,7 +71,9 @@ export default { $route: { handler(val) { this.rootStore.collectiveParam = val.params.collective + this.rootStore.collectiveId = val.params.collectiveId ? parseInt(val.params.collectiveId) : null this.rootStore.pageParam = val.params.page + this.rootStore.pageId = val.params.pageId ? parseInt(val.params.pageId) : null this.rootStore.shareTokenParam = val.params.token this.rootStore.fileIdQuery = val.query.fileId }, diff --git a/src/components/Collective.vue b/src/components/Collective.vue index f17a4211c..ef908605e 100644 --- a/src/components/Collective.vue +++ b/src/components/Collective.vue @@ -59,8 +59,9 @@ export default { }, computed: { - ...mapState(useRootStore, ['isPublic', 'loading', 'pageParam']), + ...mapState(useRootStore, ['isPublic', 'loading', 'pageParam', 'pageId']), ...mapState(useCollectivesStore, [ + 'collectivePath', 'currentCollective', 'currentCollectiveCanEdit', 'currentCollectiveIsPageShare', @@ -69,7 +70,9 @@ export default { ...mapState(usePagesStore, [ 'currentFileIdPage', 'currentPage', + 'isIndexPage', 'pagePath', + 'pageSlugPath', ]), ...mapState(useVersionsStore, ['version']), @@ -91,6 +94,20 @@ export default { }, 'currentPage.id'() { this.selectVersion(null) + + const routerParams = this.$router.currentRoute.params + // If the current page is not the one we are supposed to be on, redirect + if (this.currentPage && !this.isIndexPage) { + const actualUrl = `${routerParams.collectiveSlugPart}-${routerParams.collectiveId}/${routerParams.pageSlugPart}-${routerParams.pageId}` + const expectedUrl = this.pageSlugPath(this.currentPage) + + if (actualUrl !== expectedUrl) { + this.$router.replace(this.pagePath(this.currentPage)) + } + } else if (this.currentCollective + && `${routerParams.collectiveSlugPart}-${routerParams.collectiveId}` !== this.currentCollective.slug) { + this.$router.replace(this.collectivePath(this.currentCollective)) + } }, 'notFound'(current) { if (current && this.currentFileIdPage) { diff --git a/src/components/Nav/CollectiveSettings.vue b/src/components/Nav/CollectiveSettings.vue index 72521ec57..c273f76d3 100644 --- a/src/components/Nav/CollectiveSettings.vue +++ b/src/components/Nav/CollectiveSettings.vue @@ -192,6 +192,7 @@ export default { 'collectiveParam', 'loading', 'pageParam', + 'pageId', ]), ...mapState(useCollectivesStore, ['isCollectiveOwner']), ...mapState(usePagesStore, ['pages']), @@ -334,6 +335,7 @@ export default { // Push new router path if currentCollective was renamed if (redirect) { + // fixme: adjust this.$router.push( '/' + encodeURIComponent(this.newCollectiveName) + (this.pageParam ? '/' + this.pageParam : ''), diff --git a/src/components/Page.vue b/src/components/Page.vue index 75a0b17ac..2f24648c9 100644 --- a/src/components/Page.vue +++ b/src/components/Page.vue @@ -171,6 +171,7 @@ export default { ]), ...mapState(usePagesStore, [ 'currentPage', + 'pagePath', 'isIndexPage', 'isFullWidthView', 'isTemplatePage', @@ -316,6 +317,7 @@ export default { // The resulting title may be different due to sanitizing this.newTitle = this.currentPage.title this.getPages(false) + this.$router.replace(this.pagePath(this.currentPage)) } catch (e) { console.error(e) showError(t('collectives', 'Could not rename the page')) diff --git a/src/components/PageList/SubpageList.vue b/src/components/PageList/SubpageList.vue index fbe906ba0..450c80817 100644 --- a/src/components/PageList/SubpageList.vue +++ b/src/components/PageList/SubpageList.vue @@ -80,7 +80,7 @@ export default { }, computed: { - ...mapState(useRootStore, ['pageParam']), + ...mapState(useRootStore, ['pageParam', 'pageId']), ...mapState(useCollectivesStore, ['currentCollectiveCanEdit']), ...mapState(usePagesStore, [ 'pagePath', @@ -134,6 +134,9 @@ export default { 'pageParam'() { this.initCollapsed() }, + 'pageId'() { + this.initCollapsed() + }, }, mounted() { diff --git a/src/router.js b/src/router.js index 2c6bc3616..9d90f2283 100644 --- a/src/router.js +++ b/src/router.js @@ -16,22 +16,50 @@ const routes = [ path: '/', component: Home, }, + { + path: '/_/print/:collectiveSlugPart-:collectiveId(\\d+)', + component: CollectivePrintView, + props: (route) => route.params, + }, { path: '/_/print/:collective', component: CollectivePrintView, props: (route) => route.params, }, + { + path: '/p/:token/print/:collectiveSlugPart-:collectiveId(\\d+)', + component: CollectivePrintView, + props: (route) => route.params, + }, { path: '/p/:token/print/:collective', component: CollectivePrintView, props: (route) => route.params, }, + { + path: '/p/:token/:collectiveSlugPart-:collectiveId(\\d+)', + component: CollectiveView, + props: (route) => route.params, + children: [ + { path: ':pageSlugPart+-:pageId(\\d+)' }, + { path: ':page*' }, + ], + }, { path: '/p/:token/:collective', component: CollectiveView, props: (route) => route.params, children: [{ path: ':page*' }], }, + { + path: '/:collectiveSlugPart-:collectiveId(\\d+)', + component: CollectiveView, + props: (route) => route.params, + children: [ + { path: ':pageSlugPart+-:pageId(\\d+)' }, + { path: ':page*' }, + ], + }, { path: '/:collective', component: CollectiveView, diff --git a/src/stores/collectives.js b/src/stores/collectives.js index 70d3141fc..f1f5a552e 100644 --- a/src/stores/collectives.js +++ b/src/stores/collectives.js @@ -32,6 +32,11 @@ export const useCollectivesStore = defineStore('collectives', { currentCollective(state) { const rootStore = useRootStore() + if (rootStore.collectiveId) { + return state.collectives.find( + (collective) => collective.id === rootStore.collectiveId, + ) + } return state.collectives.find( (collective) => collective.name === rootStore.collectiveParam, ) @@ -40,11 +45,11 @@ export const useCollectivesStore = defineStore('collectives', { collectivePath() { return (collective) => { const rootStore = useRootStore() + const slug = collective.slug ? collective.slug : encodeURIComponent(collective.name) if (rootStore.isPublic) { - return `/p/${rootStore.shareTokenParam}/${encodeURIComponent(collective.name)}` - } else { - return `/${encodeURIComponent(collective.name)}` + return `/p/${rootStore.shareTokenParam}/${slug}` } + return `/${slug}` } }, @@ -75,7 +80,11 @@ export const useCollectivesStore = defineStore('collectives', { updatedCollectivePath(state) { const collective = state.updatedCollective - return collective?.name && `/${encodeURIComponent(collective.name)}` + if (!collective) { + return false + } + const slug = collective.slug ? collective.slug : encodeURIComponent(collective.name) + return `/${slug}` }, collectiveChanged(state) { diff --git a/src/stores/pages.js b/src/stores/pages.js index 2d0f01f30..2e9c0a401 100644 --- a/src/stores/pages.js +++ b/src/stores/pages.js @@ -48,7 +48,7 @@ export const usePagesStore = defineStore('pages', { const collectivesStore = useCollectivesStore() return collectivesStore.currentCollectiveIsPageShare ? false - : !rootStore.pageParam || rootStore.pageParam === INDEX_PAGE + : (!rootStore.pageId && !rootStore.pageParam) || rootStore.pageParam === INDEX_PAGE }, isIndexPage: (state) => state.currentPage.fileName === INDEX_PAGE + '.md', isTemplatePage: (state) => state.currentPage.title === TEMPLATE_PAGE, @@ -67,13 +67,23 @@ export const usePagesStore = defineStore('pages', { currentPageIds(state) { const rootStore = useRootStore() // Return root page - if (!rootStore.pageParam + if ((!rootStore.pageId && !rootStore.pageParam) || rootStore.pageParam === INDEX_PAGE) { return [state.rootPage.id] } - // Iterate through all path levels to find the correct page const pageIds = [] + if (rootStore.pageId) { + let pageId = rootStore.pageId + do { + const page = state.pageById(pageId) + pageIds.unshift(page.id) + pageId = page.parentId + } while (pageId) + return pageIds + } + + // Iterate through all path levels to find the correct page const parts = rootStore.pageParam.split('/').filter(Boolean) let page = state.rootPage for (const i in parts) { @@ -97,21 +107,21 @@ export const usePagesStore = defineStore('pages', { } }, - pagePath: () => (page) => { + pagePath: (state) => (page) => { const rootStore = useRootStore() - const collectivesStore = useCollectivesStore() - const collective = collectivesStore.currentCollective.name - const { filePath, fileName, title, id } = page - const titlePart = fileName !== INDEX_PAGE + '.md' && title // For public collectives, prepend `/p/{shareToken}` - const pagePath = [ - rootStore.isPublic ? 'p' : null, - rootStore.isPublic ? rootStore.shareTokenParam : null, - collective, - ...filePath.split('/'), - titlePart, - ].filter(Boolean).map(encodeURIComponent).join('/') - return `/${pagePath}?fileId=${id}` + let prefix = '' + if (rootStore.isPublic) { + prefix = `/p/${encodeURIComponent(rootStore.shareTokenParam)}` + } + return `${prefix}/${state.pageSlugPath(page)}` + }, + + pageSlugPath: (state) => (page) => { + const collectivesStore = useCollectivesStore() + const collective = collectivesStore.currentCollective.slug || collectivesStore.currentCollective.name + const slugsPath = pageParents(state)(page.id).map(p => p.slug) + return [collective, ...slugsPath].filter(Boolean).map(encodeURIComponent).join('/') }, pagePathTitle: () => (page) => { diff --git a/src/stores/root.js b/src/stores/root.js index 8d879fbbe..3af8f5a5f 100644 --- a/src/stores/root.js +++ b/src/stores/root.js @@ -15,7 +15,9 @@ export const useRootStore = defineStore('root', { printView: false, activeSidebarTab: 'attachments', collectiveParam: '', + collectiveId: null, pageParam: '', + pageId: null, shareTokenParam: '', fileIdQuery: '', }),