├── .env ├── .github └── workflows │ └── linting.yaml ├── .gitignore ├── .php-cs-fixer.dist.php ├── LICENSE ├── README.md ├── bin └── kimai ├── box.json.dist ├── composer.json ├── composer.lock ├── phpstan.neon ├── src ├── Api │ ├── Configuration.php │ └── Connection.php ├── Application.php ├── Command │ ├── ActiveCommand.php │ ├── ActivityListCommand.php │ ├── BaseCommand.php │ ├── ConfigurationCommand.php │ ├── CustomerListCommand.php │ ├── ProjectListCommand.php │ ├── StartCommand.php │ ├── StopCommand.php │ ├── TimesheetCommandTrait.php │ └── VersionCommand.php ├── Constants.php ├── Entity │ ├── Activity.php │ ├── Customer.php │ └── Project.php └── Exception │ ├── ConnectionProblemException.php │ └── InvalidConfigurationException.php └── symfony.lock /.env: -------------------------------------------------------------------------------- 1 | # In all environments, the following files are loaded if they exist, 2 | # the latter taking precedence over the former: 3 | # 4 | # * .env contains default values for the environment variables needed by the app 5 | # * .env.local uncommitted file with local overrides 6 | # * .env.$APP_ENV committed environment-specific defaults 7 | # * .env.$APP_ENV.local uncommitted environment-specific overrides 8 | # 9 | # Real environment variables win over .env files. 10 | # 11 | # DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES. 12 | # 13 | # Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2). 14 | # https://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration 15 | -------------------------------------------------------------------------------- /.github/workflows/linting.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: null 4 | push: 5 | branches: 6 | - master 7 | jobs: 8 | tests: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | php: ['8.1', '8.2'] 13 | 14 | name: Linting - PHP ${{ matrix.php }} 15 | steps: 16 | - uses: actions/checkout@v2 17 | - uses: shivammathur/setup-php@v2 18 | with: 19 | php-version: ${{ matrix.php }} 20 | coverage: none 21 | extensions: intl 22 | - run: composer install --no-progress 23 | - run: composer codestyle 24 | - run: composer phpstan 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | .php-cs-fixer.cache 4 | vendor/ 5 | kimai.phar 6 | kimai.phar.sha1 7 | var/ 8 | -------------------------------------------------------------------------------- /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | setRiskyAllowed(true) 13 | ->setRules([ 14 | 'encoding' => true, 15 | 'full_opening_tag' => true, 16 | 'blank_line_after_namespace' => true, 17 | 'braces' => true, 18 | 'class_definition' => true, 19 | 'elseif' => true, 20 | 'function_declaration' => true, 21 | 'indentation_type' => true, 22 | 'line_ending' => true, 23 | 'constant_case' => ['case' => 'lower'], 24 | 'lowercase_keywords' => true, 25 | 'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'], 26 | 'header_comment' => ['header' => $fileHeaderComment, 'separate' => 'both'], 27 | 'no_php4_constructor' => true, 28 | 'ordered_imports' => true, 29 | 'no_break_comment' => true, 30 | 'no_closing_tag' => true, 31 | 'no_spaces_after_function_name' => true, 32 | 'no_spaces_inside_parenthesis' => true, 33 | 'no_trailing_whitespace' => true, 34 | 'no_trailing_whitespace_in_comment' => true, 35 | 'single_blank_line_at_eof' => true, 36 | 'single_class_element_per_statement' => ['elements' => ['property']], 37 | 'single_import_per_statement' => true, 38 | 'single_line_after_imports' => true, 39 | 'switch_case_semicolon_to_colon' => true, 40 | 'switch_case_space' => true, 41 | 'array_syntax' => [ 42 | 'syntax' => 'short' 43 | ], 44 | 'binary_operator_spaces' => true, 45 | 'blank_line_after_opening_tag' => true, 46 | 'blank_line_before_statement' => [ 47 | 'statements' => ['return'], 48 | ], 49 | 'cast_spaces' => true, 50 | 'class_attributes_separation' => ['elements' => ['method' => 'one']], 51 | 'concat_space' => ['spacing' => 'one'], 52 | 'declare_equal_normalize' => true, 53 | 'function_typehint_space' => true, 54 | 'include' => true, 55 | 'lowercase_cast' => true, 56 | 'lowercase_static_reference' => true, 57 | 'magic_constant_casing' => true, 58 | 'native_function_casing' => true, 59 | 'new_with_braces' => true, 60 | 'no_blank_lines_after_class_opening' => true, 61 | 'no_blank_lines_after_phpdoc' => true, 62 | 'no_empty_comment' => true, 63 | 'no_empty_phpdoc' => true, 64 | 'no_empty_statement' => true, 65 | 'no_extra_blank_lines' => ['tokens' => [ 66 | 'curly_brace_block', 67 | 'extra', 68 | 'parenthesis_brace_block', 69 | 'square_brace_block', 70 | 'throw', 71 | 'use', 72 | ]], 73 | 'no_leading_import_slash' => true, 74 | 'no_leading_namespace_whitespace' => true, 75 | 'no_mixed_echo_print' => ['use' => 'echo'], 76 | 'no_multiline_whitespace_around_double_arrow' => true, 77 | 'no_short_bool_cast' => true, 78 | 'no_singleline_whitespace_before_semicolons' => true, 79 | 'no_spaces_around_offset' => true, 80 | 'no_trailing_comma_in_singleline' => true, 81 | 'no_unneeded_curly_braces' => true, 82 | 'no_unneeded_final_method' => true, 83 | 'no_unused_imports' => true, 84 | 'no_whitespace_before_comma_in_array' => true, 85 | 'no_whitespace_in_blank_line' => true, 86 | 'normalize_index_brace' => true, 87 | 'object_operator_without_whitespace' => true, 88 | 'php_unit_fqcn_annotation' => true, 89 | 'phpdoc_align' => [ 90 | 'align' => 'left', 91 | 'tags' => [ 92 | 'method', 93 | 'param', 94 | 'property', 95 | 'return', 96 | 'throws', 97 | 'type', 98 | 'var', 99 | ], 100 | ], 101 | 'phpdoc_annotation_without_dot' => true, 102 | 'phpdoc_indent' => true, 103 | 'phpdoc_inline_tag_normalizer' => true, 104 | 'phpdoc_no_access' => true, 105 | 'phpdoc_no_alias_tag' => true, 106 | 'phpdoc_no_empty_return' => false, 107 | 'phpdoc_no_package' => true, 108 | 'phpdoc_no_useless_inheritdoc' => true, 109 | 'phpdoc_return_self_reference' => true, 110 | 'phpdoc_scalar' => true, 111 | 'phpdoc_separation' => false, 112 | 'phpdoc_single_line_var_spacing' => true, 113 | 'phpdoc_summary' => false, 114 | 'phpdoc_to_comment' => true, 115 | 'phpdoc_trim' => true, 116 | 'phpdoc_types' => true, 117 | 'phpdoc_var_without_name' => true, 118 | 'protected_to_private' => true, 119 | 'return_type_declaration' => true, 120 | 'semicolon_after_instruction' => true, 121 | 'short_scalar_cast' => true, 122 | 'single_blank_line_before_namespace' => true, 123 | 'single_line_comment_style' => [ 124 | 'comment_types' => ['hash'], 125 | ], 126 | 'single_quote' => true, 127 | 'space_after_semicolon' => [ 128 | 'remove_in_empty_for_expressions' => true, 129 | ], 130 | 'standardize_increment' => true, 131 | 'standardize_not_equals' => true, 132 | 'ternary_operator_spaces' => true, 133 | 'trailing_comma_in_multiline' => false, 134 | 'trim_array_spaces' => true, 135 | 'unary_operator_spaces' => true, 136 | 'whitespace_after_comma_in_array' => true, 137 | 'yoda_style' => false, 138 | 'ternary_to_null_coalescing' => true, 139 | 'visibility_required' => ['elements' => [ 140 | 'const', 141 | 'method', 142 | 'property', 143 | ]], 144 | 'native_function_invocation' => [ 145 | 'include' => [ 146 | '@compiler_optimized' 147 | ], 148 | 'scope' => 'namespaced' 149 | ], 150 | 'native_function_type_declaration_casing' => true, 151 | 'no_alias_functions' => [ 152 | 'sets' => [ 153 | '@internal' 154 | ] 155 | ], 156 | ]) 157 | ->setFinder( 158 | PhpCsFixer\Finder::create() 159 | ->in([ 160 | __DIR__ . '/src/', 161 | ]) 162 | ) 163 | ->setFormat('checkstyle') 164 | ; 165 | 166 | return $fixer; 167 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Kevin Papst @ https://www.kevinpapst.de 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kimai - Remote Console 2 | 3 | A PHP application to access your Kimai installation via its JSON API. 4 | 5 | **Requirements** 6 | 7 | - Kimai > v2.14.0 8 | - PHP 8.1 to 8.4 9 | - cURL extension 10 | - json extension 11 | - iconv extension 12 | - zlib extension 13 | - mbstring extension 14 | 15 | ## Installation 16 | 17 | To install the Kimai console tools, execute the following commands: 18 | 19 | ```bash 20 | curl -LO https://github.com/kimai/cli/releases/latest/download/kimai.phar 21 | curl -LO https://github.com/kimai/cli/releases/latest/download/kimai.phar.sha1 22 | sha1sum --check kimai.phar.sha1 23 | rm kimai.phar.sha1 24 | chmod +x kimai.phar 25 | mv kimai.phar /usr/local/bin/kimai 26 | ``` 27 | 28 | ### Configuration file 29 | 30 | Before using it the first time, you have to create a configuration file, which holds the connection infos for Kimai. 31 | By default this config file will be located at `~/.kimai-api.json`: 32 | 33 | ```bash 34 | kimai configuration 35 | ``` 36 | 37 | Make sure the file is only readable for your own user: 38 | 39 | ```bash 40 | chmod 600 ~/.kimai-api.json 41 | ``` 42 | 43 | That's it, you can use Kimai from the command line now. 44 | 45 | By default, the configuration file targets the demo installation and will work... 46 | but now it's time to target your own Kimai, so please edit the config file and change the settings: 47 | 48 | - `URL`: the Kimai installation URL 49 | - `API_TOKEN`: your Kimai API token (can be set when editing your profile) 50 | - `OPTIONS`: an array of request options for CURL (see [guzzle docs](http://docs.guzzlephp.org/en/stable/request-options.html)) 51 | 52 | FAQ: 53 | 54 | - `I want to use a self-signed certificate` - add `"OPTIONS": {"verify": false}` to your configuration 55 | 56 | ## Available commands 57 | 58 | You get a list of all available commands with `kimai`. 59 | 60 | - `kimai active` - display and update all running timesheets (via `--description` and `--tags`) 61 | - `kimai stop` - stop currently active timesheets and update them (via `--description` and `--tags`) 62 | - `kimai start` - start a new timesheet (see below) 63 | - `kimai customer:list` - show a list of customers 64 | - `kimai project:list` - show a list of projects 65 | - `kimai activity:list` - show a list of activities 66 | - `kimai version` - show the full version string of the remote installation 67 | - `kimai configuration` - creates the initial configuration file or displays it 68 | 69 | To get help for a dedicated command use the `--help` switch, eg: `kimai project:list --help` 70 | 71 | ### Start a timesheet 72 | 73 | This command tries to detect customer, project and activity from your input in the following way: 74 | 75 | - if it is a number, then it tries to load the entity by its ID 76 | - if a entity is found, it will be used 77 | - if it is a string, then this is just as search term 78 | - if one entity is found, it will be used 79 | - if multiple entities are found, a select list is shown 80 | - if nothing is given or no result was found in the previous steps, a list of all entities is fetched and shown for selection 81 | - this list might be filtered (eg. only activities for found project) 82 | 83 | This most simple example will display a select list for all customers, then a filtered list for the projects of the chosen customer and finally a select list for all activities for the chosen project: 84 | ``` 85 | bin/kimai start 86 | ``` 87 | 88 | Example to start a new timesheet by search terms only, adding a description and some tags: 89 | ``` 90 | bin/kimai start --customer Schowalter --project analyzer --activity iterate --description "working for fun" --tags "test, bla foo, tagging" 91 | 92 | [OK] Started timesheet 93 | 94 | ------------- ------------------------------------ 95 | ID 5085 96 | Begin 2020-01-03T23:34:26+0100 97 | Description working for fun 98 | Tags bla foo 99 | test 100 | tagging 101 | Customer Schowalter PLC 102 | Project Grass-roots system-worthy analyzer 103 | Activity iterate viral infomediaries 104 | ------------- ------------------------------------ 105 | ``` 106 | 107 | ### Output format 108 | 109 | The `:list`ing commands display a formatted table of all found entities. 110 | 111 | If you want to use the output in a script, instead of manually looking at them, please use the `--csv` switch. 112 | 113 | ## Environment variables 114 | 115 | The following environment variables are supported: 116 | 117 | - `KIMAI_MEMORY_LIMIT` - configures the allowed memory limit (eg `128MB`, or `-1` for unlimited) (see [here](https://www.php.net/manual/en/ini.core.php#ini.memory-limit)) 118 | - `KIMAI_CONFIG` - path to your configuration file (defaults to: `$HOME/.kimai-api.json`) 119 | 120 | ## FAQ 121 | 122 | ### Updating the Console tools 123 | 124 | Redo the initial installation process and overwrite the file `/usr/local/bin/kimai` with the [latest release](https://github.com/kimai/cli/releases). 125 | 126 | ### Check the contents of the PHAR 127 | 128 | There are several ways to see the contents of the PHAR, here are some: 129 | 130 | ``` 131 | phar extract -f kimai.phar 132 | box info kimai.phar -l 133 | ``` 134 | 135 | The PHAR contents are GZ compressed and verified with a SHA512 signature by the PHAR interpreter. 136 | 137 | ### Build from source 138 | 139 | You need to have PHP cli installed with all required dependencies. 140 | The dependencies might differ from than the ones required for running the tools. 141 | Then install the [humbug/box](https://github.com/humbug/box) project, 142 | according to their [installation docs](https://github.com/humbug/box/blob/master/doc/installation.md#installation). 143 | 144 | Now compiling the PHAR is as simple as calling: 145 | 146 | ```bash 147 | box compile 148 | ``` 149 | 150 | ### Release on GitHub 151 | 152 | - Bump version in `src/Constants.php` 153 | - Execute `composer install --no-dev` 154 | - Execute `box compile` 155 | - Execute `sha1sum kimai.phar > kimai.phar.sha1` 156 | - Prepare a new GitHub release 157 | - Upload the files `kimai.phar` and `kimai.phar.sha1` to the new release 158 | - Publish the release 159 | -------------------------------------------------------------------------------- /bin/kimai: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 96 | 97 | __HALT_COMPILER(); 98 | -------------------------------------------------------------------------------- /box.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "check-requirements": false, 3 | "dump-autoload": false, 4 | "stub": "bin/kimai", 5 | "main": false, 6 | "output": "kimai.phar", 7 | "compactors": [ 8 | "KevinGH\\Box\\Compactor\\Json", 9 | "KevinGH\\Box\\Compactor\\Php" 10 | ], 11 | "files": [ 12 | "LICENSE" 13 | ], 14 | "finder": [ 15 | { 16 | "name": [ 17 | "*.php" 18 | ], 19 | "exclude": [ 20 | "Test", 21 | "test", 22 | "Tests", 23 | "tests" 24 | ], 25 | "in": [ 26 | "src", 27 | "vendor" 28 | ] 29 | } 30 | ], 31 | "compression": "GZ", 32 | "algorithm": "SHA512", 33 | "git-commit": "git-commit", 34 | "git-commit-short": "git-commit-short", 35 | "datetime": "release-date" 36 | } 37 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kimai/cli", 3 | "license": "MIT", 4 | "type": "project", 5 | "description": "Kimai - console application to manage your time-tracking data remotely", 6 | "authors": [ 7 | { 8 | "name": "Kevin Papst", 9 | "homepage": "https://www.kevinpapst.de" 10 | } 11 | ], 12 | "require": { 13 | "php": "8.1.*||8.2.*||8.3.*||8.4.*", 14 | "ext-iconv": "*", 15 | "ext-json": "*", 16 | "ext-curl": "*", 17 | "ext-mbstring": "*", 18 | "kimai/api-php": "^2.0", 19 | "symfony/console": "6.*" 20 | }, 21 | "require-dev": { 22 | "friendsofphp/php-cs-fixer": "^3.3", 23 | "phpstan/phpstan": "^2.0" 24 | }, 25 | "config": { 26 | "platform": { 27 | "php": "8.1" 28 | }, 29 | "preferred-install": { 30 | "*": "dist" 31 | }, 32 | "sort-packages": true 33 | }, 34 | "autoload": { 35 | "psr-4": { 36 | "KimaiConsole\\": "src/" 37 | } 38 | }, 39 | "extra": { 40 | "symfony": { 41 | "allow-contrib": false, 42 | "require": "6.*" 43 | } 44 | }, 45 | "scripts": { 46 | "phpstan": "vendor/bin/phpstan analyse src -c phpstan.neon --level=7", 47 | "codestyle": "vendor/bin/php-cs-fixer fix --dry-run --verbose --show-progress=none", 48 | "codestyle-fix": "vendor/bin/php-cs-fixer fix" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "bf1f22c1e0ebd047dd3d9d906d025e84", 8 | "packages": [ 9 | { 10 | "name": "guzzlehttp/guzzle", 11 | "version": "7.9.2", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/guzzle/guzzle.git", 15 | "reference": "d281ed313b989f213357e3be1a179f02196ac99b" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b", 20 | "reference": "d281ed313b989f213357e3be1a179f02196ac99b", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "ext-json": "*", 25 | "guzzlehttp/promises": "^1.5.3 || ^2.0.3", 26 | "guzzlehttp/psr7": "^2.7.0", 27 | "php": "^7.2.5 || ^8.0", 28 | "psr/http-client": "^1.0", 29 | "symfony/deprecation-contracts": "^2.2 || ^3.0" 30 | }, 31 | "provide": { 32 | "psr/http-client-implementation": "1.0" 33 | }, 34 | "require-dev": { 35 | "bamarni/composer-bin-plugin": "^1.8.2", 36 | "ext-curl": "*", 37 | "guzzle/client-integration-tests": "3.0.2", 38 | "php-http/message-factory": "^1.1", 39 | "phpunit/phpunit": "^8.5.39 || ^9.6.20", 40 | "psr/log": "^1.1 || ^2.0 || ^3.0" 41 | }, 42 | "suggest": { 43 | "ext-curl": "Required for CURL handler support", 44 | "ext-intl": "Required for Internationalized Domain Name (IDN) support", 45 | "psr/log": "Required for using the Log middleware" 46 | }, 47 | "type": "library", 48 | "extra": { 49 | "bamarni-bin": { 50 | "bin-links": true, 51 | "forward-command": false 52 | } 53 | }, 54 | "autoload": { 55 | "files": [ 56 | "src/functions_include.php" 57 | ], 58 | "psr-4": { 59 | "GuzzleHttp\\": "src/" 60 | } 61 | }, 62 | "notification-url": "https://packagist.org/downloads/", 63 | "license": [ 64 | "MIT" 65 | ], 66 | "authors": [ 67 | { 68 | "name": "Graham Campbell", 69 | "email": "hello@gjcampbell.co.uk", 70 | "homepage": "https://github.com/GrahamCampbell" 71 | }, 72 | { 73 | "name": "Michael Dowling", 74 | "email": "mtdowling@gmail.com", 75 | "homepage": "https://github.com/mtdowling" 76 | }, 77 | { 78 | "name": "Jeremy Lindblom", 79 | "email": "jeremeamia@gmail.com", 80 | "homepage": "https://github.com/jeremeamia" 81 | }, 82 | { 83 | "name": "George Mponos", 84 | "email": "gmponos@gmail.com", 85 | "homepage": "https://github.com/gmponos" 86 | }, 87 | { 88 | "name": "Tobias Nyholm", 89 | "email": "tobias.nyholm@gmail.com", 90 | "homepage": "https://github.com/Nyholm" 91 | }, 92 | { 93 | "name": "Márk Sági-Kazár", 94 | "email": "mark.sagikazar@gmail.com", 95 | "homepage": "https://github.com/sagikazarmark" 96 | }, 97 | { 98 | "name": "Tobias Schultze", 99 | "email": "webmaster@tubo-world.de", 100 | "homepage": "https://github.com/Tobion" 101 | } 102 | ], 103 | "description": "Guzzle is a PHP HTTP client library", 104 | "keywords": [ 105 | "client", 106 | "curl", 107 | "framework", 108 | "http", 109 | "http client", 110 | "psr-18", 111 | "psr-7", 112 | "rest", 113 | "web service" 114 | ], 115 | "support": { 116 | "issues": "https://github.com/guzzle/guzzle/issues", 117 | "source": "https://github.com/guzzle/guzzle/tree/7.9.2" 118 | }, 119 | "funding": [ 120 | { 121 | "url": "https://github.com/GrahamCampbell", 122 | "type": "github" 123 | }, 124 | { 125 | "url": "https://github.com/Nyholm", 126 | "type": "github" 127 | }, 128 | { 129 | "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", 130 | "type": "tidelift" 131 | } 132 | ], 133 | "time": "2024-07-24T11:22:20+00:00" 134 | }, 135 | { 136 | "name": "guzzlehttp/promises", 137 | "version": "2.0.4", 138 | "source": { 139 | "type": "git", 140 | "url": "https://github.com/guzzle/promises.git", 141 | "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455" 142 | }, 143 | "dist": { 144 | "type": "zip", 145 | "url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455", 146 | "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455", 147 | "shasum": "" 148 | }, 149 | "require": { 150 | "php": "^7.2.5 || ^8.0" 151 | }, 152 | "require-dev": { 153 | "bamarni/composer-bin-plugin": "^1.8.2", 154 | "phpunit/phpunit": "^8.5.39 || ^9.6.20" 155 | }, 156 | "type": "library", 157 | "extra": { 158 | "bamarni-bin": { 159 | "bin-links": true, 160 | "forward-command": false 161 | } 162 | }, 163 | "autoload": { 164 | "psr-4": { 165 | "GuzzleHttp\\Promise\\": "src/" 166 | } 167 | }, 168 | "notification-url": "https://packagist.org/downloads/", 169 | "license": [ 170 | "MIT" 171 | ], 172 | "authors": [ 173 | { 174 | "name": "Graham Campbell", 175 | "email": "hello@gjcampbell.co.uk", 176 | "homepage": "https://github.com/GrahamCampbell" 177 | }, 178 | { 179 | "name": "Michael Dowling", 180 | "email": "mtdowling@gmail.com", 181 | "homepage": "https://github.com/mtdowling" 182 | }, 183 | { 184 | "name": "Tobias Nyholm", 185 | "email": "tobias.nyholm@gmail.com", 186 | "homepage": "https://github.com/Nyholm" 187 | }, 188 | { 189 | "name": "Tobias Schultze", 190 | "email": "webmaster@tubo-world.de", 191 | "homepage": "https://github.com/Tobion" 192 | } 193 | ], 194 | "description": "Guzzle promises library", 195 | "keywords": [ 196 | "promise" 197 | ], 198 | "support": { 199 | "issues": "https://github.com/guzzle/promises/issues", 200 | "source": "https://github.com/guzzle/promises/tree/2.0.4" 201 | }, 202 | "funding": [ 203 | { 204 | "url": "https://github.com/GrahamCampbell", 205 | "type": "github" 206 | }, 207 | { 208 | "url": "https://github.com/Nyholm", 209 | "type": "github" 210 | }, 211 | { 212 | "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", 213 | "type": "tidelift" 214 | } 215 | ], 216 | "time": "2024-10-17T10:06:22+00:00" 217 | }, 218 | { 219 | "name": "guzzlehttp/psr7", 220 | "version": "2.7.0", 221 | "source": { 222 | "type": "git", 223 | "url": "https://github.com/guzzle/psr7.git", 224 | "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201" 225 | }, 226 | "dist": { 227 | "type": "zip", 228 | "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201", 229 | "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201", 230 | "shasum": "" 231 | }, 232 | "require": { 233 | "php": "^7.2.5 || ^8.0", 234 | "psr/http-factory": "^1.0", 235 | "psr/http-message": "^1.1 || ^2.0", 236 | "ralouphie/getallheaders": "^3.0" 237 | }, 238 | "provide": { 239 | "psr/http-factory-implementation": "1.0", 240 | "psr/http-message-implementation": "1.0" 241 | }, 242 | "require-dev": { 243 | "bamarni/composer-bin-plugin": "^1.8.2", 244 | "http-interop/http-factory-tests": "0.9.0", 245 | "phpunit/phpunit": "^8.5.39 || ^9.6.20" 246 | }, 247 | "suggest": { 248 | "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" 249 | }, 250 | "type": "library", 251 | "extra": { 252 | "bamarni-bin": { 253 | "bin-links": true, 254 | "forward-command": false 255 | } 256 | }, 257 | "autoload": { 258 | "psr-4": { 259 | "GuzzleHttp\\Psr7\\": "src/" 260 | } 261 | }, 262 | "notification-url": "https://packagist.org/downloads/", 263 | "license": [ 264 | "MIT" 265 | ], 266 | "authors": [ 267 | { 268 | "name": "Graham Campbell", 269 | "email": "hello@gjcampbell.co.uk", 270 | "homepage": "https://github.com/GrahamCampbell" 271 | }, 272 | { 273 | "name": "Michael Dowling", 274 | "email": "mtdowling@gmail.com", 275 | "homepage": "https://github.com/mtdowling" 276 | }, 277 | { 278 | "name": "George Mponos", 279 | "email": "gmponos@gmail.com", 280 | "homepage": "https://github.com/gmponos" 281 | }, 282 | { 283 | "name": "Tobias Nyholm", 284 | "email": "tobias.nyholm@gmail.com", 285 | "homepage": "https://github.com/Nyholm" 286 | }, 287 | { 288 | "name": "Márk Sági-Kazár", 289 | "email": "mark.sagikazar@gmail.com", 290 | "homepage": "https://github.com/sagikazarmark" 291 | }, 292 | { 293 | "name": "Tobias Schultze", 294 | "email": "webmaster@tubo-world.de", 295 | "homepage": "https://github.com/Tobion" 296 | }, 297 | { 298 | "name": "Márk Sági-Kazár", 299 | "email": "mark.sagikazar@gmail.com", 300 | "homepage": "https://sagikazarmark.hu" 301 | } 302 | ], 303 | "description": "PSR-7 message implementation that also provides common utility methods", 304 | "keywords": [ 305 | "http", 306 | "message", 307 | "psr-7", 308 | "request", 309 | "response", 310 | "stream", 311 | "uri", 312 | "url" 313 | ], 314 | "support": { 315 | "issues": "https://github.com/guzzle/psr7/issues", 316 | "source": "https://github.com/guzzle/psr7/tree/2.7.0" 317 | }, 318 | "funding": [ 319 | { 320 | "url": "https://github.com/GrahamCampbell", 321 | "type": "github" 322 | }, 323 | { 324 | "url": "https://github.com/Nyholm", 325 | "type": "github" 326 | }, 327 | { 328 | "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", 329 | "type": "tidelift" 330 | } 331 | ], 332 | "time": "2024-07-18T11:15:46+00:00" 333 | }, 334 | { 335 | "name": "kimai/api-php", 336 | "version": "2.0", 337 | "source": { 338 | "type": "git", 339 | "url": "https://github.com/kimai/api-php.git", 340 | "reference": "87b12c871074beed1d4ce312cc349de2d166a7d4" 341 | }, 342 | "dist": { 343 | "type": "zip", 344 | "url": "https://api.github.com/repos/kimai/api-php/zipball/87b12c871074beed1d4ce312cc349de2d166a7d4", 345 | "reference": "87b12c871074beed1d4ce312cc349de2d166a7d4", 346 | "shasum": "" 347 | }, 348 | "require": { 349 | "ext-curl": "*", 350 | "ext-json": "*", 351 | "ext-mbstring": "*", 352 | "guzzlehttp/guzzle": "^7.3", 353 | "guzzlehttp/psr7": "^1.7 || ^2.0", 354 | "php": "^7.4 || ^8.0" 355 | }, 356 | "type": "library", 357 | "autoload": { 358 | "psr-4": { 359 | "Swagger\\Client\\": "lib/" 360 | } 361 | }, 362 | "notification-url": "https://packagist.org/downloads/", 363 | "license": [ 364 | "MIT" 365 | ], 366 | "authors": [ 367 | { 368 | "name": "Kevin Papst", 369 | "homepage": "https://www.kimai.org" 370 | }, 371 | { 372 | "name": "Swagger and contributors", 373 | "homepage": "https://github.com/swagger-api/swagger-codegen" 374 | } 375 | ], 376 | "description": "Swagger generated API stubs for Kimai", 377 | "homepage": "https://github.com/kimai/api-php", 378 | "keywords": [ 379 | "Kimai", 380 | "api", 381 | "php", 382 | "sdk", 383 | "swagger" 384 | ], 385 | "support": { 386 | "issues": "https://github.com/kimai/api-php/issues", 387 | "source": "https://github.com/kimai/api-php/tree/2.0" 388 | }, 389 | "time": "2025-01-21T23:34:58+00:00" 390 | }, 391 | { 392 | "name": "psr/container", 393 | "version": "2.0.2", 394 | "source": { 395 | "type": "git", 396 | "url": "https://github.com/php-fig/container.git", 397 | "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" 398 | }, 399 | "dist": { 400 | "type": "zip", 401 | "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", 402 | "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", 403 | "shasum": "" 404 | }, 405 | "require": { 406 | "php": ">=7.4.0" 407 | }, 408 | "type": "library", 409 | "extra": { 410 | "branch-alias": { 411 | "dev-master": "2.0.x-dev" 412 | } 413 | }, 414 | "autoload": { 415 | "psr-4": { 416 | "Psr\\Container\\": "src/" 417 | } 418 | }, 419 | "notification-url": "https://packagist.org/downloads/", 420 | "license": [ 421 | "MIT" 422 | ], 423 | "authors": [ 424 | { 425 | "name": "PHP-FIG", 426 | "homepage": "https://www.php-fig.org/" 427 | } 428 | ], 429 | "description": "Common Container Interface (PHP FIG PSR-11)", 430 | "homepage": "https://github.com/php-fig/container", 431 | "keywords": [ 432 | "PSR-11", 433 | "container", 434 | "container-interface", 435 | "container-interop", 436 | "psr" 437 | ], 438 | "support": { 439 | "issues": "https://github.com/php-fig/container/issues", 440 | "source": "https://github.com/php-fig/container/tree/2.0.2" 441 | }, 442 | "time": "2021-11-05T16:47:00+00:00" 443 | }, 444 | { 445 | "name": "psr/http-client", 446 | "version": "1.0.3", 447 | "source": { 448 | "type": "git", 449 | "url": "https://github.com/php-fig/http-client.git", 450 | "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" 451 | }, 452 | "dist": { 453 | "type": "zip", 454 | "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", 455 | "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", 456 | "shasum": "" 457 | }, 458 | "require": { 459 | "php": "^7.0 || ^8.0", 460 | "psr/http-message": "^1.0 || ^2.0" 461 | }, 462 | "type": "library", 463 | "extra": { 464 | "branch-alias": { 465 | "dev-master": "1.0.x-dev" 466 | } 467 | }, 468 | "autoload": { 469 | "psr-4": { 470 | "Psr\\Http\\Client\\": "src/" 471 | } 472 | }, 473 | "notification-url": "https://packagist.org/downloads/", 474 | "license": [ 475 | "MIT" 476 | ], 477 | "authors": [ 478 | { 479 | "name": "PHP-FIG", 480 | "homepage": "https://www.php-fig.org/" 481 | } 482 | ], 483 | "description": "Common interface for HTTP clients", 484 | "homepage": "https://github.com/php-fig/http-client", 485 | "keywords": [ 486 | "http", 487 | "http-client", 488 | "psr", 489 | "psr-18" 490 | ], 491 | "support": { 492 | "source": "https://github.com/php-fig/http-client" 493 | }, 494 | "time": "2023-09-23T14:17:50+00:00" 495 | }, 496 | { 497 | "name": "psr/http-factory", 498 | "version": "1.1.0", 499 | "source": { 500 | "type": "git", 501 | "url": "https://github.com/php-fig/http-factory.git", 502 | "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" 503 | }, 504 | "dist": { 505 | "type": "zip", 506 | "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", 507 | "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", 508 | "shasum": "" 509 | }, 510 | "require": { 511 | "php": ">=7.1", 512 | "psr/http-message": "^1.0 || ^2.0" 513 | }, 514 | "type": "library", 515 | "extra": { 516 | "branch-alias": { 517 | "dev-master": "1.0.x-dev" 518 | } 519 | }, 520 | "autoload": { 521 | "psr-4": { 522 | "Psr\\Http\\Message\\": "src/" 523 | } 524 | }, 525 | "notification-url": "https://packagist.org/downloads/", 526 | "license": [ 527 | "MIT" 528 | ], 529 | "authors": [ 530 | { 531 | "name": "PHP-FIG", 532 | "homepage": "https://www.php-fig.org/" 533 | } 534 | ], 535 | "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", 536 | "keywords": [ 537 | "factory", 538 | "http", 539 | "message", 540 | "psr", 541 | "psr-17", 542 | "psr-7", 543 | "request", 544 | "response" 545 | ], 546 | "support": { 547 | "source": "https://github.com/php-fig/http-factory" 548 | }, 549 | "time": "2024-04-15T12:06:14+00:00" 550 | }, 551 | { 552 | "name": "psr/http-message", 553 | "version": "2.0", 554 | "source": { 555 | "type": "git", 556 | "url": "https://github.com/php-fig/http-message.git", 557 | "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" 558 | }, 559 | "dist": { 560 | "type": "zip", 561 | "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", 562 | "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", 563 | "shasum": "" 564 | }, 565 | "require": { 566 | "php": "^7.2 || ^8.0" 567 | }, 568 | "type": "library", 569 | "extra": { 570 | "branch-alias": { 571 | "dev-master": "2.0.x-dev" 572 | } 573 | }, 574 | "autoload": { 575 | "psr-4": { 576 | "Psr\\Http\\Message\\": "src/" 577 | } 578 | }, 579 | "notification-url": "https://packagist.org/downloads/", 580 | "license": [ 581 | "MIT" 582 | ], 583 | "authors": [ 584 | { 585 | "name": "PHP-FIG", 586 | "homepage": "https://www.php-fig.org/" 587 | } 588 | ], 589 | "description": "Common interface for HTTP messages", 590 | "homepage": "https://github.com/php-fig/http-message", 591 | "keywords": [ 592 | "http", 593 | "http-message", 594 | "psr", 595 | "psr-7", 596 | "request", 597 | "response" 598 | ], 599 | "support": { 600 | "source": "https://github.com/php-fig/http-message/tree/2.0" 601 | }, 602 | "time": "2023-04-04T09:54:51+00:00" 603 | }, 604 | { 605 | "name": "ralouphie/getallheaders", 606 | "version": "3.0.3", 607 | "source": { 608 | "type": "git", 609 | "url": "https://github.com/ralouphie/getallheaders.git", 610 | "reference": "120b605dfeb996808c31b6477290a714d356e822" 611 | }, 612 | "dist": { 613 | "type": "zip", 614 | "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", 615 | "reference": "120b605dfeb996808c31b6477290a714d356e822", 616 | "shasum": "" 617 | }, 618 | "require": { 619 | "php": ">=5.6" 620 | }, 621 | "require-dev": { 622 | "php-coveralls/php-coveralls": "^2.1", 623 | "phpunit/phpunit": "^5 || ^6.5" 624 | }, 625 | "type": "library", 626 | "autoload": { 627 | "files": [ 628 | "src/getallheaders.php" 629 | ] 630 | }, 631 | "notification-url": "https://packagist.org/downloads/", 632 | "license": [ 633 | "MIT" 634 | ], 635 | "authors": [ 636 | { 637 | "name": "Ralph Khattar", 638 | "email": "ralph.khattar@gmail.com" 639 | } 640 | ], 641 | "description": "A polyfill for getallheaders.", 642 | "support": { 643 | "issues": "https://github.com/ralouphie/getallheaders/issues", 644 | "source": "https://github.com/ralouphie/getallheaders/tree/develop" 645 | }, 646 | "time": "2019-03-08T08:55:37+00:00" 647 | }, 648 | { 649 | "name": "symfony/console", 650 | "version": "v6.4.17", 651 | "source": { 652 | "type": "git", 653 | "url": "https://github.com/symfony/console.git", 654 | "reference": "799445db3f15768ecc382ac5699e6da0520a0a04" 655 | }, 656 | "dist": { 657 | "type": "zip", 658 | "url": "https://api.github.com/repos/symfony/console/zipball/799445db3f15768ecc382ac5699e6da0520a0a04", 659 | "reference": "799445db3f15768ecc382ac5699e6da0520a0a04", 660 | "shasum": "" 661 | }, 662 | "require": { 663 | "php": ">=8.1", 664 | "symfony/deprecation-contracts": "^2.5|^3", 665 | "symfony/polyfill-mbstring": "~1.0", 666 | "symfony/service-contracts": "^2.5|^3", 667 | "symfony/string": "^5.4|^6.0|^7.0" 668 | }, 669 | "conflict": { 670 | "symfony/dependency-injection": "<5.4", 671 | "symfony/dotenv": "<5.4", 672 | "symfony/event-dispatcher": "<5.4", 673 | "symfony/lock": "<5.4", 674 | "symfony/process": "<5.4" 675 | }, 676 | "provide": { 677 | "psr/log-implementation": "1.0|2.0|3.0" 678 | }, 679 | "require-dev": { 680 | "psr/log": "^1|^2|^3", 681 | "symfony/config": "^5.4|^6.0|^7.0", 682 | "symfony/dependency-injection": "^5.4|^6.0|^7.0", 683 | "symfony/event-dispatcher": "^5.4|^6.0|^7.0", 684 | "symfony/http-foundation": "^6.4|^7.0", 685 | "symfony/http-kernel": "^6.4|^7.0", 686 | "symfony/lock": "^5.4|^6.0|^7.0", 687 | "symfony/messenger": "^5.4|^6.0|^7.0", 688 | "symfony/process": "^5.4|^6.0|^7.0", 689 | "symfony/stopwatch": "^5.4|^6.0|^7.0", 690 | "symfony/var-dumper": "^5.4|^6.0|^7.0" 691 | }, 692 | "type": "library", 693 | "autoload": { 694 | "psr-4": { 695 | "Symfony\\Component\\Console\\": "" 696 | }, 697 | "exclude-from-classmap": [ 698 | "/Tests/" 699 | ] 700 | }, 701 | "notification-url": "https://packagist.org/downloads/", 702 | "license": [ 703 | "MIT" 704 | ], 705 | "authors": [ 706 | { 707 | "name": "Fabien Potencier", 708 | "email": "fabien@symfony.com" 709 | }, 710 | { 711 | "name": "Symfony Community", 712 | "homepage": "https://symfony.com/contributors" 713 | } 714 | ], 715 | "description": "Eases the creation of beautiful and testable command line interfaces", 716 | "homepage": "https://symfony.com", 717 | "keywords": [ 718 | "cli", 719 | "command-line", 720 | "console", 721 | "terminal" 722 | ], 723 | "support": { 724 | "source": "https://github.com/symfony/console/tree/v6.4.17" 725 | }, 726 | "funding": [ 727 | { 728 | "url": "https://symfony.com/sponsor", 729 | "type": "custom" 730 | }, 731 | { 732 | "url": "https://github.com/fabpot", 733 | "type": "github" 734 | }, 735 | { 736 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 737 | "type": "tidelift" 738 | } 739 | ], 740 | "time": "2024-12-07T12:07:30+00:00" 741 | }, 742 | { 743 | "name": "symfony/deprecation-contracts", 744 | "version": "v3.5.1", 745 | "source": { 746 | "type": "git", 747 | "url": "https://github.com/symfony/deprecation-contracts.git", 748 | "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" 749 | }, 750 | "dist": { 751 | "type": "zip", 752 | "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", 753 | "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", 754 | "shasum": "" 755 | }, 756 | "require": { 757 | "php": ">=8.1" 758 | }, 759 | "type": "library", 760 | "extra": { 761 | "thanks": { 762 | "url": "https://github.com/symfony/contracts", 763 | "name": "symfony/contracts" 764 | }, 765 | "branch-alias": { 766 | "dev-main": "3.5-dev" 767 | } 768 | }, 769 | "autoload": { 770 | "files": [ 771 | "function.php" 772 | ] 773 | }, 774 | "notification-url": "https://packagist.org/downloads/", 775 | "license": [ 776 | "MIT" 777 | ], 778 | "authors": [ 779 | { 780 | "name": "Nicolas Grekas", 781 | "email": "p@tchwork.com" 782 | }, 783 | { 784 | "name": "Symfony Community", 785 | "homepage": "https://symfony.com/contributors" 786 | } 787 | ], 788 | "description": "A generic function and convention to trigger deprecation notices", 789 | "homepage": "https://symfony.com", 790 | "support": { 791 | "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" 792 | }, 793 | "funding": [ 794 | { 795 | "url": "https://symfony.com/sponsor", 796 | "type": "custom" 797 | }, 798 | { 799 | "url": "https://github.com/fabpot", 800 | "type": "github" 801 | }, 802 | { 803 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 804 | "type": "tidelift" 805 | } 806 | ], 807 | "time": "2024-09-25T14:20:29+00:00" 808 | }, 809 | { 810 | "name": "symfony/polyfill-ctype", 811 | "version": "v1.31.0", 812 | "source": { 813 | "type": "git", 814 | "url": "https://github.com/symfony/polyfill-ctype.git", 815 | "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" 816 | }, 817 | "dist": { 818 | "type": "zip", 819 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", 820 | "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", 821 | "shasum": "" 822 | }, 823 | "require": { 824 | "php": ">=7.2" 825 | }, 826 | "provide": { 827 | "ext-ctype": "*" 828 | }, 829 | "suggest": { 830 | "ext-ctype": "For best performance" 831 | }, 832 | "type": "library", 833 | "extra": { 834 | "thanks": { 835 | "url": "https://github.com/symfony/polyfill", 836 | "name": "symfony/polyfill" 837 | } 838 | }, 839 | "autoload": { 840 | "files": [ 841 | "bootstrap.php" 842 | ], 843 | "psr-4": { 844 | "Symfony\\Polyfill\\Ctype\\": "" 845 | } 846 | }, 847 | "notification-url": "https://packagist.org/downloads/", 848 | "license": [ 849 | "MIT" 850 | ], 851 | "authors": [ 852 | { 853 | "name": "Gert de Pagter", 854 | "email": "BackEndTea@gmail.com" 855 | }, 856 | { 857 | "name": "Symfony Community", 858 | "homepage": "https://symfony.com/contributors" 859 | } 860 | ], 861 | "description": "Symfony polyfill for ctype functions", 862 | "homepage": "https://symfony.com", 863 | "keywords": [ 864 | "compatibility", 865 | "ctype", 866 | "polyfill", 867 | "portable" 868 | ], 869 | "support": { 870 | "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" 871 | }, 872 | "funding": [ 873 | { 874 | "url": "https://symfony.com/sponsor", 875 | "type": "custom" 876 | }, 877 | { 878 | "url": "https://github.com/fabpot", 879 | "type": "github" 880 | }, 881 | { 882 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 883 | "type": "tidelift" 884 | } 885 | ], 886 | "time": "2024-09-09T11:45:10+00:00" 887 | }, 888 | { 889 | "name": "symfony/polyfill-intl-grapheme", 890 | "version": "v1.31.0", 891 | "source": { 892 | "type": "git", 893 | "url": "https://github.com/symfony/polyfill-intl-grapheme.git", 894 | "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" 895 | }, 896 | "dist": { 897 | "type": "zip", 898 | "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", 899 | "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", 900 | "shasum": "" 901 | }, 902 | "require": { 903 | "php": ">=7.2" 904 | }, 905 | "suggest": { 906 | "ext-intl": "For best performance" 907 | }, 908 | "type": "library", 909 | "extra": { 910 | "thanks": { 911 | "url": "https://github.com/symfony/polyfill", 912 | "name": "symfony/polyfill" 913 | } 914 | }, 915 | "autoload": { 916 | "files": [ 917 | "bootstrap.php" 918 | ], 919 | "psr-4": { 920 | "Symfony\\Polyfill\\Intl\\Grapheme\\": "" 921 | } 922 | }, 923 | "notification-url": "https://packagist.org/downloads/", 924 | "license": [ 925 | "MIT" 926 | ], 927 | "authors": [ 928 | { 929 | "name": "Nicolas Grekas", 930 | "email": "p@tchwork.com" 931 | }, 932 | { 933 | "name": "Symfony Community", 934 | "homepage": "https://symfony.com/contributors" 935 | } 936 | ], 937 | "description": "Symfony polyfill for intl's grapheme_* functions", 938 | "homepage": "https://symfony.com", 939 | "keywords": [ 940 | "compatibility", 941 | "grapheme", 942 | "intl", 943 | "polyfill", 944 | "portable", 945 | "shim" 946 | ], 947 | "support": { 948 | "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" 949 | }, 950 | "funding": [ 951 | { 952 | "url": "https://symfony.com/sponsor", 953 | "type": "custom" 954 | }, 955 | { 956 | "url": "https://github.com/fabpot", 957 | "type": "github" 958 | }, 959 | { 960 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 961 | "type": "tidelift" 962 | } 963 | ], 964 | "time": "2024-09-09T11:45:10+00:00" 965 | }, 966 | { 967 | "name": "symfony/polyfill-intl-normalizer", 968 | "version": "v1.31.0", 969 | "source": { 970 | "type": "git", 971 | "url": "https://github.com/symfony/polyfill-intl-normalizer.git", 972 | "reference": "3833d7255cc303546435cb650316bff708a1c75c" 973 | }, 974 | "dist": { 975 | "type": "zip", 976 | "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", 977 | "reference": "3833d7255cc303546435cb650316bff708a1c75c", 978 | "shasum": "" 979 | }, 980 | "require": { 981 | "php": ">=7.2" 982 | }, 983 | "suggest": { 984 | "ext-intl": "For best performance" 985 | }, 986 | "type": "library", 987 | "extra": { 988 | "thanks": { 989 | "url": "https://github.com/symfony/polyfill", 990 | "name": "symfony/polyfill" 991 | } 992 | }, 993 | "autoload": { 994 | "files": [ 995 | "bootstrap.php" 996 | ], 997 | "psr-4": { 998 | "Symfony\\Polyfill\\Intl\\Normalizer\\": "" 999 | }, 1000 | "classmap": [ 1001 | "Resources/stubs" 1002 | ] 1003 | }, 1004 | "notification-url": "https://packagist.org/downloads/", 1005 | "license": [ 1006 | "MIT" 1007 | ], 1008 | "authors": [ 1009 | { 1010 | "name": "Nicolas Grekas", 1011 | "email": "p@tchwork.com" 1012 | }, 1013 | { 1014 | "name": "Symfony Community", 1015 | "homepage": "https://symfony.com/contributors" 1016 | } 1017 | ], 1018 | "description": "Symfony polyfill for intl's Normalizer class and related functions", 1019 | "homepage": "https://symfony.com", 1020 | "keywords": [ 1021 | "compatibility", 1022 | "intl", 1023 | "normalizer", 1024 | "polyfill", 1025 | "portable", 1026 | "shim" 1027 | ], 1028 | "support": { 1029 | "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" 1030 | }, 1031 | "funding": [ 1032 | { 1033 | "url": "https://symfony.com/sponsor", 1034 | "type": "custom" 1035 | }, 1036 | { 1037 | "url": "https://github.com/fabpot", 1038 | "type": "github" 1039 | }, 1040 | { 1041 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1042 | "type": "tidelift" 1043 | } 1044 | ], 1045 | "time": "2024-09-09T11:45:10+00:00" 1046 | }, 1047 | { 1048 | "name": "symfony/polyfill-mbstring", 1049 | "version": "v1.31.0", 1050 | "source": { 1051 | "type": "git", 1052 | "url": "https://github.com/symfony/polyfill-mbstring.git", 1053 | "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" 1054 | }, 1055 | "dist": { 1056 | "type": "zip", 1057 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", 1058 | "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", 1059 | "shasum": "" 1060 | }, 1061 | "require": { 1062 | "php": ">=7.2" 1063 | }, 1064 | "provide": { 1065 | "ext-mbstring": "*" 1066 | }, 1067 | "suggest": { 1068 | "ext-mbstring": "For best performance" 1069 | }, 1070 | "type": "library", 1071 | "extra": { 1072 | "thanks": { 1073 | "url": "https://github.com/symfony/polyfill", 1074 | "name": "symfony/polyfill" 1075 | } 1076 | }, 1077 | "autoload": { 1078 | "files": [ 1079 | "bootstrap.php" 1080 | ], 1081 | "psr-4": { 1082 | "Symfony\\Polyfill\\Mbstring\\": "" 1083 | } 1084 | }, 1085 | "notification-url": "https://packagist.org/downloads/", 1086 | "license": [ 1087 | "MIT" 1088 | ], 1089 | "authors": [ 1090 | { 1091 | "name": "Nicolas Grekas", 1092 | "email": "p@tchwork.com" 1093 | }, 1094 | { 1095 | "name": "Symfony Community", 1096 | "homepage": "https://symfony.com/contributors" 1097 | } 1098 | ], 1099 | "description": "Symfony polyfill for the Mbstring extension", 1100 | "homepage": "https://symfony.com", 1101 | "keywords": [ 1102 | "compatibility", 1103 | "mbstring", 1104 | "polyfill", 1105 | "portable", 1106 | "shim" 1107 | ], 1108 | "support": { 1109 | "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" 1110 | }, 1111 | "funding": [ 1112 | { 1113 | "url": "https://symfony.com/sponsor", 1114 | "type": "custom" 1115 | }, 1116 | { 1117 | "url": "https://github.com/fabpot", 1118 | "type": "github" 1119 | }, 1120 | { 1121 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1122 | "type": "tidelift" 1123 | } 1124 | ], 1125 | "time": "2024-09-09T11:45:10+00:00" 1126 | }, 1127 | { 1128 | "name": "symfony/service-contracts", 1129 | "version": "v3.5.1", 1130 | "source": { 1131 | "type": "git", 1132 | "url": "https://github.com/symfony/service-contracts.git", 1133 | "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" 1134 | }, 1135 | "dist": { 1136 | "type": "zip", 1137 | "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", 1138 | "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", 1139 | "shasum": "" 1140 | }, 1141 | "require": { 1142 | "php": ">=8.1", 1143 | "psr/container": "^1.1|^2.0", 1144 | "symfony/deprecation-contracts": "^2.5|^3" 1145 | }, 1146 | "conflict": { 1147 | "ext-psr": "<1.1|>=2" 1148 | }, 1149 | "type": "library", 1150 | "extra": { 1151 | "thanks": { 1152 | "url": "https://github.com/symfony/contracts", 1153 | "name": "symfony/contracts" 1154 | }, 1155 | "branch-alias": { 1156 | "dev-main": "3.5-dev" 1157 | } 1158 | }, 1159 | "autoload": { 1160 | "psr-4": { 1161 | "Symfony\\Contracts\\Service\\": "" 1162 | }, 1163 | "exclude-from-classmap": [ 1164 | "/Test/" 1165 | ] 1166 | }, 1167 | "notification-url": "https://packagist.org/downloads/", 1168 | "license": [ 1169 | "MIT" 1170 | ], 1171 | "authors": [ 1172 | { 1173 | "name": "Nicolas Grekas", 1174 | "email": "p@tchwork.com" 1175 | }, 1176 | { 1177 | "name": "Symfony Community", 1178 | "homepage": "https://symfony.com/contributors" 1179 | } 1180 | ], 1181 | "description": "Generic abstractions related to writing services", 1182 | "homepage": "https://symfony.com", 1183 | "keywords": [ 1184 | "abstractions", 1185 | "contracts", 1186 | "decoupling", 1187 | "interfaces", 1188 | "interoperability", 1189 | "standards" 1190 | ], 1191 | "support": { 1192 | "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" 1193 | }, 1194 | "funding": [ 1195 | { 1196 | "url": "https://symfony.com/sponsor", 1197 | "type": "custom" 1198 | }, 1199 | { 1200 | "url": "https://github.com/fabpot", 1201 | "type": "github" 1202 | }, 1203 | { 1204 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1205 | "type": "tidelift" 1206 | } 1207 | ], 1208 | "time": "2024-09-25T14:20:29+00:00" 1209 | }, 1210 | { 1211 | "name": "symfony/string", 1212 | "version": "v6.4.15", 1213 | "source": { 1214 | "type": "git", 1215 | "url": "https://github.com/symfony/string.git", 1216 | "reference": "73a5e66ea2e1677c98d4449177c5a9cf9d8b4c6f" 1217 | }, 1218 | "dist": { 1219 | "type": "zip", 1220 | "url": "https://api.github.com/repos/symfony/string/zipball/73a5e66ea2e1677c98d4449177c5a9cf9d8b4c6f", 1221 | "reference": "73a5e66ea2e1677c98d4449177c5a9cf9d8b4c6f", 1222 | "shasum": "" 1223 | }, 1224 | "require": { 1225 | "php": ">=8.1", 1226 | "symfony/polyfill-ctype": "~1.8", 1227 | "symfony/polyfill-intl-grapheme": "~1.0", 1228 | "symfony/polyfill-intl-normalizer": "~1.0", 1229 | "symfony/polyfill-mbstring": "~1.0" 1230 | }, 1231 | "conflict": { 1232 | "symfony/translation-contracts": "<2.5" 1233 | }, 1234 | "require-dev": { 1235 | "symfony/error-handler": "^5.4|^6.0|^7.0", 1236 | "symfony/http-client": "^5.4|^6.0|^7.0", 1237 | "symfony/intl": "^6.2|^7.0", 1238 | "symfony/translation-contracts": "^2.5|^3.0", 1239 | "symfony/var-exporter": "^5.4|^6.0|^7.0" 1240 | }, 1241 | "type": "library", 1242 | "autoload": { 1243 | "files": [ 1244 | "Resources/functions.php" 1245 | ], 1246 | "psr-4": { 1247 | "Symfony\\Component\\String\\": "" 1248 | }, 1249 | "exclude-from-classmap": [ 1250 | "/Tests/" 1251 | ] 1252 | }, 1253 | "notification-url": "https://packagist.org/downloads/", 1254 | "license": [ 1255 | "MIT" 1256 | ], 1257 | "authors": [ 1258 | { 1259 | "name": "Nicolas Grekas", 1260 | "email": "p@tchwork.com" 1261 | }, 1262 | { 1263 | "name": "Symfony Community", 1264 | "homepage": "https://symfony.com/contributors" 1265 | } 1266 | ], 1267 | "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", 1268 | "homepage": "https://symfony.com", 1269 | "keywords": [ 1270 | "grapheme", 1271 | "i18n", 1272 | "string", 1273 | "unicode", 1274 | "utf-8", 1275 | "utf8" 1276 | ], 1277 | "support": { 1278 | "source": "https://github.com/symfony/string/tree/v6.4.15" 1279 | }, 1280 | "funding": [ 1281 | { 1282 | "url": "https://symfony.com/sponsor", 1283 | "type": "custom" 1284 | }, 1285 | { 1286 | "url": "https://github.com/fabpot", 1287 | "type": "github" 1288 | }, 1289 | { 1290 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1291 | "type": "tidelift" 1292 | } 1293 | ], 1294 | "time": "2024-11-13T13:31:12+00:00" 1295 | } 1296 | ], 1297 | "packages-dev": [ 1298 | { 1299 | "name": "clue/ndjson-react", 1300 | "version": "v1.3.0", 1301 | "source": { 1302 | "type": "git", 1303 | "url": "https://github.com/clue/reactphp-ndjson.git", 1304 | "reference": "392dc165fce93b5bb5c637b67e59619223c931b0" 1305 | }, 1306 | "dist": { 1307 | "type": "zip", 1308 | "url": "https://api.github.com/repos/clue/reactphp-ndjson/zipball/392dc165fce93b5bb5c637b67e59619223c931b0", 1309 | "reference": "392dc165fce93b5bb5c637b67e59619223c931b0", 1310 | "shasum": "" 1311 | }, 1312 | "require": { 1313 | "php": ">=5.3", 1314 | "react/stream": "^1.2" 1315 | }, 1316 | "require-dev": { 1317 | "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", 1318 | "react/event-loop": "^1.2" 1319 | }, 1320 | "type": "library", 1321 | "autoload": { 1322 | "psr-4": { 1323 | "Clue\\React\\NDJson\\": "src/" 1324 | } 1325 | }, 1326 | "notification-url": "https://packagist.org/downloads/", 1327 | "license": [ 1328 | "MIT" 1329 | ], 1330 | "authors": [ 1331 | { 1332 | "name": "Christian Lück", 1333 | "email": "christian@clue.engineering" 1334 | } 1335 | ], 1336 | "description": "Streaming newline-delimited JSON (NDJSON) parser and encoder for ReactPHP.", 1337 | "homepage": "https://github.com/clue/reactphp-ndjson", 1338 | "keywords": [ 1339 | "NDJSON", 1340 | "json", 1341 | "jsonlines", 1342 | "newline", 1343 | "reactphp", 1344 | "streaming" 1345 | ], 1346 | "support": { 1347 | "issues": "https://github.com/clue/reactphp-ndjson/issues", 1348 | "source": "https://github.com/clue/reactphp-ndjson/tree/v1.3.0" 1349 | }, 1350 | "funding": [ 1351 | { 1352 | "url": "https://clue.engineering/support", 1353 | "type": "custom" 1354 | }, 1355 | { 1356 | "url": "https://github.com/clue", 1357 | "type": "github" 1358 | } 1359 | ], 1360 | "time": "2022-12-23T10:58:28+00:00" 1361 | }, 1362 | { 1363 | "name": "composer/pcre", 1364 | "version": "3.3.2", 1365 | "source": { 1366 | "type": "git", 1367 | "url": "https://github.com/composer/pcre.git", 1368 | "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" 1369 | }, 1370 | "dist": { 1371 | "type": "zip", 1372 | "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", 1373 | "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", 1374 | "shasum": "" 1375 | }, 1376 | "require": { 1377 | "php": "^7.4 || ^8.0" 1378 | }, 1379 | "conflict": { 1380 | "phpstan/phpstan": "<1.11.10" 1381 | }, 1382 | "require-dev": { 1383 | "phpstan/phpstan": "^1.12 || ^2", 1384 | "phpstan/phpstan-strict-rules": "^1 || ^2", 1385 | "phpunit/phpunit": "^8 || ^9" 1386 | }, 1387 | "type": "library", 1388 | "extra": { 1389 | "phpstan": { 1390 | "includes": [ 1391 | "extension.neon" 1392 | ] 1393 | }, 1394 | "branch-alias": { 1395 | "dev-main": "3.x-dev" 1396 | } 1397 | }, 1398 | "autoload": { 1399 | "psr-4": { 1400 | "Composer\\Pcre\\": "src" 1401 | } 1402 | }, 1403 | "notification-url": "https://packagist.org/downloads/", 1404 | "license": [ 1405 | "MIT" 1406 | ], 1407 | "authors": [ 1408 | { 1409 | "name": "Jordi Boggiano", 1410 | "email": "j.boggiano@seld.be", 1411 | "homepage": "http://seld.be" 1412 | } 1413 | ], 1414 | "description": "PCRE wrapping library that offers type-safe preg_* replacements.", 1415 | "keywords": [ 1416 | "PCRE", 1417 | "preg", 1418 | "regex", 1419 | "regular expression" 1420 | ], 1421 | "support": { 1422 | "issues": "https://github.com/composer/pcre/issues", 1423 | "source": "https://github.com/composer/pcre/tree/3.3.2" 1424 | }, 1425 | "funding": [ 1426 | { 1427 | "url": "https://packagist.com", 1428 | "type": "custom" 1429 | }, 1430 | { 1431 | "url": "https://github.com/composer", 1432 | "type": "github" 1433 | }, 1434 | { 1435 | "url": "https://tidelift.com/funding/github/packagist/composer/composer", 1436 | "type": "tidelift" 1437 | } 1438 | ], 1439 | "time": "2024-11-12T16:29:46+00:00" 1440 | }, 1441 | { 1442 | "name": "composer/semver", 1443 | "version": "3.4.3", 1444 | "source": { 1445 | "type": "git", 1446 | "url": "https://github.com/composer/semver.git", 1447 | "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" 1448 | }, 1449 | "dist": { 1450 | "type": "zip", 1451 | "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", 1452 | "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", 1453 | "shasum": "" 1454 | }, 1455 | "require": { 1456 | "php": "^5.3.2 || ^7.0 || ^8.0" 1457 | }, 1458 | "require-dev": { 1459 | "phpstan/phpstan": "^1.11", 1460 | "symfony/phpunit-bridge": "^3 || ^7" 1461 | }, 1462 | "type": "library", 1463 | "extra": { 1464 | "branch-alias": { 1465 | "dev-main": "3.x-dev" 1466 | } 1467 | }, 1468 | "autoload": { 1469 | "psr-4": { 1470 | "Composer\\Semver\\": "src" 1471 | } 1472 | }, 1473 | "notification-url": "https://packagist.org/downloads/", 1474 | "license": [ 1475 | "MIT" 1476 | ], 1477 | "authors": [ 1478 | { 1479 | "name": "Nils Adermann", 1480 | "email": "naderman@naderman.de", 1481 | "homepage": "http://www.naderman.de" 1482 | }, 1483 | { 1484 | "name": "Jordi Boggiano", 1485 | "email": "j.boggiano@seld.be", 1486 | "homepage": "http://seld.be" 1487 | }, 1488 | { 1489 | "name": "Rob Bast", 1490 | "email": "rob.bast@gmail.com", 1491 | "homepage": "http://robbast.nl" 1492 | } 1493 | ], 1494 | "description": "Semver library that offers utilities, version constraint parsing and validation.", 1495 | "keywords": [ 1496 | "semantic", 1497 | "semver", 1498 | "validation", 1499 | "versioning" 1500 | ], 1501 | "support": { 1502 | "irc": "ircs://irc.libera.chat:6697/composer", 1503 | "issues": "https://github.com/composer/semver/issues", 1504 | "source": "https://github.com/composer/semver/tree/3.4.3" 1505 | }, 1506 | "funding": [ 1507 | { 1508 | "url": "https://packagist.com", 1509 | "type": "custom" 1510 | }, 1511 | { 1512 | "url": "https://github.com/composer", 1513 | "type": "github" 1514 | }, 1515 | { 1516 | "url": "https://tidelift.com/funding/github/packagist/composer/composer", 1517 | "type": "tidelift" 1518 | } 1519 | ], 1520 | "time": "2024-09-19T14:15:21+00:00" 1521 | }, 1522 | { 1523 | "name": "composer/xdebug-handler", 1524 | "version": "3.0.5", 1525 | "source": { 1526 | "type": "git", 1527 | "url": "https://github.com/composer/xdebug-handler.git", 1528 | "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" 1529 | }, 1530 | "dist": { 1531 | "type": "zip", 1532 | "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", 1533 | "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", 1534 | "shasum": "" 1535 | }, 1536 | "require": { 1537 | "composer/pcre": "^1 || ^2 || ^3", 1538 | "php": "^7.2.5 || ^8.0", 1539 | "psr/log": "^1 || ^2 || ^3" 1540 | }, 1541 | "require-dev": { 1542 | "phpstan/phpstan": "^1.0", 1543 | "phpstan/phpstan-strict-rules": "^1.1", 1544 | "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" 1545 | }, 1546 | "type": "library", 1547 | "autoload": { 1548 | "psr-4": { 1549 | "Composer\\XdebugHandler\\": "src" 1550 | } 1551 | }, 1552 | "notification-url": "https://packagist.org/downloads/", 1553 | "license": [ 1554 | "MIT" 1555 | ], 1556 | "authors": [ 1557 | { 1558 | "name": "John Stevenson", 1559 | "email": "john-stevenson@blueyonder.co.uk" 1560 | } 1561 | ], 1562 | "description": "Restarts a process without Xdebug.", 1563 | "keywords": [ 1564 | "Xdebug", 1565 | "performance" 1566 | ], 1567 | "support": { 1568 | "irc": "ircs://irc.libera.chat:6697/composer", 1569 | "issues": "https://github.com/composer/xdebug-handler/issues", 1570 | "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" 1571 | }, 1572 | "funding": [ 1573 | { 1574 | "url": "https://packagist.com", 1575 | "type": "custom" 1576 | }, 1577 | { 1578 | "url": "https://github.com/composer", 1579 | "type": "github" 1580 | }, 1581 | { 1582 | "url": "https://tidelift.com/funding/github/packagist/composer/composer", 1583 | "type": "tidelift" 1584 | } 1585 | ], 1586 | "time": "2024-05-06T16:37:16+00:00" 1587 | }, 1588 | { 1589 | "name": "evenement/evenement", 1590 | "version": "v3.0.2", 1591 | "source": { 1592 | "type": "git", 1593 | "url": "https://github.com/igorw/evenement.git", 1594 | "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" 1595 | }, 1596 | "dist": { 1597 | "type": "zip", 1598 | "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", 1599 | "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", 1600 | "shasum": "" 1601 | }, 1602 | "require": { 1603 | "php": ">=7.0" 1604 | }, 1605 | "require-dev": { 1606 | "phpunit/phpunit": "^9 || ^6" 1607 | }, 1608 | "type": "library", 1609 | "autoload": { 1610 | "psr-4": { 1611 | "Evenement\\": "src/" 1612 | } 1613 | }, 1614 | "notification-url": "https://packagist.org/downloads/", 1615 | "license": [ 1616 | "MIT" 1617 | ], 1618 | "authors": [ 1619 | { 1620 | "name": "Igor Wiedler", 1621 | "email": "igor@wiedler.ch" 1622 | } 1623 | ], 1624 | "description": "Événement is a very simple event dispatching library for PHP", 1625 | "keywords": [ 1626 | "event-dispatcher", 1627 | "event-emitter" 1628 | ], 1629 | "support": { 1630 | "issues": "https://github.com/igorw/evenement/issues", 1631 | "source": "https://github.com/igorw/evenement/tree/v3.0.2" 1632 | }, 1633 | "time": "2023-08-08T05:53:35+00:00" 1634 | }, 1635 | { 1636 | "name": "fidry/cpu-core-counter", 1637 | "version": "1.2.0", 1638 | "source": { 1639 | "type": "git", 1640 | "url": "https://github.com/theofidry/cpu-core-counter.git", 1641 | "reference": "8520451a140d3f46ac33042715115e290cf5785f" 1642 | }, 1643 | "dist": { 1644 | "type": "zip", 1645 | "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f", 1646 | "reference": "8520451a140d3f46ac33042715115e290cf5785f", 1647 | "shasum": "" 1648 | }, 1649 | "require": { 1650 | "php": "^7.2 || ^8.0" 1651 | }, 1652 | "require-dev": { 1653 | "fidry/makefile": "^0.2.0", 1654 | "fidry/php-cs-fixer-config": "^1.1.2", 1655 | "phpstan/extension-installer": "^1.2.0", 1656 | "phpstan/phpstan": "^1.9.2", 1657 | "phpstan/phpstan-deprecation-rules": "^1.0.0", 1658 | "phpstan/phpstan-phpunit": "^1.2.2", 1659 | "phpstan/phpstan-strict-rules": "^1.4.4", 1660 | "phpunit/phpunit": "^8.5.31 || ^9.5.26", 1661 | "webmozarts/strict-phpunit": "^7.5" 1662 | }, 1663 | "type": "library", 1664 | "autoload": { 1665 | "psr-4": { 1666 | "Fidry\\CpuCoreCounter\\": "src/" 1667 | } 1668 | }, 1669 | "notification-url": "https://packagist.org/downloads/", 1670 | "license": [ 1671 | "MIT" 1672 | ], 1673 | "authors": [ 1674 | { 1675 | "name": "Théo FIDRY", 1676 | "email": "theo.fidry@gmail.com" 1677 | } 1678 | ], 1679 | "description": "Tiny utility to get the number of CPU cores.", 1680 | "keywords": [ 1681 | "CPU", 1682 | "core" 1683 | ], 1684 | "support": { 1685 | "issues": "https://github.com/theofidry/cpu-core-counter/issues", 1686 | "source": "https://github.com/theofidry/cpu-core-counter/tree/1.2.0" 1687 | }, 1688 | "funding": [ 1689 | { 1690 | "url": "https://github.com/theofidry", 1691 | "type": "github" 1692 | } 1693 | ], 1694 | "time": "2024-08-06T10:04:20+00:00" 1695 | }, 1696 | { 1697 | "name": "friendsofphp/php-cs-fixer", 1698 | "version": "v3.68.1", 1699 | "source": { 1700 | "type": "git", 1701 | "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", 1702 | "reference": "b9db2b2ea3cdba7201067acee46f984ef2397cff" 1703 | }, 1704 | "dist": { 1705 | "type": "zip", 1706 | "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/b9db2b2ea3cdba7201067acee46f984ef2397cff", 1707 | "reference": "b9db2b2ea3cdba7201067acee46f984ef2397cff", 1708 | "shasum": "" 1709 | }, 1710 | "require": { 1711 | "clue/ndjson-react": "^1.0", 1712 | "composer/semver": "^3.4", 1713 | "composer/xdebug-handler": "^3.0.3", 1714 | "ext-filter": "*", 1715 | "ext-json": "*", 1716 | "ext-tokenizer": "*", 1717 | "fidry/cpu-core-counter": "^1.2", 1718 | "php": "^7.4 || ^8.0", 1719 | "react/child-process": "^0.6.5", 1720 | "react/event-loop": "^1.0", 1721 | "react/promise": "^2.0 || ^3.0", 1722 | "react/socket": "^1.0", 1723 | "react/stream": "^1.0", 1724 | "sebastian/diff": "^4.0 || ^5.1 || ^6.0", 1725 | "symfony/console": "^5.4 || ^6.4 || ^7.0", 1726 | "symfony/event-dispatcher": "^5.4 || ^6.4 || ^7.0", 1727 | "symfony/filesystem": "^5.4 || ^6.4 || ^7.0", 1728 | "symfony/finder": "^5.4 || ^6.4 || ^7.0", 1729 | "symfony/options-resolver": "^5.4 || ^6.4 || ^7.0", 1730 | "symfony/polyfill-mbstring": "^1.31", 1731 | "symfony/polyfill-php80": "^1.31", 1732 | "symfony/polyfill-php81": "^1.31", 1733 | "symfony/process": "^5.4 || ^6.4 || ^7.2", 1734 | "symfony/stopwatch": "^5.4 || ^6.4 || ^7.0" 1735 | }, 1736 | "require-dev": { 1737 | "facile-it/paraunit": "^1.3.1 || ^2.4", 1738 | "infection/infection": "^0.29.8", 1739 | "justinrainbow/json-schema": "^5.3 || ^6.0", 1740 | "keradus/cli-executor": "^2.1", 1741 | "mikey179/vfsstream": "^1.6.12", 1742 | "php-coveralls/php-coveralls": "^2.7", 1743 | "php-cs-fixer/accessible-object": "^1.1", 1744 | "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.5", 1745 | "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.5", 1746 | "phpunit/phpunit": "^9.6.22 || ^10.5.40 || ^11.5.2", 1747 | "symfony/var-dumper": "^5.4.48 || ^6.4.15 || ^7.2.0", 1748 | "symfony/yaml": "^5.4.45 || ^6.4.13 || ^7.2.0" 1749 | }, 1750 | "suggest": { 1751 | "ext-dom": "For handling output formats in XML", 1752 | "ext-mbstring": "For handling non-UTF8 characters." 1753 | }, 1754 | "bin": [ 1755 | "php-cs-fixer" 1756 | ], 1757 | "type": "application", 1758 | "autoload": { 1759 | "psr-4": { 1760 | "PhpCsFixer\\": "src/" 1761 | }, 1762 | "exclude-from-classmap": [ 1763 | "src/Fixer/Internal/*" 1764 | ] 1765 | }, 1766 | "notification-url": "https://packagist.org/downloads/", 1767 | "license": [ 1768 | "MIT" 1769 | ], 1770 | "authors": [ 1771 | { 1772 | "name": "Fabien Potencier", 1773 | "email": "fabien@symfony.com" 1774 | }, 1775 | { 1776 | "name": "Dariusz Rumiński", 1777 | "email": "dariusz.ruminski@gmail.com" 1778 | } 1779 | ], 1780 | "description": "A tool to automatically fix PHP code style", 1781 | "keywords": [ 1782 | "Static code analysis", 1783 | "fixer", 1784 | "standards", 1785 | "static analysis" 1786 | ], 1787 | "support": { 1788 | "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", 1789 | "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.68.1" 1790 | }, 1791 | "funding": [ 1792 | { 1793 | "url": "https://github.com/keradus", 1794 | "type": "github" 1795 | } 1796 | ], 1797 | "time": "2025-01-17T09:20:36+00:00" 1798 | }, 1799 | { 1800 | "name": "phpstan/phpstan", 1801 | "version": "2.1.2", 1802 | "source": { 1803 | "type": "git", 1804 | "url": "https://github.com/phpstan/phpstan.git", 1805 | "reference": "7d08f569e582ade182a375c366cbd896eccadd3a" 1806 | }, 1807 | "dist": { 1808 | "type": "zip", 1809 | "url": "https://api.github.com/repos/phpstan/phpstan/zipball/7d08f569e582ade182a375c366cbd896eccadd3a", 1810 | "reference": "7d08f569e582ade182a375c366cbd896eccadd3a", 1811 | "shasum": "" 1812 | }, 1813 | "require": { 1814 | "php": "^7.4|^8.0" 1815 | }, 1816 | "conflict": { 1817 | "phpstan/phpstan-shim": "*" 1818 | }, 1819 | "bin": [ 1820 | "phpstan", 1821 | "phpstan.phar" 1822 | ], 1823 | "type": "library", 1824 | "autoload": { 1825 | "files": [ 1826 | "bootstrap.php" 1827 | ] 1828 | }, 1829 | "notification-url": "https://packagist.org/downloads/", 1830 | "license": [ 1831 | "MIT" 1832 | ], 1833 | "description": "PHPStan - PHP Static Analysis Tool", 1834 | "keywords": [ 1835 | "dev", 1836 | "static analysis" 1837 | ], 1838 | "support": { 1839 | "docs": "https://phpstan.org/user-guide/getting-started", 1840 | "forum": "https://github.com/phpstan/phpstan/discussions", 1841 | "issues": "https://github.com/phpstan/phpstan/issues", 1842 | "security": "https://github.com/phpstan/phpstan/security/policy", 1843 | "source": "https://github.com/phpstan/phpstan-src" 1844 | }, 1845 | "funding": [ 1846 | { 1847 | "url": "https://github.com/ondrejmirtes", 1848 | "type": "github" 1849 | }, 1850 | { 1851 | "url": "https://github.com/phpstan", 1852 | "type": "github" 1853 | } 1854 | ], 1855 | "time": "2025-01-21T14:54:06+00:00" 1856 | }, 1857 | { 1858 | "name": "psr/event-dispatcher", 1859 | "version": "1.0.0", 1860 | "source": { 1861 | "type": "git", 1862 | "url": "https://github.com/php-fig/event-dispatcher.git", 1863 | "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" 1864 | }, 1865 | "dist": { 1866 | "type": "zip", 1867 | "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", 1868 | "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", 1869 | "shasum": "" 1870 | }, 1871 | "require": { 1872 | "php": ">=7.2.0" 1873 | }, 1874 | "type": "library", 1875 | "extra": { 1876 | "branch-alias": { 1877 | "dev-master": "1.0.x-dev" 1878 | } 1879 | }, 1880 | "autoload": { 1881 | "psr-4": { 1882 | "Psr\\EventDispatcher\\": "src/" 1883 | } 1884 | }, 1885 | "notification-url": "https://packagist.org/downloads/", 1886 | "license": [ 1887 | "MIT" 1888 | ], 1889 | "authors": [ 1890 | { 1891 | "name": "PHP-FIG", 1892 | "homepage": "http://www.php-fig.org/" 1893 | } 1894 | ], 1895 | "description": "Standard interfaces for event handling.", 1896 | "keywords": [ 1897 | "events", 1898 | "psr", 1899 | "psr-14" 1900 | ], 1901 | "support": { 1902 | "issues": "https://github.com/php-fig/event-dispatcher/issues", 1903 | "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" 1904 | }, 1905 | "time": "2019-01-08T18:20:26+00:00" 1906 | }, 1907 | { 1908 | "name": "psr/log", 1909 | "version": "3.0.2", 1910 | "source": { 1911 | "type": "git", 1912 | "url": "https://github.com/php-fig/log.git", 1913 | "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" 1914 | }, 1915 | "dist": { 1916 | "type": "zip", 1917 | "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", 1918 | "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", 1919 | "shasum": "" 1920 | }, 1921 | "require": { 1922 | "php": ">=8.0.0" 1923 | }, 1924 | "type": "library", 1925 | "extra": { 1926 | "branch-alias": { 1927 | "dev-master": "3.x-dev" 1928 | } 1929 | }, 1930 | "autoload": { 1931 | "psr-4": { 1932 | "Psr\\Log\\": "src" 1933 | } 1934 | }, 1935 | "notification-url": "https://packagist.org/downloads/", 1936 | "license": [ 1937 | "MIT" 1938 | ], 1939 | "authors": [ 1940 | { 1941 | "name": "PHP-FIG", 1942 | "homepage": "https://www.php-fig.org/" 1943 | } 1944 | ], 1945 | "description": "Common interface for logging libraries", 1946 | "homepage": "https://github.com/php-fig/log", 1947 | "keywords": [ 1948 | "log", 1949 | "psr", 1950 | "psr-3" 1951 | ], 1952 | "support": { 1953 | "source": "https://github.com/php-fig/log/tree/3.0.2" 1954 | }, 1955 | "time": "2024-09-11T13:17:53+00:00" 1956 | }, 1957 | { 1958 | "name": "react/cache", 1959 | "version": "v1.2.0", 1960 | "source": { 1961 | "type": "git", 1962 | "url": "https://github.com/reactphp/cache.git", 1963 | "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" 1964 | }, 1965 | "dist": { 1966 | "type": "zip", 1967 | "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", 1968 | "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", 1969 | "shasum": "" 1970 | }, 1971 | "require": { 1972 | "php": ">=5.3.0", 1973 | "react/promise": "^3.0 || ^2.0 || ^1.1" 1974 | }, 1975 | "require-dev": { 1976 | "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" 1977 | }, 1978 | "type": "library", 1979 | "autoload": { 1980 | "psr-4": { 1981 | "React\\Cache\\": "src/" 1982 | } 1983 | }, 1984 | "notification-url": "https://packagist.org/downloads/", 1985 | "license": [ 1986 | "MIT" 1987 | ], 1988 | "authors": [ 1989 | { 1990 | "name": "Christian Lück", 1991 | "email": "christian@clue.engineering", 1992 | "homepage": "https://clue.engineering/" 1993 | }, 1994 | { 1995 | "name": "Cees-Jan Kiewiet", 1996 | "email": "reactphp@ceesjankiewiet.nl", 1997 | "homepage": "https://wyrihaximus.net/" 1998 | }, 1999 | { 2000 | "name": "Jan Sorgalla", 2001 | "email": "jsorgalla@gmail.com", 2002 | "homepage": "https://sorgalla.com/" 2003 | }, 2004 | { 2005 | "name": "Chris Boden", 2006 | "email": "cboden@gmail.com", 2007 | "homepage": "https://cboden.dev/" 2008 | } 2009 | ], 2010 | "description": "Async, Promise-based cache interface for ReactPHP", 2011 | "keywords": [ 2012 | "cache", 2013 | "caching", 2014 | "promise", 2015 | "reactphp" 2016 | ], 2017 | "support": { 2018 | "issues": "https://github.com/reactphp/cache/issues", 2019 | "source": "https://github.com/reactphp/cache/tree/v1.2.0" 2020 | }, 2021 | "funding": [ 2022 | { 2023 | "url": "https://opencollective.com/reactphp", 2024 | "type": "open_collective" 2025 | } 2026 | ], 2027 | "time": "2022-11-30T15:59:55+00:00" 2028 | }, 2029 | { 2030 | "name": "react/child-process", 2031 | "version": "v0.6.6", 2032 | "source": { 2033 | "type": "git", 2034 | "url": "https://github.com/reactphp/child-process.git", 2035 | "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159" 2036 | }, 2037 | "dist": { 2038 | "type": "zip", 2039 | "url": "https://api.github.com/repos/reactphp/child-process/zipball/1721e2b93d89b745664353b9cfc8f155ba8a6159", 2040 | "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159", 2041 | "shasum": "" 2042 | }, 2043 | "require": { 2044 | "evenement/evenement": "^3.0 || ^2.0 || ^1.0", 2045 | "php": ">=5.3.0", 2046 | "react/event-loop": "^1.2", 2047 | "react/stream": "^1.4" 2048 | }, 2049 | "require-dev": { 2050 | "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", 2051 | "react/socket": "^1.16", 2052 | "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" 2053 | }, 2054 | "type": "library", 2055 | "autoload": { 2056 | "psr-4": { 2057 | "React\\ChildProcess\\": "src/" 2058 | } 2059 | }, 2060 | "notification-url": "https://packagist.org/downloads/", 2061 | "license": [ 2062 | "MIT" 2063 | ], 2064 | "authors": [ 2065 | { 2066 | "name": "Christian Lück", 2067 | "email": "christian@clue.engineering", 2068 | "homepage": "https://clue.engineering/" 2069 | }, 2070 | { 2071 | "name": "Cees-Jan Kiewiet", 2072 | "email": "reactphp@ceesjankiewiet.nl", 2073 | "homepage": "https://wyrihaximus.net/" 2074 | }, 2075 | { 2076 | "name": "Jan Sorgalla", 2077 | "email": "jsorgalla@gmail.com", 2078 | "homepage": "https://sorgalla.com/" 2079 | }, 2080 | { 2081 | "name": "Chris Boden", 2082 | "email": "cboden@gmail.com", 2083 | "homepage": "https://cboden.dev/" 2084 | } 2085 | ], 2086 | "description": "Event-driven library for executing child processes with ReactPHP.", 2087 | "keywords": [ 2088 | "event-driven", 2089 | "process", 2090 | "reactphp" 2091 | ], 2092 | "support": { 2093 | "issues": "https://github.com/reactphp/child-process/issues", 2094 | "source": "https://github.com/reactphp/child-process/tree/v0.6.6" 2095 | }, 2096 | "funding": [ 2097 | { 2098 | "url": "https://opencollective.com/reactphp", 2099 | "type": "open_collective" 2100 | } 2101 | ], 2102 | "time": "2025-01-01T16:37:48+00:00" 2103 | }, 2104 | { 2105 | "name": "react/dns", 2106 | "version": "v1.13.0", 2107 | "source": { 2108 | "type": "git", 2109 | "url": "https://github.com/reactphp/dns.git", 2110 | "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5" 2111 | }, 2112 | "dist": { 2113 | "type": "zip", 2114 | "url": "https://api.github.com/repos/reactphp/dns/zipball/eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", 2115 | "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", 2116 | "shasum": "" 2117 | }, 2118 | "require": { 2119 | "php": ">=5.3.0", 2120 | "react/cache": "^1.0 || ^0.6 || ^0.5", 2121 | "react/event-loop": "^1.2", 2122 | "react/promise": "^3.2 || ^2.7 || ^1.2.1" 2123 | }, 2124 | "require-dev": { 2125 | "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", 2126 | "react/async": "^4.3 || ^3 || ^2", 2127 | "react/promise-timer": "^1.11" 2128 | }, 2129 | "type": "library", 2130 | "autoload": { 2131 | "psr-4": { 2132 | "React\\Dns\\": "src/" 2133 | } 2134 | }, 2135 | "notification-url": "https://packagist.org/downloads/", 2136 | "license": [ 2137 | "MIT" 2138 | ], 2139 | "authors": [ 2140 | { 2141 | "name": "Christian Lück", 2142 | "email": "christian@clue.engineering", 2143 | "homepage": "https://clue.engineering/" 2144 | }, 2145 | { 2146 | "name": "Cees-Jan Kiewiet", 2147 | "email": "reactphp@ceesjankiewiet.nl", 2148 | "homepage": "https://wyrihaximus.net/" 2149 | }, 2150 | { 2151 | "name": "Jan Sorgalla", 2152 | "email": "jsorgalla@gmail.com", 2153 | "homepage": "https://sorgalla.com/" 2154 | }, 2155 | { 2156 | "name": "Chris Boden", 2157 | "email": "cboden@gmail.com", 2158 | "homepage": "https://cboden.dev/" 2159 | } 2160 | ], 2161 | "description": "Async DNS resolver for ReactPHP", 2162 | "keywords": [ 2163 | "async", 2164 | "dns", 2165 | "dns-resolver", 2166 | "reactphp" 2167 | ], 2168 | "support": { 2169 | "issues": "https://github.com/reactphp/dns/issues", 2170 | "source": "https://github.com/reactphp/dns/tree/v1.13.0" 2171 | }, 2172 | "funding": [ 2173 | { 2174 | "url": "https://opencollective.com/reactphp", 2175 | "type": "open_collective" 2176 | } 2177 | ], 2178 | "time": "2024-06-13T14:18:03+00:00" 2179 | }, 2180 | { 2181 | "name": "react/event-loop", 2182 | "version": "v1.5.0", 2183 | "source": { 2184 | "type": "git", 2185 | "url": "https://github.com/reactphp/event-loop.git", 2186 | "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354" 2187 | }, 2188 | "dist": { 2189 | "type": "zip", 2190 | "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", 2191 | "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", 2192 | "shasum": "" 2193 | }, 2194 | "require": { 2195 | "php": ">=5.3.0" 2196 | }, 2197 | "require-dev": { 2198 | "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" 2199 | }, 2200 | "suggest": { 2201 | "ext-pcntl": "For signal handling support when using the StreamSelectLoop" 2202 | }, 2203 | "type": "library", 2204 | "autoload": { 2205 | "psr-4": { 2206 | "React\\EventLoop\\": "src/" 2207 | } 2208 | }, 2209 | "notification-url": "https://packagist.org/downloads/", 2210 | "license": [ 2211 | "MIT" 2212 | ], 2213 | "authors": [ 2214 | { 2215 | "name": "Christian Lück", 2216 | "email": "christian@clue.engineering", 2217 | "homepage": "https://clue.engineering/" 2218 | }, 2219 | { 2220 | "name": "Cees-Jan Kiewiet", 2221 | "email": "reactphp@ceesjankiewiet.nl", 2222 | "homepage": "https://wyrihaximus.net/" 2223 | }, 2224 | { 2225 | "name": "Jan Sorgalla", 2226 | "email": "jsorgalla@gmail.com", 2227 | "homepage": "https://sorgalla.com/" 2228 | }, 2229 | { 2230 | "name": "Chris Boden", 2231 | "email": "cboden@gmail.com", 2232 | "homepage": "https://cboden.dev/" 2233 | } 2234 | ], 2235 | "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", 2236 | "keywords": [ 2237 | "asynchronous", 2238 | "event-loop" 2239 | ], 2240 | "support": { 2241 | "issues": "https://github.com/reactphp/event-loop/issues", 2242 | "source": "https://github.com/reactphp/event-loop/tree/v1.5.0" 2243 | }, 2244 | "funding": [ 2245 | { 2246 | "url": "https://opencollective.com/reactphp", 2247 | "type": "open_collective" 2248 | } 2249 | ], 2250 | "time": "2023-11-13T13:48:05+00:00" 2251 | }, 2252 | { 2253 | "name": "react/promise", 2254 | "version": "v3.2.0", 2255 | "source": { 2256 | "type": "git", 2257 | "url": "https://github.com/reactphp/promise.git", 2258 | "reference": "8a164643313c71354582dc850b42b33fa12a4b63" 2259 | }, 2260 | "dist": { 2261 | "type": "zip", 2262 | "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", 2263 | "reference": "8a164643313c71354582dc850b42b33fa12a4b63", 2264 | "shasum": "" 2265 | }, 2266 | "require": { 2267 | "php": ">=7.1.0" 2268 | }, 2269 | "require-dev": { 2270 | "phpstan/phpstan": "1.10.39 || 1.4.10", 2271 | "phpunit/phpunit": "^9.6 || ^7.5" 2272 | }, 2273 | "type": "library", 2274 | "autoload": { 2275 | "files": [ 2276 | "src/functions_include.php" 2277 | ], 2278 | "psr-4": { 2279 | "React\\Promise\\": "src/" 2280 | } 2281 | }, 2282 | "notification-url": "https://packagist.org/downloads/", 2283 | "license": [ 2284 | "MIT" 2285 | ], 2286 | "authors": [ 2287 | { 2288 | "name": "Jan Sorgalla", 2289 | "email": "jsorgalla@gmail.com", 2290 | "homepage": "https://sorgalla.com/" 2291 | }, 2292 | { 2293 | "name": "Christian Lück", 2294 | "email": "christian@clue.engineering", 2295 | "homepage": "https://clue.engineering/" 2296 | }, 2297 | { 2298 | "name": "Cees-Jan Kiewiet", 2299 | "email": "reactphp@ceesjankiewiet.nl", 2300 | "homepage": "https://wyrihaximus.net/" 2301 | }, 2302 | { 2303 | "name": "Chris Boden", 2304 | "email": "cboden@gmail.com", 2305 | "homepage": "https://cboden.dev/" 2306 | } 2307 | ], 2308 | "description": "A lightweight implementation of CommonJS Promises/A for PHP", 2309 | "keywords": [ 2310 | "promise", 2311 | "promises" 2312 | ], 2313 | "support": { 2314 | "issues": "https://github.com/reactphp/promise/issues", 2315 | "source": "https://github.com/reactphp/promise/tree/v3.2.0" 2316 | }, 2317 | "funding": [ 2318 | { 2319 | "url": "https://opencollective.com/reactphp", 2320 | "type": "open_collective" 2321 | } 2322 | ], 2323 | "time": "2024-05-24T10:39:05+00:00" 2324 | }, 2325 | { 2326 | "name": "react/socket", 2327 | "version": "v1.16.0", 2328 | "source": { 2329 | "type": "git", 2330 | "url": "https://github.com/reactphp/socket.git", 2331 | "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1" 2332 | }, 2333 | "dist": { 2334 | "type": "zip", 2335 | "url": "https://api.github.com/repos/reactphp/socket/zipball/23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", 2336 | "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", 2337 | "shasum": "" 2338 | }, 2339 | "require": { 2340 | "evenement/evenement": "^3.0 || ^2.0 || ^1.0", 2341 | "php": ">=5.3.0", 2342 | "react/dns": "^1.13", 2343 | "react/event-loop": "^1.2", 2344 | "react/promise": "^3.2 || ^2.6 || ^1.2.1", 2345 | "react/stream": "^1.4" 2346 | }, 2347 | "require-dev": { 2348 | "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", 2349 | "react/async": "^4.3 || ^3.3 || ^2", 2350 | "react/promise-stream": "^1.4", 2351 | "react/promise-timer": "^1.11" 2352 | }, 2353 | "type": "library", 2354 | "autoload": { 2355 | "psr-4": { 2356 | "React\\Socket\\": "src/" 2357 | } 2358 | }, 2359 | "notification-url": "https://packagist.org/downloads/", 2360 | "license": [ 2361 | "MIT" 2362 | ], 2363 | "authors": [ 2364 | { 2365 | "name": "Christian Lück", 2366 | "email": "christian@clue.engineering", 2367 | "homepage": "https://clue.engineering/" 2368 | }, 2369 | { 2370 | "name": "Cees-Jan Kiewiet", 2371 | "email": "reactphp@ceesjankiewiet.nl", 2372 | "homepage": "https://wyrihaximus.net/" 2373 | }, 2374 | { 2375 | "name": "Jan Sorgalla", 2376 | "email": "jsorgalla@gmail.com", 2377 | "homepage": "https://sorgalla.com/" 2378 | }, 2379 | { 2380 | "name": "Chris Boden", 2381 | "email": "cboden@gmail.com", 2382 | "homepage": "https://cboden.dev/" 2383 | } 2384 | ], 2385 | "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", 2386 | "keywords": [ 2387 | "Connection", 2388 | "Socket", 2389 | "async", 2390 | "reactphp", 2391 | "stream" 2392 | ], 2393 | "support": { 2394 | "issues": "https://github.com/reactphp/socket/issues", 2395 | "source": "https://github.com/reactphp/socket/tree/v1.16.0" 2396 | }, 2397 | "funding": [ 2398 | { 2399 | "url": "https://opencollective.com/reactphp", 2400 | "type": "open_collective" 2401 | } 2402 | ], 2403 | "time": "2024-07-26T10:38:09+00:00" 2404 | }, 2405 | { 2406 | "name": "react/stream", 2407 | "version": "v1.4.0", 2408 | "source": { 2409 | "type": "git", 2410 | "url": "https://github.com/reactphp/stream.git", 2411 | "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" 2412 | }, 2413 | "dist": { 2414 | "type": "zip", 2415 | "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", 2416 | "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", 2417 | "shasum": "" 2418 | }, 2419 | "require": { 2420 | "evenement/evenement": "^3.0 || ^2.0 || ^1.0", 2421 | "php": ">=5.3.8", 2422 | "react/event-loop": "^1.2" 2423 | }, 2424 | "require-dev": { 2425 | "clue/stream-filter": "~1.2", 2426 | "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" 2427 | }, 2428 | "type": "library", 2429 | "autoload": { 2430 | "psr-4": { 2431 | "React\\Stream\\": "src/" 2432 | } 2433 | }, 2434 | "notification-url": "https://packagist.org/downloads/", 2435 | "license": [ 2436 | "MIT" 2437 | ], 2438 | "authors": [ 2439 | { 2440 | "name": "Christian Lück", 2441 | "email": "christian@clue.engineering", 2442 | "homepage": "https://clue.engineering/" 2443 | }, 2444 | { 2445 | "name": "Cees-Jan Kiewiet", 2446 | "email": "reactphp@ceesjankiewiet.nl", 2447 | "homepage": "https://wyrihaximus.net/" 2448 | }, 2449 | { 2450 | "name": "Jan Sorgalla", 2451 | "email": "jsorgalla@gmail.com", 2452 | "homepage": "https://sorgalla.com/" 2453 | }, 2454 | { 2455 | "name": "Chris Boden", 2456 | "email": "cboden@gmail.com", 2457 | "homepage": "https://cboden.dev/" 2458 | } 2459 | ], 2460 | "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", 2461 | "keywords": [ 2462 | "event-driven", 2463 | "io", 2464 | "non-blocking", 2465 | "pipe", 2466 | "reactphp", 2467 | "readable", 2468 | "stream", 2469 | "writable" 2470 | ], 2471 | "support": { 2472 | "issues": "https://github.com/reactphp/stream/issues", 2473 | "source": "https://github.com/reactphp/stream/tree/v1.4.0" 2474 | }, 2475 | "funding": [ 2476 | { 2477 | "url": "https://opencollective.com/reactphp", 2478 | "type": "open_collective" 2479 | } 2480 | ], 2481 | "time": "2024-06-11T12:45:25+00:00" 2482 | }, 2483 | { 2484 | "name": "sebastian/diff", 2485 | "version": "5.1.1", 2486 | "source": { 2487 | "type": "git", 2488 | "url": "https://github.com/sebastianbergmann/diff.git", 2489 | "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" 2490 | }, 2491 | "dist": { 2492 | "type": "zip", 2493 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", 2494 | "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", 2495 | "shasum": "" 2496 | }, 2497 | "require": { 2498 | "php": ">=8.1" 2499 | }, 2500 | "require-dev": { 2501 | "phpunit/phpunit": "^10.0", 2502 | "symfony/process": "^6.4" 2503 | }, 2504 | "type": "library", 2505 | "extra": { 2506 | "branch-alias": { 2507 | "dev-main": "5.1-dev" 2508 | } 2509 | }, 2510 | "autoload": { 2511 | "classmap": [ 2512 | "src/" 2513 | ] 2514 | }, 2515 | "notification-url": "https://packagist.org/downloads/", 2516 | "license": [ 2517 | "BSD-3-Clause" 2518 | ], 2519 | "authors": [ 2520 | { 2521 | "name": "Sebastian Bergmann", 2522 | "email": "sebastian@phpunit.de" 2523 | }, 2524 | { 2525 | "name": "Kore Nordmann", 2526 | "email": "mail@kore-nordmann.de" 2527 | } 2528 | ], 2529 | "description": "Diff implementation", 2530 | "homepage": "https://github.com/sebastianbergmann/diff", 2531 | "keywords": [ 2532 | "diff", 2533 | "udiff", 2534 | "unidiff", 2535 | "unified diff" 2536 | ], 2537 | "support": { 2538 | "issues": "https://github.com/sebastianbergmann/diff/issues", 2539 | "security": "https://github.com/sebastianbergmann/diff/security/policy", 2540 | "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" 2541 | }, 2542 | "funding": [ 2543 | { 2544 | "url": "https://github.com/sebastianbergmann", 2545 | "type": "github" 2546 | } 2547 | ], 2548 | "time": "2024-03-02T07:15:17+00:00" 2549 | }, 2550 | { 2551 | "name": "symfony/event-dispatcher", 2552 | "version": "v6.4.13", 2553 | "source": { 2554 | "type": "git", 2555 | "url": "https://github.com/symfony/event-dispatcher.git", 2556 | "reference": "0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e" 2557 | }, 2558 | "dist": { 2559 | "type": "zip", 2560 | "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e", 2561 | "reference": "0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e", 2562 | "shasum": "" 2563 | }, 2564 | "require": { 2565 | "php": ">=8.1", 2566 | "symfony/event-dispatcher-contracts": "^2.5|^3" 2567 | }, 2568 | "conflict": { 2569 | "symfony/dependency-injection": "<5.4", 2570 | "symfony/service-contracts": "<2.5" 2571 | }, 2572 | "provide": { 2573 | "psr/event-dispatcher-implementation": "1.0", 2574 | "symfony/event-dispatcher-implementation": "2.0|3.0" 2575 | }, 2576 | "require-dev": { 2577 | "psr/log": "^1|^2|^3", 2578 | "symfony/config": "^5.4|^6.0|^7.0", 2579 | "symfony/dependency-injection": "^5.4|^6.0|^7.0", 2580 | "symfony/error-handler": "^5.4|^6.0|^7.0", 2581 | "symfony/expression-language": "^5.4|^6.0|^7.0", 2582 | "symfony/http-foundation": "^5.4|^6.0|^7.0", 2583 | "symfony/service-contracts": "^2.5|^3", 2584 | "symfony/stopwatch": "^5.4|^6.0|^7.0" 2585 | }, 2586 | "type": "library", 2587 | "autoload": { 2588 | "psr-4": { 2589 | "Symfony\\Component\\EventDispatcher\\": "" 2590 | }, 2591 | "exclude-from-classmap": [ 2592 | "/Tests/" 2593 | ] 2594 | }, 2595 | "notification-url": "https://packagist.org/downloads/", 2596 | "license": [ 2597 | "MIT" 2598 | ], 2599 | "authors": [ 2600 | { 2601 | "name": "Fabien Potencier", 2602 | "email": "fabien@symfony.com" 2603 | }, 2604 | { 2605 | "name": "Symfony Community", 2606 | "homepage": "https://symfony.com/contributors" 2607 | } 2608 | ], 2609 | "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", 2610 | "homepage": "https://symfony.com", 2611 | "support": { 2612 | "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.13" 2613 | }, 2614 | "funding": [ 2615 | { 2616 | "url": "https://symfony.com/sponsor", 2617 | "type": "custom" 2618 | }, 2619 | { 2620 | "url": "https://github.com/fabpot", 2621 | "type": "github" 2622 | }, 2623 | { 2624 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2625 | "type": "tidelift" 2626 | } 2627 | ], 2628 | "time": "2024-09-25T14:18:03+00:00" 2629 | }, 2630 | { 2631 | "name": "symfony/event-dispatcher-contracts", 2632 | "version": "v3.5.1", 2633 | "source": { 2634 | "type": "git", 2635 | "url": "https://github.com/symfony/event-dispatcher-contracts.git", 2636 | "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f" 2637 | }, 2638 | "dist": { 2639 | "type": "zip", 2640 | "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7642f5e970b672283b7823222ae8ef8bbc160b9f", 2641 | "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f", 2642 | "shasum": "" 2643 | }, 2644 | "require": { 2645 | "php": ">=8.1", 2646 | "psr/event-dispatcher": "^1" 2647 | }, 2648 | "type": "library", 2649 | "extra": { 2650 | "thanks": { 2651 | "url": "https://github.com/symfony/contracts", 2652 | "name": "symfony/contracts" 2653 | }, 2654 | "branch-alias": { 2655 | "dev-main": "3.5-dev" 2656 | } 2657 | }, 2658 | "autoload": { 2659 | "psr-4": { 2660 | "Symfony\\Contracts\\EventDispatcher\\": "" 2661 | } 2662 | }, 2663 | "notification-url": "https://packagist.org/downloads/", 2664 | "license": [ 2665 | "MIT" 2666 | ], 2667 | "authors": [ 2668 | { 2669 | "name": "Nicolas Grekas", 2670 | "email": "p@tchwork.com" 2671 | }, 2672 | { 2673 | "name": "Symfony Community", 2674 | "homepage": "https://symfony.com/contributors" 2675 | } 2676 | ], 2677 | "description": "Generic abstractions related to dispatching event", 2678 | "homepage": "https://symfony.com", 2679 | "keywords": [ 2680 | "abstractions", 2681 | "contracts", 2682 | "decoupling", 2683 | "interfaces", 2684 | "interoperability", 2685 | "standards" 2686 | ], 2687 | "support": { 2688 | "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.1" 2689 | }, 2690 | "funding": [ 2691 | { 2692 | "url": "https://symfony.com/sponsor", 2693 | "type": "custom" 2694 | }, 2695 | { 2696 | "url": "https://github.com/fabpot", 2697 | "type": "github" 2698 | }, 2699 | { 2700 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2701 | "type": "tidelift" 2702 | } 2703 | ], 2704 | "time": "2024-09-25T14:20:29+00:00" 2705 | }, 2706 | { 2707 | "name": "symfony/filesystem", 2708 | "version": "v6.4.13", 2709 | "source": { 2710 | "type": "git", 2711 | "url": "https://github.com/symfony/filesystem.git", 2712 | "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3" 2713 | }, 2714 | "dist": { 2715 | "type": "zip", 2716 | "url": "https://api.github.com/repos/symfony/filesystem/zipball/4856c9cf585d5a0313d8d35afd681a526f038dd3", 2717 | "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3", 2718 | "shasum": "" 2719 | }, 2720 | "require": { 2721 | "php": ">=8.1", 2722 | "symfony/polyfill-ctype": "~1.8", 2723 | "symfony/polyfill-mbstring": "~1.8" 2724 | }, 2725 | "require-dev": { 2726 | "symfony/process": "^5.4|^6.4|^7.0" 2727 | }, 2728 | "type": "library", 2729 | "autoload": { 2730 | "psr-4": { 2731 | "Symfony\\Component\\Filesystem\\": "" 2732 | }, 2733 | "exclude-from-classmap": [ 2734 | "/Tests/" 2735 | ] 2736 | }, 2737 | "notification-url": "https://packagist.org/downloads/", 2738 | "license": [ 2739 | "MIT" 2740 | ], 2741 | "authors": [ 2742 | { 2743 | "name": "Fabien Potencier", 2744 | "email": "fabien@symfony.com" 2745 | }, 2746 | { 2747 | "name": "Symfony Community", 2748 | "homepage": "https://symfony.com/contributors" 2749 | } 2750 | ], 2751 | "description": "Provides basic utilities for the filesystem", 2752 | "homepage": "https://symfony.com", 2753 | "support": { 2754 | "source": "https://github.com/symfony/filesystem/tree/v6.4.13" 2755 | }, 2756 | "funding": [ 2757 | { 2758 | "url": "https://symfony.com/sponsor", 2759 | "type": "custom" 2760 | }, 2761 | { 2762 | "url": "https://github.com/fabpot", 2763 | "type": "github" 2764 | }, 2765 | { 2766 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2767 | "type": "tidelift" 2768 | } 2769 | ], 2770 | "time": "2024-10-25T15:07:50+00:00" 2771 | }, 2772 | { 2773 | "name": "symfony/finder", 2774 | "version": "v6.4.17", 2775 | "source": { 2776 | "type": "git", 2777 | "url": "https://github.com/symfony/finder.git", 2778 | "reference": "1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7" 2779 | }, 2780 | "dist": { 2781 | "type": "zip", 2782 | "url": "https://api.github.com/repos/symfony/finder/zipball/1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7", 2783 | "reference": "1d0e8266248c5d9ab6a87e3789e6dc482af3c9c7", 2784 | "shasum": "" 2785 | }, 2786 | "require": { 2787 | "php": ">=8.1" 2788 | }, 2789 | "require-dev": { 2790 | "symfony/filesystem": "^6.0|^7.0" 2791 | }, 2792 | "type": "library", 2793 | "autoload": { 2794 | "psr-4": { 2795 | "Symfony\\Component\\Finder\\": "" 2796 | }, 2797 | "exclude-from-classmap": [ 2798 | "/Tests/" 2799 | ] 2800 | }, 2801 | "notification-url": "https://packagist.org/downloads/", 2802 | "license": [ 2803 | "MIT" 2804 | ], 2805 | "authors": [ 2806 | { 2807 | "name": "Fabien Potencier", 2808 | "email": "fabien@symfony.com" 2809 | }, 2810 | { 2811 | "name": "Symfony Community", 2812 | "homepage": "https://symfony.com/contributors" 2813 | } 2814 | ], 2815 | "description": "Finds files and directories via an intuitive fluent interface", 2816 | "homepage": "https://symfony.com", 2817 | "support": { 2818 | "source": "https://github.com/symfony/finder/tree/v6.4.17" 2819 | }, 2820 | "funding": [ 2821 | { 2822 | "url": "https://symfony.com/sponsor", 2823 | "type": "custom" 2824 | }, 2825 | { 2826 | "url": "https://github.com/fabpot", 2827 | "type": "github" 2828 | }, 2829 | { 2830 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2831 | "type": "tidelift" 2832 | } 2833 | ], 2834 | "time": "2024-12-29T13:51:37+00:00" 2835 | }, 2836 | { 2837 | "name": "symfony/options-resolver", 2838 | "version": "v6.4.16", 2839 | "source": { 2840 | "type": "git", 2841 | "url": "https://github.com/symfony/options-resolver.git", 2842 | "reference": "368128ad168f20e22c32159b9f761e456cec0c78" 2843 | }, 2844 | "dist": { 2845 | "type": "zip", 2846 | "url": "https://api.github.com/repos/symfony/options-resolver/zipball/368128ad168f20e22c32159b9f761e456cec0c78", 2847 | "reference": "368128ad168f20e22c32159b9f761e456cec0c78", 2848 | "shasum": "" 2849 | }, 2850 | "require": { 2851 | "php": ">=8.1", 2852 | "symfony/deprecation-contracts": "^2.5|^3" 2853 | }, 2854 | "type": "library", 2855 | "autoload": { 2856 | "psr-4": { 2857 | "Symfony\\Component\\OptionsResolver\\": "" 2858 | }, 2859 | "exclude-from-classmap": [ 2860 | "/Tests/" 2861 | ] 2862 | }, 2863 | "notification-url": "https://packagist.org/downloads/", 2864 | "license": [ 2865 | "MIT" 2866 | ], 2867 | "authors": [ 2868 | { 2869 | "name": "Fabien Potencier", 2870 | "email": "fabien@symfony.com" 2871 | }, 2872 | { 2873 | "name": "Symfony Community", 2874 | "homepage": "https://symfony.com/contributors" 2875 | } 2876 | ], 2877 | "description": "Provides an improved replacement for the array_replace PHP function", 2878 | "homepage": "https://symfony.com", 2879 | "keywords": [ 2880 | "config", 2881 | "configuration", 2882 | "options" 2883 | ], 2884 | "support": { 2885 | "source": "https://github.com/symfony/options-resolver/tree/v6.4.16" 2886 | }, 2887 | "funding": [ 2888 | { 2889 | "url": "https://symfony.com/sponsor", 2890 | "type": "custom" 2891 | }, 2892 | { 2893 | "url": "https://github.com/fabpot", 2894 | "type": "github" 2895 | }, 2896 | { 2897 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2898 | "type": "tidelift" 2899 | } 2900 | ], 2901 | "time": "2024-11-20T10:57:02+00:00" 2902 | }, 2903 | { 2904 | "name": "symfony/polyfill-php80", 2905 | "version": "v1.31.0", 2906 | "source": { 2907 | "type": "git", 2908 | "url": "https://github.com/symfony/polyfill-php80.git", 2909 | "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" 2910 | }, 2911 | "dist": { 2912 | "type": "zip", 2913 | "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", 2914 | "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", 2915 | "shasum": "" 2916 | }, 2917 | "require": { 2918 | "php": ">=7.2" 2919 | }, 2920 | "type": "library", 2921 | "extra": { 2922 | "thanks": { 2923 | "url": "https://github.com/symfony/polyfill", 2924 | "name": "symfony/polyfill" 2925 | } 2926 | }, 2927 | "autoload": { 2928 | "files": [ 2929 | "bootstrap.php" 2930 | ], 2931 | "psr-4": { 2932 | "Symfony\\Polyfill\\Php80\\": "" 2933 | }, 2934 | "classmap": [ 2935 | "Resources/stubs" 2936 | ] 2937 | }, 2938 | "notification-url": "https://packagist.org/downloads/", 2939 | "license": [ 2940 | "MIT" 2941 | ], 2942 | "authors": [ 2943 | { 2944 | "name": "Ion Bazan", 2945 | "email": "ion.bazan@gmail.com" 2946 | }, 2947 | { 2948 | "name": "Nicolas Grekas", 2949 | "email": "p@tchwork.com" 2950 | }, 2951 | { 2952 | "name": "Symfony Community", 2953 | "homepage": "https://symfony.com/contributors" 2954 | } 2955 | ], 2956 | "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", 2957 | "homepage": "https://symfony.com", 2958 | "keywords": [ 2959 | "compatibility", 2960 | "polyfill", 2961 | "portable", 2962 | "shim" 2963 | ], 2964 | "support": { 2965 | "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" 2966 | }, 2967 | "funding": [ 2968 | { 2969 | "url": "https://symfony.com/sponsor", 2970 | "type": "custom" 2971 | }, 2972 | { 2973 | "url": "https://github.com/fabpot", 2974 | "type": "github" 2975 | }, 2976 | { 2977 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2978 | "type": "tidelift" 2979 | } 2980 | ], 2981 | "time": "2024-09-09T11:45:10+00:00" 2982 | }, 2983 | { 2984 | "name": "symfony/polyfill-php81", 2985 | "version": "v1.31.0", 2986 | "source": { 2987 | "type": "git", 2988 | "url": "https://github.com/symfony/polyfill-php81.git", 2989 | "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" 2990 | }, 2991 | "dist": { 2992 | "type": "zip", 2993 | "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", 2994 | "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", 2995 | "shasum": "" 2996 | }, 2997 | "require": { 2998 | "php": ">=7.2" 2999 | }, 3000 | "type": "library", 3001 | "extra": { 3002 | "thanks": { 3003 | "url": "https://github.com/symfony/polyfill", 3004 | "name": "symfony/polyfill" 3005 | } 3006 | }, 3007 | "autoload": { 3008 | "files": [ 3009 | "bootstrap.php" 3010 | ], 3011 | "psr-4": { 3012 | "Symfony\\Polyfill\\Php81\\": "" 3013 | }, 3014 | "classmap": [ 3015 | "Resources/stubs" 3016 | ] 3017 | }, 3018 | "notification-url": "https://packagist.org/downloads/", 3019 | "license": [ 3020 | "MIT" 3021 | ], 3022 | "authors": [ 3023 | { 3024 | "name": "Nicolas Grekas", 3025 | "email": "p@tchwork.com" 3026 | }, 3027 | { 3028 | "name": "Symfony Community", 3029 | "homepage": "https://symfony.com/contributors" 3030 | } 3031 | ], 3032 | "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", 3033 | "homepage": "https://symfony.com", 3034 | "keywords": [ 3035 | "compatibility", 3036 | "polyfill", 3037 | "portable", 3038 | "shim" 3039 | ], 3040 | "support": { 3041 | "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" 3042 | }, 3043 | "funding": [ 3044 | { 3045 | "url": "https://symfony.com/sponsor", 3046 | "type": "custom" 3047 | }, 3048 | { 3049 | "url": "https://github.com/fabpot", 3050 | "type": "github" 3051 | }, 3052 | { 3053 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 3054 | "type": "tidelift" 3055 | } 3056 | ], 3057 | "time": "2024-09-09T11:45:10+00:00" 3058 | }, 3059 | { 3060 | "name": "symfony/process", 3061 | "version": "v6.4.15", 3062 | "source": { 3063 | "type": "git", 3064 | "url": "https://github.com/symfony/process.git", 3065 | "reference": "3cb242f059c14ae08591c5c4087d1fe443564392" 3066 | }, 3067 | "dist": { 3068 | "type": "zip", 3069 | "url": "https://api.github.com/repos/symfony/process/zipball/3cb242f059c14ae08591c5c4087d1fe443564392", 3070 | "reference": "3cb242f059c14ae08591c5c4087d1fe443564392", 3071 | "shasum": "" 3072 | }, 3073 | "require": { 3074 | "php": ">=8.1" 3075 | }, 3076 | "type": "library", 3077 | "autoload": { 3078 | "psr-4": { 3079 | "Symfony\\Component\\Process\\": "" 3080 | }, 3081 | "exclude-from-classmap": [ 3082 | "/Tests/" 3083 | ] 3084 | }, 3085 | "notification-url": "https://packagist.org/downloads/", 3086 | "license": [ 3087 | "MIT" 3088 | ], 3089 | "authors": [ 3090 | { 3091 | "name": "Fabien Potencier", 3092 | "email": "fabien@symfony.com" 3093 | }, 3094 | { 3095 | "name": "Symfony Community", 3096 | "homepage": "https://symfony.com/contributors" 3097 | } 3098 | ], 3099 | "description": "Executes commands in sub-processes", 3100 | "homepage": "https://symfony.com", 3101 | "support": { 3102 | "source": "https://github.com/symfony/process/tree/v6.4.15" 3103 | }, 3104 | "funding": [ 3105 | { 3106 | "url": "https://symfony.com/sponsor", 3107 | "type": "custom" 3108 | }, 3109 | { 3110 | "url": "https://github.com/fabpot", 3111 | "type": "github" 3112 | }, 3113 | { 3114 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 3115 | "type": "tidelift" 3116 | } 3117 | ], 3118 | "time": "2024-11-06T14:19:14+00:00" 3119 | }, 3120 | { 3121 | "name": "symfony/stopwatch", 3122 | "version": "v6.4.13", 3123 | "source": { 3124 | "type": "git", 3125 | "url": "https://github.com/symfony/stopwatch.git", 3126 | "reference": "2cae0a6f8d04937d02f6d19806251e2104d54f92" 3127 | }, 3128 | "dist": { 3129 | "type": "zip", 3130 | "url": "https://api.github.com/repos/symfony/stopwatch/zipball/2cae0a6f8d04937d02f6d19806251e2104d54f92", 3131 | "reference": "2cae0a6f8d04937d02f6d19806251e2104d54f92", 3132 | "shasum": "" 3133 | }, 3134 | "require": { 3135 | "php": ">=8.1", 3136 | "symfony/service-contracts": "^2.5|^3" 3137 | }, 3138 | "type": "library", 3139 | "autoload": { 3140 | "psr-4": { 3141 | "Symfony\\Component\\Stopwatch\\": "" 3142 | }, 3143 | "exclude-from-classmap": [ 3144 | "/Tests/" 3145 | ] 3146 | }, 3147 | "notification-url": "https://packagist.org/downloads/", 3148 | "license": [ 3149 | "MIT" 3150 | ], 3151 | "authors": [ 3152 | { 3153 | "name": "Fabien Potencier", 3154 | "email": "fabien@symfony.com" 3155 | }, 3156 | { 3157 | "name": "Symfony Community", 3158 | "homepage": "https://symfony.com/contributors" 3159 | } 3160 | ], 3161 | "description": "Provides a way to profile code", 3162 | "homepage": "https://symfony.com", 3163 | "support": { 3164 | "source": "https://github.com/symfony/stopwatch/tree/v6.4.13" 3165 | }, 3166 | "funding": [ 3167 | { 3168 | "url": "https://symfony.com/sponsor", 3169 | "type": "custom" 3170 | }, 3171 | { 3172 | "url": "https://github.com/fabpot", 3173 | "type": "github" 3174 | }, 3175 | { 3176 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 3177 | "type": "tidelift" 3178 | } 3179 | ], 3180 | "time": "2024-09-25T14:18:03+00:00" 3181 | } 3182 | ], 3183 | "aliases": [], 3184 | "minimum-stability": "stable", 3185 | "stability-flags": {}, 3186 | "prefer-stable": false, 3187 | "prefer-lowest": false, 3188 | "platform": { 3189 | "php": "8.1.*||8.2.*||8.3.*||8.4.*", 3190 | "ext-iconv": "*", 3191 | "ext-json": "*", 3192 | "ext-curl": "*", 3193 | "ext-mbstring": "*" 3194 | }, 3195 | "platform-dev": {}, 3196 | "platform-overrides": { 3197 | "php": "8.1" 3198 | }, 3199 | "plugin-api-version": "2.6.0" 3200 | } 3201 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | includes: 2 | - %rootDir%/../phpstan/conf/bleedingEdge.neon 3 | 4 | parameters: 5 | level: 5 6 | tmpDir: %rootDir%/../../../var/phpstan 7 | treatPhpDocTypesAsCertain: false 8 | inferPrivatePropertyTypeFromConstructor: true 9 | -------------------------------------------------------------------------------- /src/Api/Configuration.php: -------------------------------------------------------------------------------- 1 | } $settings 16 | * @throws \Exception 17 | */ 18 | public function __construct(private readonly array $settings) 19 | { 20 | $this->validate($settings); 21 | } 22 | 23 | public function getApiToken(): string 24 | { 25 | return $this->settings['API_TOKEN']; 26 | } 27 | 28 | public function getUrl(): string 29 | { 30 | return $this->settings['URL']; 31 | } 32 | 33 | /** 34 | * @return array 35 | */ 36 | public function getCurlOptions(): array 37 | { 38 | if (!\array_key_exists('OPTIONS', $this->settings)) { 39 | return []; 40 | } 41 | 42 | return $this->settings['OPTIONS']; 43 | } 44 | 45 | /** 46 | * @return array 47 | */ 48 | public static function getDefaultConfiguration(): array 49 | { 50 | return [ 51 | 'URL' => 'https://demo.kimai.org/', 52 | 'API_TOKEN' => 'token_super', 53 | ]; 54 | } 55 | 56 | public static function getFilename(): string 57 | { 58 | if (false === ($filename = getenv('KIMAI_CONFIG'))) { 59 | $old = getenv('HOME') . DIRECTORY_SEPARATOR . '.kimai2-console.json'; 60 | $new = getenv('HOME') . DIRECTORY_SEPARATOR . '.kimai-api.json'; 61 | if (file_exists($old)) { 62 | rename($old, $new); 63 | } 64 | $filename = $new; 65 | } 66 | 67 | return $filename; 68 | } 69 | 70 | /** 71 | * @param array{URL: string, API_TOKEN: string, OPTIONS: null|array} $settings 72 | * @throws \Exception 73 | */ 74 | private function validate(array $settings): bool 75 | { 76 | if (!\array_key_exists('URL', $settings) || $settings['URL'] === '') { 77 | throw new \Exception('Missing API URL with the key "URL"'); 78 | } 79 | 80 | if (!\array_key_exists('API_TOKEN', $settings) || $settings['API_TOKEN'] === '') { 81 | throw new \Exception('Missing API token with the key "API_TOKEN"'); 82 | } 83 | 84 | if (\array_key_exists('OPTIONS', $settings) && !\is_array($settings['OPTIONS'])) { 85 | throw new \Exception('Invalid config "OPTIONS": key-value array expected'); 86 | } 87 | 88 | return true; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Api/Connection.php: -------------------------------------------------------------------------------- 1 | configuration = SwaggerConfiguration::getDefaultConfiguration(); 24 | $this->configuration->setAccessToken($configuration->getApiToken()); 25 | $this->configuration->setHost(rtrim($configuration->getUrl(), '/')); 26 | 27 | $clientOptions = $configuration->getCurlOptions(); 28 | $this->client = new Client($clientOptions); 29 | 30 | $apiInstance = new DefaultApi($this->client, $this->configuration); 31 | $apiInstance->getAppApiStatusPing(); 32 | } 33 | 34 | public function getClient(): Client 35 | { 36 | return $this->client; 37 | } 38 | 39 | public function getConfiguration(): SwaggerConfiguration 40 | { 41 | return $this->configuration; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Application.php: -------------------------------------------------------------------------------- 1 | %s version %s %s (#%s)', $this->getName(), $this->getVersion(), Constants::DATE, Constants::GIT); 41 | } 42 | 43 | public function doRun(InputInterface $input, OutputInterface $output): int 44 | { 45 | $io = new SymfonyStyle($input, $output); 46 | 47 | try { 48 | if ($input->hasParameterOption('--profile')) { 49 | $startTime = microtime(true); 50 | } 51 | 52 | $result = parent::doRun($input, $output); 53 | 54 | if (isset($startTime)) { 55 | $io->writeln('Memory usage: ' . round(memory_get_usage() / 1024 / 1024, 2) . 'MiB (peak: ' . round(memory_get_peak_usage() / 1024 / 1024, 2) . 'MiB), time: ' . round(microtime(true) - $startTime, 2) . 's'); 56 | } 57 | 58 | return $result; 59 | } catch (InvalidConfigurationException $e) { 60 | $io->error([ 61 | $e->getMessage(), 62 | 'You can create a default configuration with: bin/kimai dump-configuration' 63 | ]); 64 | 65 | return 1; 66 | } catch (ConnectionProblemException $e) { 67 | $io->error([$e->getMessage()]); 68 | 69 | return 2; 70 | } catch (\Exception $e) { 71 | $io->error('Failed execution: ' . $e->getMessage()); 72 | 73 | return 3; 74 | } 75 | } 76 | 77 | protected function getDefaultCommands(): array 78 | { 79 | return [ 80 | new HelpCommand(), 81 | new ListCommand(), 82 | new Command\ActiveCommand(), 83 | new Command\StartCommand(), 84 | new Command\StopCommand(), 85 | new Command\ActivityListCommand(), 86 | new Command\ProjectListCommand(), 87 | new Command\CustomerListCommand(), 88 | new Command\ConfigurationCommand(), 89 | new Command\VersionCommand(), 90 | ]; 91 | } 92 | 93 | protected function getDefaultInputDefinition(): InputDefinition 94 | { 95 | $definition = parent::getDefaultInputDefinition(); 96 | $definition->addOption(new InputOption('--profile', null, InputOption::VALUE_NONE, 'Display timing and memory usage information')); 97 | $definition->addOption(new InputOption('--csv', null, InputOption::VALUE_NONE, 'Render raw data instead of styled tables')); 98 | //$definition->addOption(new InputOption('--json', null, InputOption::VALUE_NONE, 'Render JSON data instead of styled tables')); 99 | 100 | return $definition; 101 | } 102 | 103 | public function getHelp(): string 104 | { 105 | return self::$logo . parent::getHelp(); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/Command/ActiveCommand.php: -------------------------------------------------------------------------------- 1 | setName('active') 24 | ->setDescription('List active timesheets, update description and tags') 25 | ->setHelp('This command shows all currently running timesheet records and lets you update them') 26 | ->addOption('description', 'd', InputOption::VALUE_OPTIONAL, 'Set the given description or if none was given, you will be prompted for one.', false) 27 | ->addOption('tags', 't', InputOption::VALUE_OPTIONAL, 'Set the given (comma separated list) of tags or if no tags were given, you will be prompted for them.', false) 28 | ; 29 | } 30 | 31 | protected function execute(InputInterface $input, OutputInterface $output): int 32 | { 33 | $io = new SymfonyStyle($input, $output); 34 | 35 | $api = $this->getTimesheetApi(); 36 | $running = $api->getActiveTimesheet(); 37 | 38 | if (\count($running) === 0) { 39 | $io->writeln('You have no active timesheets'); 40 | 41 | return 0; 42 | } 43 | 44 | $update = false; 45 | if (false !== ($description = $input->getOption('description'))) { 46 | $update = true; 47 | } 48 | if (false !== ($tags = $input->getOption('tags'))) { 49 | $update = true; 50 | } 51 | 52 | $rows = []; 53 | 54 | if (!$update) { 55 | foreach ($running as $timesheet) { 56 | $rows[] = [ 57 | $timesheet->getId(), 58 | $timesheet->getBegin()->format(\DateTime::ISO8601), 59 | $timesheet->getActivity() !== null ? $timesheet->getActivity()->getName() : '', 60 | $timesheet->getProject() !== null ? $timesheet->getProject()->getName() : '', 61 | $timesheet->getProject() !== null ? $timesheet->getProject()->getCustomer()->getName() : '', 62 | $timesheet->getDescription() !== null ? $timesheet->getDescription() : '', 63 | implode(', ', $timesheet->getTags()), 64 | ]; 65 | } 66 | 67 | $this->formatOutput($input, $output, ['ID', 'Started at', 'Activity', 'Project', 'Customer', 'Description', 'Tags'], $rows); 68 | } else { 69 | $timesheet = $this->getSelectedTimesheet($io, $running); 70 | 71 | try { 72 | $this->updateTimesheet($io, $timesheet, $description, $tags); 73 | 74 | $io->success('Updated timesheet'); 75 | } catch (ApiException $ex) { 76 | $this->renderApiException($input, $io, $ex, 'Failed updating timesheet'); 77 | 78 | return 1; 79 | } 80 | } 81 | 82 | return 0; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Command/ActivityListCommand.php: -------------------------------------------------------------------------------- 1 | setName('activity:list') 22 | ->setDescription('List available activities') 23 | ->setHelp('This command lets you search for activities') 24 | ->addOption('project', null, InputOption::VALUE_OPTIONAL, 'The project to be filtered', null) 25 | ->addOption('term', null, InputOption::VALUE_OPTIONAL, 'A search term to filter activities', null) 26 | ->addOption('globals', null, InputOption::VALUE_NONE, 'Show global activities only (will ignore project option)') 27 | ; 28 | } 29 | 30 | protected function execute(InputInterface $input, OutputInterface $output): int 31 | { 32 | $project = null; 33 | $term = null; 34 | 35 | if (null !== $input->getOption('project')) { 36 | $project = $input->getOption('project'); 37 | } 38 | 39 | if (null !== $input->getOption('term')) { 40 | $term = $input->getOption('term'); 41 | } 42 | 43 | $api = $this->getActivityApi(); 44 | 45 | $visible = '1'; 46 | $globals = null; 47 | $order_by = 'project'; 48 | $order = null; 49 | 50 | if ($input->getOption('globals')) { 51 | $globals = '1'; 52 | } 53 | 54 | $collection = $api->getGetActivities( 55 | $project, 56 | null, // @phpstan-ignore argument.type 57 | $visible, 58 | $globals, 59 | $order_by, 60 | $order, 61 | $term 62 | ); 63 | 64 | $rows = []; 65 | foreach ($collection as $activity) { 66 | $projectId = ''; 67 | $projectName = ''; 68 | if (!empty($activity->getProject())) { 69 | $projectId = $activity->getProject(); 70 | $projectName = $activity->getParentTitle(); 71 | } 72 | $rows[] = [ 73 | $activity->getId(), 74 | $activity->getName(), 75 | $projectId, 76 | $projectName, 77 | ]; 78 | } 79 | 80 | $this->formatOutput($input, $output, ['Id', 'Name', 'Project ID', 'Project Name'], $rows); 81 | 82 | return 0; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Command/BaseCommand.php: -------------------------------------------------------------------------------- 1 | getConnection(); 36 | 37 | return new DefaultApi($connection->getClient(), $connection->getConfiguration()); 38 | } 39 | 40 | protected function getTimesheetApi(): TimesheetApi 41 | { 42 | $connection = $this->getConnection(); 43 | 44 | return new TimesheetApi($connection->getClient(), $connection->getConfiguration()); 45 | } 46 | 47 | protected function getProjectApi(): ProjectApi 48 | { 49 | $connection = $this->getConnection(); 50 | 51 | return new ProjectApi($connection->getClient(), $connection->getConfiguration()); 52 | } 53 | 54 | protected function getActivityApi(): ActivityApi 55 | { 56 | $connection = $this->getConnection(); 57 | 58 | return new ActivityApi($connection->getClient(), $connection->getConfiguration()); 59 | } 60 | 61 | protected function getCustomerApi(): CustomerApi 62 | { 63 | $connection = $this->getConnection(); 64 | 65 | return new CustomerApi($connection->getClient(), $connection->getConfiguration()); 66 | } 67 | 68 | protected function getConnection(): Connection 69 | { 70 | if ($this->connection === null) { 71 | $filename = Configuration::getFilename(); 72 | if (!file_exists($filename)) { 73 | throw new InvalidConfigurationException('Missing configuration file: ' . $filename); 74 | } 75 | 76 | if (!is_readable($filename)) { 77 | throw new InvalidConfigurationException('Cannot read configuration: ' . $filename); 78 | } 79 | 80 | $configFile = file_get_contents($filename); 81 | if ($configFile === false) { 82 | throw new InvalidConfigurationException('Failed reading configuration: ' . $filename); 83 | } 84 | 85 | try { 86 | $result = json_decode($configFile, true); 87 | $config = new Configuration($result); 88 | } catch (\Exception $ex) { 89 | throw new InvalidConfigurationException('Invalid configuration: ' . $ex->getMessage()); 90 | } 91 | 92 | try { 93 | $this->connection = new Connection($config); 94 | } catch (\Exception $ex) { 95 | throw new ConnectionProblemException('Failed to connect to API: ' . $ex->getMessage()); 96 | } 97 | } 98 | 99 | return $this->connection; 100 | } 101 | 102 | /** 103 | * @param array $headers 104 | * @param list> $rows 105 | */ 106 | protected function formatOutput(InputInterface $input, OutputInterface $output, array $headers, array $rows): void 107 | { 108 | $io = new SymfonyStyle($input, $output); 109 | 110 | if (false !== $input->getOption('csv')) { 111 | $io->writeln(implode(',', $headers)); 112 | foreach ($rows as $row) { 113 | $io->writeln('"' . implode('","', $row) . '"'); 114 | } 115 | 116 | return; 117 | } 118 | /*elseif (false !== $input->getOption('json')) { 119 | echo json_encode($rows); 120 | 121 | return; 122 | }*/ 123 | 124 | $io->table($headers, $rows); 125 | } 126 | 127 | protected function renderApiException(InputInterface $input, SymfonyStyle $io, ApiException $ex, string $title): void 128 | { 129 | if ($ex->getCode() === 400) { 130 | $message = json_decode($ex->getResponseBody(), true); 131 | if (false === $message) { 132 | $io->error($ex->getMessage()); 133 | } else { 134 | $messages = [$message['code'] . ' ' . $message['message']]; 135 | 136 | // only happens for validation problems 137 | if (isset($message['errors']['children'])) { 138 | foreach ($message['errors']['children'] as $field => $errors) { 139 | if (\array_key_exists('errors', $errors)) { 140 | foreach ($errors['errors'] as $msg) { 141 | $messages[] = $field . ': ' . $msg; 142 | } 143 | } 144 | } 145 | } 146 | 147 | $io->error($messages); 148 | } 149 | 150 | return; 151 | } 152 | 153 | $io->error($ex->getMessage()); 154 | 155 | if ($input->getOption('verbose') === true) { 156 | $io->note($ex->getResponseBody()); 157 | } 158 | } 159 | 160 | /** 161 | * @param SymfonyStyle $io 162 | * @param TimesheetCollectionExpanded[] $timesheets 163 | */ 164 | protected function getSelectedTimesheet(SymfonyStyle $io, array $timesheets): ?TimesheetCollectionExpanded 165 | { 166 | if (\count($timesheets) === 0) { 167 | return null; 168 | } 169 | 170 | $rows = []; 171 | $options = []; 172 | foreach ($timesheets as $timesheet) { 173 | $options[] = $timesheet->getId(); 174 | $rows[] = [ 175 | $timesheet->getId(), 176 | $timesheet->getBegin()->format(\DateTime::ISO8601), 177 | $timesheet->getActivity() !== null ? $timesheet->getActivity()->getName() : '', 178 | $timesheet->getProject() !== null ? $timesheet->getProject()->getName() : '', 179 | $timesheet->getProject() !== null ? $timesheet->getProject()->getCustomer()->getName() : '', 180 | $timesheet->getDescription(), 181 | implode(', ', $timesheet->getTags()), 182 | ]; 183 | } 184 | 185 | $io->table( 186 | ['ID', 'Started at', 'Activity', 'Project', 'Customer', 'Description', 'Tags'], 187 | $rows 188 | ); 189 | 190 | $id = $io->choice('Please select a timesheet', $options); 191 | $id = \intval($id); 192 | 193 | foreach ($timesheets as $timesheet) { 194 | if ($timesheet->getId() === $id) { 195 | return $timesheet; 196 | } 197 | } 198 | 199 | return null; 200 | } 201 | 202 | protected function updateTimesheet(SymfonyStyle $io, TimesheetCollectionExpanded $timesheet, bool|string|null $description = false, bool|string|null $tags = false): void 203 | { 204 | $form = new TimesheetEditForm(); 205 | 206 | if (false !== $description) { 207 | if (null === $description) { 208 | $description = $io->ask('Please enter the description for this timesheet', $timesheet->getDescription()); 209 | } 210 | $form->setDescription($description); 211 | } 212 | 213 | if (false !== $tags) { 214 | if (null === $tags) { 215 | $tags = $io->ask('Please enter the tags for this timesheet (comma separated)', implode(', ', $timesheet->getTags())); 216 | } 217 | $form->setTags($tags); 218 | } 219 | 220 | $api = $this->getTimesheetApi(); 221 | $api->patchPatchTimesheet($form, (string) $timesheet->getId()); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/Command/ConfigurationCommand.php: -------------------------------------------------------------------------------- 1 | setName('configuration') 24 | ->setDescription('Handle the Kimai configuration file') 25 | ->setHelp('This command creates a default configuration file or displays its information.') 26 | ; 27 | } 28 | 29 | protected function execute(InputInterface $input, OutputInterface $output): int 30 | { 31 | $io = new SymfonyStyle($input, $output); 32 | 33 | if (false === $filename = getenv('KIMAI_CONFIG')) { 34 | $filename = getenv('HOME') . DIRECTORY_SEPARATOR . '.kimai-api.json'; 35 | } 36 | 37 | if (file_exists($filename)) { 38 | $configFile = file_get_contents($filename); 39 | if ($configFile === false) { 40 | throw new InvalidConfigurationException('Failed reading configuration: ' . $filename); 41 | } 42 | 43 | try { 44 | $result = json_decode($configFile, true); 45 | $config = new Configuration($result); 46 | 47 | $io->success( 48 | 'Configuration file: ' . $filename . PHP_EOL . 49 | 'URL: ' . $config->getUrl() . PHP_EOL . 50 | 'API Token: ' . $config->getApiToken() . PHP_EOL 51 | ); 52 | 53 | return 0; 54 | } catch (\Exception $ex) { 55 | $io->error(\sprintf('Invalid configuration file %s: %s', $filename, $ex->getMessage())); 56 | } 57 | 58 | return 2; 59 | } 60 | 61 | if (!is_writable(\dirname($filename))) { 62 | $io->error('Cannot write config file: ' . $filename); 63 | 64 | return 1; 65 | } 66 | 67 | $config = Configuration::getDefaultConfiguration(); 68 | 69 | // TODO ask for connection details 70 | 71 | $json = json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); 72 | 73 | if (false === $json) { 74 | $io->error('Failed generating JSON configuration: ' . $filename); 75 | 76 | return 3; 77 | } 78 | 79 | if (false === file_put_contents($filename, $json)) { 80 | $io->error('Failed writing configuration to file: ' . $filename); 81 | 82 | return 4; 83 | } 84 | 85 | $io->success('Created default configuration, please adjust it at: ' . $filename); 86 | 87 | return 0; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Command/CustomerListCommand.php: -------------------------------------------------------------------------------- 1 | setName('customer:list') 23 | ->setDescription('List available customers') 24 | ->setHelp('This command lets you search for customer') 25 | ->addOption('term', null, InputOption::VALUE_OPTIONAL, 'A search term to filter customer', null) 26 | ; 27 | } 28 | 29 | protected function execute(InputInterface $input, OutputInterface $output): int 30 | { 31 | $io = new SymfonyStyle($input, $output); 32 | 33 | $term = null; 34 | 35 | if (null !== $input->getOption('term')) { 36 | $term = $input->getOption('term'); 37 | } 38 | 39 | $api = $this->getCustomerApi(); 40 | 41 | $visible = '1'; 42 | $order = null; 43 | $order_by = null; 44 | 45 | $collection = $api->getGetCustomers($visible, $order, $order_by, $term); 46 | 47 | $rows = []; 48 | foreach ($collection as $customer) { 49 | $rows[] = [ 50 | $customer->getId(), 51 | $customer->getName(), 52 | ]; 53 | } 54 | 55 | $this->formatOutput($input, $output, ['Id', 'Name'], $rows); 56 | 57 | return 0; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Command/ProjectListCommand.php: -------------------------------------------------------------------------------- 1 | setName('project:list') 22 | ->setDescription('List available projects') 23 | ->setHelp('This command lets you search for projects') 24 | ->addOption('customer', null, InputOption::VALUE_OPTIONAL, 'A customer ID to filter projects by (comma separated list possible)') 25 | ->addOption('term', null, InputOption::VALUE_OPTIONAL, 'A search term to filter projects') 26 | ; 27 | } 28 | 29 | protected function execute(InputInterface $input, OutputInterface $output): int 30 | { 31 | $term = null; 32 | $customer = null; 33 | 34 | if (null !== $input->getOption('term')) { 35 | $term = $input->getOption('term'); 36 | } 37 | 38 | if (null !== $input->getOption('customer')) { 39 | $customer = $input->getOption('customer'); 40 | } 41 | 42 | $api = $this->getProjectApi(); 43 | 44 | $visible = '1'; 45 | $start = null; 46 | $end = null; 47 | $global_activities = null; 48 | $ignore_dates = null; 49 | $order = null; 50 | $order_by = 'customer'; 51 | 52 | // null parameters are deprecated 53 | $collection = $api->getGetProjects( 54 | $customer, 55 | null, // @phpstan-ignore argument.type 56 | $visible, 57 | $start, 58 | $end, 59 | $ignore_dates, 60 | $global_activities, 61 | $order, 62 | $order_by, 63 | $term 64 | ); 65 | 66 | $rows = []; 67 | foreach ($collection as $project) { 68 | $rows[] = [ 69 | $project->getId(), 70 | $project->getName(), 71 | $project->getCustomer(), 72 | $project->getParentTitle(), 73 | ]; 74 | } 75 | 76 | $this->formatOutput($input, $output, ['Id', 'Name', 'Customer ID', 'Customer Name'], $rows); 77 | 78 | return 0; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Command/StartCommand.php: -------------------------------------------------------------------------------- 1 | setName('start') 27 | ->setDescription('Starts a new timesheet') 28 | ->setHelp('This command lets you start a new timesheet') 29 | ->addOption('customer', 'c', InputOption::VALUE_OPTIONAL, 'The customer to filter the project list, can be an ID or a search term or empty (you will be prompted for a customer).') 30 | ->addOption('project', 'p', InputOption::VALUE_OPTIONAL, 'The project to use, can be an ID or a search term or empty. You will be prompted for the project.') 31 | ->addOption('activity', 'a', InputOption::VALUE_OPTIONAL, 'The activity ID to use') 32 | ->addOption('tags', 't', InputOption::VALUE_OPTIONAL, 'Comma separated list of tag names') 33 | ->addOption('description', 'd', InputOption::VALUE_OPTIONAL, 'The timesheet description') 34 | ; 35 | } 36 | 37 | protected function execute(InputInterface $input, OutputInterface $output): int 38 | { 39 | $io = new SymfonyStyle($input, $output); 40 | 41 | $customer = null; 42 | 43 | $customerId = $input->getOption('customer'); 44 | $projectId = $input->getOption('project'); 45 | $activityId = $input->getOption('activity'); 46 | 47 | if (null === $projectId) { 48 | $customer = $this->findCustomer($input, $output, $io, $customerId); 49 | } 50 | 51 | $project = $this->findProject($input, $output, $io, $projectId, $customer); 52 | if (null === $project) { 53 | $io->error('Cannot start timesheet: missing project'); 54 | 55 | return 1; 56 | } 57 | 58 | if (null === $customer) { 59 | $customer = $this->loadCustomerById($io, $project->getCustomerId()); 60 | } 61 | 62 | $activity = $this->findActivity($input, $output, $io, $activityId, $project); 63 | if (null === $activity) { 64 | $io->error('Cannot start timesheet: missing activity'); 65 | 66 | return 1; 67 | } 68 | 69 | $api = $this->getTimesheetApi(); 70 | $form = new TimesheetEditForm(); 71 | $form->setProject($project->getId()); 72 | $form->setActivity($activity->getId()); 73 | 74 | // TODO ask for tags if given empty 75 | if (null !== ($tags = $input->getOption('tags'))) { 76 | $form->setTags($tags); 77 | } 78 | 79 | // TODO ask for description if given empty 80 | if (null !== ($description = $input->getOption('description'))) { 81 | $form->setDescription($description); 82 | } 83 | 84 | try { 85 | $timesheet = $api->postPostTimesheet($form); 86 | } catch (ApiException $ex) { 87 | $this->renderApiException($input, $io, $ex, 'Failed creating timesheet'); 88 | 89 | return 1; 90 | } 91 | 92 | $fields = [ 93 | 'ID' => $timesheet->getId(), 94 | 'Begin' => $timesheet->getBegin()->format(\DateTime::ISO8601), 95 | 'Description' => $timesheet->getDescription(), 96 | 'Tags' => implode(PHP_EOL, $timesheet->getTags()), 97 | 'Customer' => $customer->getName(), 98 | 'Project' => $project->getName(), 99 | 'Activity' => $activity->getName(), 100 | ]; 101 | 102 | $io->success('Started timesheet'); 103 | $io->horizontalTable(array_keys($fields), [$fields]); 104 | 105 | return 0; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/Command/StopCommand.php: -------------------------------------------------------------------------------- 1 | setName('stop') 24 | ->setDescription('Stop running timesheets, set description and tags') 25 | ->setHelp('This command lets you stop running timesheets, if multiple are running you can choose of them') 26 | ->addOption('all', 'a', InputOption::VALUE_NONE, 'Stop all running tasks without asking') 27 | ->addOption('description', 'd', InputOption::VALUE_OPTIONAL, 'Set the given description for the stopped timesheet. If none was given, you will be prompted for one.', false) 28 | ->addOption('tags', 't', InputOption::VALUE_OPTIONAL, 'Set the given (comma separated list) of tags for the stopped timesheet. If no tags were given, you will be prompted for them.', false) 29 | ; 30 | } 31 | 32 | protected function execute(InputInterface $input, OutputInterface $output): int 33 | { 34 | $io = new SymfonyStyle($input, $output); 35 | 36 | try { 37 | $api = $this->getTimesheetApi(); 38 | $running = $api->getActiveTimesheet(); 39 | } catch (ApiException $ex) { 40 | $this->renderApiException($input, $io, $ex, 'Failed loading active timesheets'); 41 | 42 | return 1; 43 | } 44 | 45 | if (\count($running) === 0) { 46 | $io->writeln('You have no active timesheets'); 47 | 48 | return 0; 49 | } 50 | 51 | $stopIds = []; 52 | 53 | if (false !== $input->getOption('all')) { 54 | foreach ($running as $timesheet) { 55 | $stopIds[] = $timesheet->getId(); 56 | } 57 | } else { 58 | $timesheet = $this->getSelectedTimesheet($io, $running); 59 | 60 | if (null === $timesheet) { 61 | $io->error('You must selected a timesheet'); 62 | 63 | return 1; 64 | } 65 | 66 | $stopIds[] = $timesheet->getId(); 67 | } 68 | 69 | $updateBeforeStop = false; 70 | if (false !== ($description = $input->getOption('description'))) { 71 | $updateBeforeStop = true; 72 | } 73 | if (false !== ($tags = $input->getOption('tags'))) { 74 | $updateBeforeStop = true; 75 | } 76 | 77 | if ($updateBeforeStop) { 78 | foreach ($running as $timesheet) { 79 | if (!\in_array($timesheet->getId(), $stopIds)) { 80 | continue; 81 | } 82 | 83 | $row = [ 84 | $timesheet->getId(), 85 | $timesheet->getBegin()->format(\DateTime::ISO8601), 86 | $timesheet->getActivity() !== null ? $timesheet->getActivity()->getName() : '', 87 | $timesheet->getProject() !== null ? $timesheet->getProject()->getName() : '', 88 | $timesheet->getProject() !== null ? $timesheet->getProject()->getCustomer()->getName() : '', 89 | ]; 90 | 91 | $io->table( 92 | ['ID', 'Started at', 'Activity', 'Project', 'Customer'], 93 | [$row] 94 | ); 95 | 96 | try { 97 | $this->updateTimesheet($io, $timesheet, $description, $tags); 98 | 99 | $io->success('Updated timesheet'); 100 | } catch (ApiException $ex) { 101 | $this->renderApiException($input, $io, $ex, 'Failed updating timesheet'); 102 | 103 | return 1; 104 | } 105 | } 106 | } 107 | 108 | $stopped = []; 109 | foreach ($stopIds as $id) { 110 | try { 111 | $api->patchStopTimesheet((string) $id); 112 | $stopped[] = $id; 113 | } catch (ApiException $ex) { 114 | $this->renderApiException($input, $io, $ex, 'Failed stopping timesheet'); 115 | } 116 | } 117 | 118 | $io->success(\sprintf('Stopped %s active timesheet(s) with ID: %s', \count($stopped), implode(', ', $stopped))); 119 | 120 | return 0; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/Command/TimesheetCommandTrait.php: -------------------------------------------------------------------------------- 1 | getCustomerApi(); 36 | $customer = null; 37 | 38 | if (null !== $customerId) { 39 | if (is_numeric($customerId)) { 40 | $customerId = \intval($customerId); 41 | $customer = $this->loadCustomerById($io, $customerId); 42 | } else { 43 | $customerList = $api->getGetCustomers('1', null, null, $customerId); 44 | if (\count($customerList) === 1) { 45 | $customer = Customer::fromCollection($customerList[0]); 46 | } elseif (\count($customerList) > 1) { 47 | $customer = $this->askForCustomer($io, $customerList); 48 | } else { 49 | $io->warning(\sprintf('Could not find customer with term: %s', $customerId)); 50 | } 51 | } 52 | } 53 | 54 | if (null !== $customer) { 55 | return $customer; 56 | } 57 | 58 | $customerList = $api->getGetCustomers('1'); 59 | 60 | if (\count($customerList) === 0) { 61 | $io->error('Could not find any customer'); 62 | 63 | return null; 64 | } 65 | 66 | return $this->askForCustomer($io, $customerList); 67 | } 68 | 69 | private function loadCustomerById(SymfonyStyle $io, string|int $id): ?Customer 70 | { 71 | $api = $this->getCustomerApi(); 72 | 73 | try { 74 | $customerEntity = $api->getGetCustomer((string) $id); 75 | 76 | return Customer::fromEntity($customerEntity); 77 | } catch (\Exception $ex) { 78 | if ($ex->getCode() === 404) { 79 | $io->error(\sprintf('Customer with ID %s does not exist', $id)); 80 | } else { 81 | $io->error(\sprintf('Failed loading customer with ID %s: %s', $id, $ex->getMessage())); 82 | } 83 | } 84 | 85 | return null; 86 | } 87 | 88 | /** 89 | * @param SymfonyStyle $io 90 | * @param array $customers 91 | * @return Customer 92 | */ 93 | private function askForCustomer(SymfonyStyle $io, array $customers): ?Customer 94 | { 95 | $choices = []; 96 | foreach ($customers as $customerEntity) { 97 | $choices[$customerEntity->getId()] = $customerEntity->getName(); 98 | } 99 | 100 | $id = $io->choice('Please select a customer', $choices); 101 | 102 | $flipped = array_flip($choices); 103 | $id = \intval($flipped[$id]); 104 | 105 | foreach ($customers as $customerEntity) { 106 | if ($customerEntity->getId() !== $id) { 107 | continue; 108 | } 109 | 110 | return Customer::fromCollection($customerEntity); 111 | } 112 | 113 | $io->error('Failed loading customer with ID ' . $id); 114 | 115 | return null; 116 | } 117 | 118 | // ============================================================================================================== 119 | 120 | private function findProject(InputInterface $input, OutputInterface $output, SymfonyStyle $io, string|int|null $projectId, ?Customer $customer = null): ?Project 121 | { 122 | $api = $this->getProjectApi(); 123 | $project = null; 124 | $customerId = null !== $customer ? (string) $customer->getId() : null; 125 | 126 | if (null !== $projectId) { 127 | if (is_numeric($projectId)) { 128 | $projectId = \intval($projectId); 129 | $project = $this->loadProjectById($io, $projectId); 130 | } else { 131 | $projectList = $api->getGetProjects( 132 | $customerId, 133 | null, // @phpstan-ignore argument.type 134 | '1', 135 | null, 136 | null, 137 | null, 138 | null, 139 | null, 140 | null, 141 | $projectId 142 | ); 143 | if (\count($projectList) === 1) { 144 | $project = Project::fromCollection($projectList[0]); 145 | } elseif (\count($projectList) > 1) { 146 | $project = $this->askForProject($io, $projectList); 147 | } else { 148 | $io->warning(\sprintf('Could not find project with term: %s', $projectId)); 149 | } 150 | } 151 | } 152 | 153 | if (null !== $project) { 154 | return $project; 155 | } 156 | 157 | $projectList = $api->getGetProjects( 158 | $customerId, 159 | null, // @phpstan-ignore argument.type 160 | '1' 161 | ); 162 | 163 | if (\count($projectList) === 0) { 164 | $io->error('Could not find any project'); 165 | 166 | return null; 167 | } 168 | 169 | return $this->askForProject($io, $projectList); 170 | } 171 | 172 | private function loadProjectById(SymfonyStyle $io, int $id): ?Project 173 | { 174 | $api = $this->getProjectApi(); 175 | 176 | try { 177 | $projectEntity = $api->getGetProject((string) $id); 178 | 179 | return Project::fromEntity($projectEntity); 180 | } catch (\Exception $ex) { 181 | if ($ex->getCode() === 404) { 182 | $io->error(\sprintf('Project with ID %s does not exist', $id)); 183 | } else { 184 | $io->error(\sprintf('Failed loading project with ID %s: %s', $id, $ex->getMessage())); 185 | } 186 | } 187 | 188 | return null; 189 | } 190 | 191 | /** 192 | * @param SymfonyStyle $io 193 | * @param array $projects 194 | * @return Project 195 | */ 196 | private function askForProject(SymfonyStyle $io, array $projects): ?Project 197 | { 198 | $choices = []; 199 | /** @var ProjectCollection $projectEntity */ 200 | foreach ($projects as $projectEntity) { 201 | $choices[$projectEntity->getId()] = $projectEntity->getName(); 202 | } 203 | 204 | $id = $io->choice('Please select a project', $choices); 205 | 206 | $flipped = array_flip($choices); 207 | $id = \intval($flipped[$id]); 208 | 209 | /** @var ProjectCollection $projectEntity */ 210 | foreach ($projects as $projectEntity) { 211 | if ($projectEntity->getId() !== $id) { 212 | continue; 213 | } 214 | 215 | return Project::fromCollection($projectEntity); 216 | } 217 | 218 | $io->error('Failed loading project with ID ' . $id); 219 | 220 | return null; 221 | } 222 | 223 | // ============================================================================================================== 224 | 225 | private function findActivity(InputInterface $input, OutputInterface $output, SymfonyStyle $io, string|int|null $activityId, ?Project $projectEntity = null): ?Activity 226 | { 227 | $api = $this->getActivityApi(); 228 | $activity = null; 229 | $projectId = null !== $projectEntity ? (string) $projectEntity->getId() : null; 230 | 231 | if (null !== $activityId) { 232 | if (is_numeric($activityId)) { 233 | $activityId = \intval($activityId); 234 | $activity = $this->loadActivityById($io, $activityId); 235 | } else { 236 | $activityList = $api->getGetActivities( 237 | $projectId, 238 | null, // @phpstan-ignore argument.type 239 | '1', 240 | null, 241 | null, 242 | null, 243 | $activityId 244 | ); 245 | if (\count($activityList) === 1) { 246 | $activity = Activity::fromCollection($activityList[0]); 247 | } elseif (\count($activityList) > 1) { 248 | $activity = $this->askForActivity($io, $activityList); 249 | } else { 250 | $io->warning(\sprintf('Could not find activity with term: %s', $activityId)); 251 | } 252 | } 253 | } 254 | 255 | if (null !== $activity) { 256 | return $activity; 257 | } 258 | 259 | $activityList = $api->getGetActivities( 260 | $projectId, 261 | null, // @phpstan-ignore argument.type 262 | '1' 263 | ); 264 | 265 | if (\count($activityList) === 0) { 266 | $io->error('Could not find any activity'); 267 | 268 | return null; 269 | } 270 | 271 | return $this->askForActivity($io, $activityList); 272 | } 273 | 274 | private function loadActivityById(SymfonyStyle $io, int $id): ?Activity 275 | { 276 | $api = $this->getActivityApi(); 277 | 278 | try { 279 | $activityEntity = $api->getGetActivity((string) $id); 280 | 281 | return Activity::fromEntity($activityEntity); 282 | } catch (\Exception $ex) { 283 | if ($ex->getCode() === 404) { 284 | $io->error(\sprintf('Activity with ID %s does not exist', $id)); 285 | } else { 286 | $io->error(\sprintf('Failed loading activity with ID %s: %s', $id, $ex->getMessage())); 287 | } 288 | } 289 | 290 | return null; 291 | } 292 | 293 | /** 294 | * @param SymfonyStyle $io 295 | * @param array $activities 296 | * @return Activity 297 | */ 298 | private function askForActivity(SymfonyStyle $io, array $activities): ?Activity 299 | { 300 | $choices = []; 301 | /** @var ActivityCollection $activityEntity */ 302 | foreach ($activities as $activityEntity) { 303 | $choices[$activityEntity->getId()] = $activityEntity->getName(); 304 | } 305 | 306 | $id = $io->choice('Please select an activity', $choices); 307 | 308 | $flipped = array_flip($choices); 309 | $id = \intval($flipped[$id]); 310 | 311 | /* @var ActivityCollection $activity */ 312 | foreach ($activities as $activityEntity) { 313 | if ($activityEntity->getId() !== $id) { 314 | continue; 315 | } 316 | 317 | return Activity::fromCollection($activityEntity); 318 | } 319 | 320 | $io->error('Failed loading activity with ID ' . $id); 321 | 322 | return null; 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /src/Command/VersionCommand.php: -------------------------------------------------------------------------------- 1 | setName('version') 22 | ->setDescription('Show the Kimai version') 23 | ->setHelp('This command shows the Kimai version from the remote server') 24 | ; 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | protected function execute(InputInterface $input, OutputInterface $output): int 31 | { 32 | $version = $this->getApi()->getAppApiStatusVersion(); 33 | 34 | $io = new SymfonyStyle($input, $output); 35 | $io->writeln($version->getCopyright()); 36 | 37 | return 0; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Constants.php: -------------------------------------------------------------------------------- 1 | id; 24 | } 25 | 26 | public function setId(int $id): void 27 | { 28 | $this->id = $id; 29 | } 30 | 31 | public function getName(): ?string 32 | { 33 | return $this->name; 34 | } 35 | 36 | public function setName(string $name): void 37 | { 38 | $this->name = $name; 39 | } 40 | 41 | public function getProjectId(): ?int 42 | { 43 | return $this->projectId; 44 | } 45 | 46 | public function setProjectId(?int $projectId): void 47 | { 48 | $this->projectId = $projectId; 49 | } 50 | 51 | public static function fromEntity(ActivityEntity $entity): Activity 52 | { 53 | $activity = new Activity(); 54 | $activity->setId($entity->getId()); 55 | $activity->setName($entity->getName()); 56 | $activity->setProjectId($entity->getProject()); 57 | 58 | return $activity; 59 | } 60 | 61 | public static function fromCollection(ActivityCollection $entity): Activity 62 | { 63 | $activity = new Activity(); 64 | $activity->setId($entity->getId()); 65 | $activity->setName($entity->getName()); 66 | $activity->setProjectId($entity->getProject()); 67 | 68 | return $activity; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Entity/Customer.php: -------------------------------------------------------------------------------- 1 | id; 23 | } 24 | 25 | public function setId(int $id): void 26 | { 27 | $this->id = $id; 28 | } 29 | 30 | public function getName(): ?string 31 | { 32 | return $this->name; 33 | } 34 | 35 | public function setName(string $name): void 36 | { 37 | $this->name = $name; 38 | } 39 | 40 | public static function fromEntity(CustomerEntity $entity): Customer 41 | { 42 | $customer = new Customer(); 43 | $customer->setId($entity->getId()); 44 | $customer->setName($entity->getName()); 45 | 46 | return $customer; 47 | } 48 | 49 | public static function fromCollection(CustomerCollection $entity): Customer 50 | { 51 | $customer = new Customer(); 52 | $customer->setId($entity->getId()); 53 | $customer->setName($entity->getName()); 54 | 55 | return $customer; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Entity/Project.php: -------------------------------------------------------------------------------- 1 | id; 24 | } 25 | 26 | public function setId(int $id): void 27 | { 28 | $this->id = $id; 29 | } 30 | 31 | public function getName(): ?string 32 | { 33 | return $this->name; 34 | } 35 | 36 | public function setName(string $name): void 37 | { 38 | $this->name = $name; 39 | } 40 | 41 | public function getCustomerId(): ?int 42 | { 43 | return $this->customerId; 44 | } 45 | 46 | public function setCustomerId(int $customerId): void 47 | { 48 | $this->customerId = $customerId; 49 | } 50 | 51 | public static function fromEntity(ProjectEntity $entity): Project 52 | { 53 | $project = new Project(); 54 | $project->setId($entity->getId()); 55 | $project->setName($entity->getName()); 56 | $project->setCustomerId($entity->getCustomer()); 57 | 58 | return $project; 59 | } 60 | 61 | public static function fromCollection(ProjectCollection $entity): Project 62 | { 63 | $project = new Project(); 64 | $project->setId($entity->getId()); 65 | $project->setName($entity->getName()); 66 | $project->setCustomerId($entity->getCustomer()); 67 | 68 | return $project; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Exception/ConnectionProblemException.php: -------------------------------------------------------------------------------- 1 |