├── .github └── workflows │ └── tests.yaml ├── .gitignore ├── .php_cs.php ├── Dockerfile ├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── docker-compose.yml ├── docker └── xdebug.ini ├── spec ├── Akeneo │ └── Crowdin │ │ ├── Api │ │ ├── AddDirectorySpec.php │ │ ├── AddFileSpec.php │ │ ├── ChangeDirectorySpec.php │ │ ├── DeleteDirectorySpec.php │ │ ├── DeleteFileSpec.php │ │ ├── DownloadSpec.php │ │ ├── ExportSpec.php │ │ ├── InfoSpec.php │ │ ├── LanguageStatusSpec.php │ │ ├── StatusSpec.php │ │ ├── SupportedLanguagesSpec.php │ │ ├── UpdateFileSpec.php │ │ └── UploadTranslationSpec.php │ │ ├── ClientSpec.php │ │ ├── FileReaderSpec.php │ │ └── TranslationSpec.php └── fixtures │ └── messages.en.yml └── src └── Akeneo └── Crowdin ├── Api ├── AbstractApi.php ├── AddDirectory.php ├── AddFile.php ├── ApiInterface.php ├── ChangeDirectory.php ├── DeleteDirectory.php ├── DeleteFile.php ├── Download.php ├── Export.php ├── Info.php ├── LanguageStatus.php ├── Status.php ├── SupportedLanguages.php ├── UpdateFile.php └── UploadTranslation.php ├── Client.php ├── FileNotFoundException.php ├── FileReader.php └── Translation.php /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: Nelson tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | docker: 13 | timeout-minutes: 10 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v3 19 | 20 | - name: Build image 21 | run: docker-compose build php 22 | 23 | - name: test directories 24 | run: mkdir vendor && chmod -R 0777 vendor 25 | 26 | - name: Install dependencies 27 | run: docker-compose run --rm php composer install 28 | 29 | - name: Code style check 30 | run: docker-compose run --rm php vendor/bin/php-cs-fixer fix --dry-run -v --diff --config=.php_cs.php 31 | 32 | - name: Static checks 33 | run: docker-compose run --rm php vendor/bin/phpstan analyze src --level 5 34 | 35 | - name: Run specs 36 | run: docker-compose run --rm php vendor/bin/phpspec run 37 | 38 | php: 39 | timeout-minutes: 10 40 | runs-on: ubuntu-latest 41 | strategy: 42 | matrix: 43 | php-version: ['8.1', '8.2'] 44 | steps: 45 | - name: Checkout 46 | uses: actions/checkout@v3 47 | 48 | - name: Setup PHP with PECL extension 49 | uses: shivammathur/setup-php@v2 50 | with: 51 | php-version: ${{ matrix.php-version }} 52 | tools: composer:v2 53 | 54 | - name: Install dependencies 55 | run: composer update 56 | 57 | - name: Run specs 58 | run: vendor/bin/phpspec run 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | -------------------------------------------------------------------------------- /.php_cs.php: -------------------------------------------------------------------------------- 1 | name('*.php') 5 | ->in(__DIR__ . '/spec') 6 | ->in(__DIR__ . '/src') 7 | ->files(); 8 | 9 | $config = new PhpCsFixer\Config(); 10 | 11 | return $config->setUsingCache(false) 12 | ->setRules([ 13 | '@PSR2' => true, 14 | 'linebreak_after_opening_tag' => true, 15 | 'ordered_imports' => true, 16 | ]) 17 | ->setFinder($finder); 18 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:bullseye-slim 2 | 3 | ENV DEBIAN_FRONTEND noninteractive 4 | ENV COMPOSER_HOME: '/home/docker/.composer' 5 | 6 | RUN echo 'APT::Install-Recommends "0" ; APT::Install-Suggests "0" ;' > /etc/apt/apt.conf.d/01-no-recommended && \ 7 | echo 'path-exclude=/usr/share/man/*' > /etc/dpkg/dpkg.cfg.d/path_exclusions && \ 8 | echo 'path-exclude=/usr/share/doc/*' >> /etc/dpkg/dpkg.cfg.d/path_exclusions && \ 9 | apt-get update && \ 10 | apt-get --yes install apt-transport-https ca-certificates curl wget &&\ 11 | wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg &&\ 12 | sh -c 'echo "deb https://packages.sury.org/php/ bullseye main" > /etc/apt/sources.list.d/php.list' &&\ 13 | apt-get update && \ 14 | apt-get --yes install \ 15 | curl \ 16 | php8.1-cli \ 17 | php8.1-curl \ 18 | php8.1-xdebug \ 19 | unzip &&\ 20 | apt-get clean && \ 21 | rm -rf /var/lib/apt/lists/* 22 | 23 | COPY docker/xdebug.ini /etc/php/8.1/mods-available/nelson.ini 24 | RUN phpenmod nelson 25 | 26 | COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer 27 | 28 | RUN chmod +x /usr/local/bin/composer \ 29 | && useradd -m docker \ 30 | && mkdir -p /home/docker/.composer/cache \ 31 | && chown -R 1000:1000 /home/docker/.composer 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Akeneo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | php-crowdin-api 2 | =============== 3 | 4 | A simple PHP Crowdin API client http://crowdin.net/page/api. 5 | 6 | Crowdin is a translation and localization management platform : http://crowdin.net/ 7 | 8 | FYI, an official and more complete Ruby Client exists here : https://github.com/crowdin/crowdin-api 9 | 10 | [![Build Status](https://travis-ci.org/akeneo/php-crowdin-api.png)](https://travis-ci.org/akeneo/php-crowdin-api) 11 | [![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/akeneo/php-crowdin-api/badges/quality-score.png?s=6f2062a3c333671eb8112a79d3c5f6118f0ad496)](https://scrutinizer-ci.com/g/akeneo/php-crowdin-api/) 12 | 13 | Features 14 | -------- 15 | 16 | PSR-2 conventions and coding standard 17 | 18 | Wrap following API methods : 19 | 20 | * Add a file, delete a file 21 | * Add a directory, delete a directory, change a directory 22 | * Update File, Upload translations, Upload fresh version of your localization file 23 | * Export Translations, Build fresh package with the latest translations. 24 | * Download Translations, Download last exported translation package (one language or all languages as one zip file). 25 | * Supported Languages, Get supported languages list with Crowdin codes mapped to locale name and standardized codes. 26 | * Translation Status, Track overall translation and proofreading progress of each target language. 27 | * Project Info, Shows project details and meta information. 28 | 29 | Missing API methods https://github.com/akeneo/php-crowdin-api/issues?q=is%3Aopen+is%3Aissue+label%3Aapi-method 30 | 31 | Requirements 32 | ------------ 33 | 34 | * PHP >= 8.1 35 | * Docker for a better dev experience 36 | 37 | How to use ? 38 | ------------ 39 | 40 | Add the following lines in your project composer.json : 41 | 42 | ```yaml 43 | { 44 | "require": { 45 | "akeneo/crowdin-api": "^2.0.0" 46 | }, 47 | "minimum-stability": "stable" 48 | } 49 | ``` 50 | 51 | Then, to instantiate the client and use available API methods : 52 | 53 | ```php 54 | api('download'); 65 | $api->setCopyDestination('/tmp/download-crowdin'); 66 | $api->setPackage('fr.zip'); 67 | $result = $api->execute(); 68 | 69 | // update a Crowdin file from local filesystem 70 | $api = $client->api('update-file'); 71 | $localPath = '/home/nico/git/pim/src/Pim/Bundle/CatalogBundle/Resources/translations/messages.en.yml'; 72 | $crowdinPath = 'PimCommunity/CatalogBundle/Resources/translations/messages.en.yml'; 73 | $api->addTranslation($localPath, $crowdinPath); 74 | $result = $api->execute(); 75 | ``` 76 | 77 | ### Run tests 78 | 79 | ```bash 80 | docker-compose run --rm php vendor/bin/php-cs-fixer fix --config=.php_cs.php --diff 81 | docker-compose run --rm php vendor/bin/phpstan analyze src --level 5 82 | docker-compose run --rm php vendor/bin/phpspec run 83 | ``` 84 | 85 | Use cases 86 | --------- 87 | 88 | You can take a look at following use cases to see more real life samples. 89 | 90 | The Akeneo core team uses this library in Nelson, a command based translation workflow between Crowdin and GitHub 91 | (cf https://github.com/akeneo/nelson). 92 | 93 | The Sylius core team uses this library in SyliusBot to manage community translations 94 | https://github.com/SyliusBot/SyliusBot. 95 | 96 | If you use this library don't hesitate to open a PR to explain your use case here :) 97 | 98 | Licence 99 | ------- 100 | 101 | The MIT License (MIT) 102 | 103 | Contribution 104 | ------------ 105 | 106 | Feel free to fork and propose PR to complete missing API methods 107 | https://github.com/akeneo/php-crowdin-api/issues?q=is%3Aopen+is%3Aissue+label%3Aapi-method 108 | 109 | Any contributions are welcome! 110 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "akeneo/crowdin-api", 3 | "type": "library", 4 | "description": "Crowdin PHP API client", 5 | "homepage": "https://github.com/akeneo/php-crowdin-api", 6 | "keywords": [ 7 | "crowdin", 8 | "translation", 9 | "localization", 10 | "api" 11 | ], 12 | "license": "MIT", 13 | "authors": [ 14 | { 15 | "name": "Nicolas Dupont ", 16 | "homepage": "http://www.akeneo.com" 17 | }, 18 | { 19 | "name": "Julien Janvier " 20 | }, 21 | { 22 | "name": "Laurent Ghirardotti ", 23 | "homepage": "http://ghirardotti.fr" 24 | }, 25 | { 26 | "name": "JM Leroux " 27 | } 28 | ], 29 | "require": { 30 | "php": "^8.1", 31 | "symfony/http-client": "^6.3.2" 32 | }, 33 | "require-dev": { 34 | "phpspec/phpspec": "^7.4.0", 35 | "friendsofphp/php-cs-fixer": "^v3.23.0", 36 | "phpstan/phpstan": "^1.10" 37 | }, 38 | "autoload": { 39 | "psr-4": { 40 | "Akeneo\\Crowdin\\": "src/Akeneo/Crowdin/" 41 | } 42 | }, 43 | "autoload-dev": { 44 | "psr-4": { 45 | "spec\\Akeneo\\": "spec/Akeneo" 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /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": "9413fb068f20d275febdb5d0024aa1a3", 8 | "packages": [ 9 | { 10 | "name": "psr/container", 11 | "version": "2.0.2", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/php-fig/container.git", 15 | "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", 20 | "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": ">=7.4.0" 25 | }, 26 | "type": "library", 27 | "extra": { 28 | "branch-alias": { 29 | "dev-master": "2.0.x-dev" 30 | } 31 | }, 32 | "autoload": { 33 | "psr-4": { 34 | "Psr\\Container\\": "src/" 35 | } 36 | }, 37 | "notification-url": "https://packagist.org/downloads/", 38 | "license": [ 39 | "MIT" 40 | ], 41 | "authors": [ 42 | { 43 | "name": "PHP-FIG", 44 | "homepage": "https://www.php-fig.org/" 45 | } 46 | ], 47 | "description": "Common Container Interface (PHP FIG PSR-11)", 48 | "homepage": "https://github.com/php-fig/container", 49 | "keywords": [ 50 | "PSR-11", 51 | "container", 52 | "container-interface", 53 | "container-interop", 54 | "psr" 55 | ], 56 | "support": { 57 | "issues": "https://github.com/php-fig/container/issues", 58 | "source": "https://github.com/php-fig/container/tree/2.0.2" 59 | }, 60 | "time": "2021-11-05T16:47:00+00:00" 61 | }, 62 | { 63 | "name": "psr/log", 64 | "version": "3.0.0", 65 | "source": { 66 | "type": "git", 67 | "url": "https://github.com/php-fig/log.git", 68 | "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" 69 | }, 70 | "dist": { 71 | "type": "zip", 72 | "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", 73 | "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", 74 | "shasum": "" 75 | }, 76 | "require": { 77 | "php": ">=8.0.0" 78 | }, 79 | "type": "library", 80 | "extra": { 81 | "branch-alias": { 82 | "dev-master": "3.x-dev" 83 | } 84 | }, 85 | "autoload": { 86 | "psr-4": { 87 | "Psr\\Log\\": "src" 88 | } 89 | }, 90 | "notification-url": "https://packagist.org/downloads/", 91 | "license": [ 92 | "MIT" 93 | ], 94 | "authors": [ 95 | { 96 | "name": "PHP-FIG", 97 | "homepage": "https://www.php-fig.org/" 98 | } 99 | ], 100 | "description": "Common interface for logging libraries", 101 | "homepage": "https://github.com/php-fig/log", 102 | "keywords": [ 103 | "log", 104 | "psr", 105 | "psr-3" 106 | ], 107 | "support": { 108 | "source": "https://github.com/php-fig/log/tree/3.0.0" 109 | }, 110 | "time": "2021-07-14T16:46:02+00:00" 111 | }, 112 | { 113 | "name": "symfony/deprecation-contracts", 114 | "version": "v3.3.0", 115 | "source": { 116 | "type": "git", 117 | "url": "https://github.com/symfony/deprecation-contracts.git", 118 | "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" 119 | }, 120 | "dist": { 121 | "type": "zip", 122 | "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", 123 | "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", 124 | "shasum": "" 125 | }, 126 | "require": { 127 | "php": ">=8.1" 128 | }, 129 | "type": "library", 130 | "extra": { 131 | "branch-alias": { 132 | "dev-main": "3.4-dev" 133 | }, 134 | "thanks": { 135 | "name": "symfony/contracts", 136 | "url": "https://github.com/symfony/contracts" 137 | } 138 | }, 139 | "autoload": { 140 | "files": [ 141 | "function.php" 142 | ] 143 | }, 144 | "notification-url": "https://packagist.org/downloads/", 145 | "license": [ 146 | "MIT" 147 | ], 148 | "authors": [ 149 | { 150 | "name": "Nicolas Grekas", 151 | "email": "p@tchwork.com" 152 | }, 153 | { 154 | "name": "Symfony Community", 155 | "homepage": "https://symfony.com/contributors" 156 | } 157 | ], 158 | "description": "A generic function and convention to trigger deprecation notices", 159 | "homepage": "https://symfony.com", 160 | "support": { 161 | "source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0" 162 | }, 163 | "funding": [ 164 | { 165 | "url": "https://symfony.com/sponsor", 166 | "type": "custom" 167 | }, 168 | { 169 | "url": "https://github.com/fabpot", 170 | "type": "github" 171 | }, 172 | { 173 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 174 | "type": "tidelift" 175 | } 176 | ], 177 | "time": "2023-05-23T14:45:45+00:00" 178 | }, 179 | { 180 | "name": "symfony/http-client", 181 | "version": "v6.3.2", 182 | "source": { 183 | "type": "git", 184 | "url": "https://github.com/symfony/http-client.git", 185 | "reference": "15f9f4bad62bfcbe48b5dedd866f04a08fc7ff00" 186 | }, 187 | "dist": { 188 | "type": "zip", 189 | "url": "https://api.github.com/repos/symfony/http-client/zipball/15f9f4bad62bfcbe48b5dedd866f04a08fc7ff00", 190 | "reference": "15f9f4bad62bfcbe48b5dedd866f04a08fc7ff00", 191 | "shasum": "" 192 | }, 193 | "require": { 194 | "php": ">=8.1", 195 | "psr/log": "^1|^2|^3", 196 | "symfony/deprecation-contracts": "^2.5|^3", 197 | "symfony/http-client-contracts": "^3", 198 | "symfony/service-contracts": "^2.5|^3" 199 | }, 200 | "conflict": { 201 | "php-http/discovery": "<1.15", 202 | "symfony/http-foundation": "<6.3" 203 | }, 204 | "provide": { 205 | "php-http/async-client-implementation": "*", 206 | "php-http/client-implementation": "*", 207 | "psr/http-client-implementation": "1.0", 208 | "symfony/http-client-implementation": "3.0" 209 | }, 210 | "require-dev": { 211 | "amphp/amp": "^2.5", 212 | "amphp/http-client": "^4.2.1", 213 | "amphp/http-tunnel": "^1.0", 214 | "amphp/socket": "^1.1", 215 | "guzzlehttp/promises": "^1.4", 216 | "nyholm/psr7": "^1.0", 217 | "php-http/httplug": "^1.0|^2.0", 218 | "psr/http-client": "^1.0", 219 | "symfony/dependency-injection": "^5.4|^6.0", 220 | "symfony/http-kernel": "^5.4|^6.0", 221 | "symfony/process": "^5.4|^6.0", 222 | "symfony/stopwatch": "^5.4|^6.0" 223 | }, 224 | "type": "library", 225 | "autoload": { 226 | "psr-4": { 227 | "Symfony\\Component\\HttpClient\\": "" 228 | }, 229 | "exclude-from-classmap": [ 230 | "/Tests/" 231 | ] 232 | }, 233 | "notification-url": "https://packagist.org/downloads/", 234 | "license": [ 235 | "MIT" 236 | ], 237 | "authors": [ 238 | { 239 | "name": "Nicolas Grekas", 240 | "email": "p@tchwork.com" 241 | }, 242 | { 243 | "name": "Symfony Community", 244 | "homepage": "https://symfony.com/contributors" 245 | } 246 | ], 247 | "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", 248 | "homepage": "https://symfony.com", 249 | "keywords": [ 250 | "http" 251 | ], 252 | "support": { 253 | "source": "https://github.com/symfony/http-client/tree/v6.3.2" 254 | }, 255 | "funding": [ 256 | { 257 | "url": "https://symfony.com/sponsor", 258 | "type": "custom" 259 | }, 260 | { 261 | "url": "https://github.com/fabpot", 262 | "type": "github" 263 | }, 264 | { 265 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 266 | "type": "tidelift" 267 | } 268 | ], 269 | "time": "2023-07-05T08:41:27+00:00" 270 | }, 271 | { 272 | "name": "symfony/http-client-contracts", 273 | "version": "v3.3.0", 274 | "source": { 275 | "type": "git", 276 | "url": "https://github.com/symfony/http-client-contracts.git", 277 | "reference": "3b66325d0176b4ec826bffab57c9037d759c31fb" 278 | }, 279 | "dist": { 280 | "type": "zip", 281 | "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/3b66325d0176b4ec826bffab57c9037d759c31fb", 282 | "reference": "3b66325d0176b4ec826bffab57c9037d759c31fb", 283 | "shasum": "" 284 | }, 285 | "require": { 286 | "php": ">=8.1" 287 | }, 288 | "type": "library", 289 | "extra": { 290 | "branch-alias": { 291 | "dev-main": "3.4-dev" 292 | }, 293 | "thanks": { 294 | "name": "symfony/contracts", 295 | "url": "https://github.com/symfony/contracts" 296 | } 297 | }, 298 | "autoload": { 299 | "psr-4": { 300 | "Symfony\\Contracts\\HttpClient\\": "" 301 | }, 302 | "exclude-from-classmap": [ 303 | "/Test/" 304 | ] 305 | }, 306 | "notification-url": "https://packagist.org/downloads/", 307 | "license": [ 308 | "MIT" 309 | ], 310 | "authors": [ 311 | { 312 | "name": "Nicolas Grekas", 313 | "email": "p@tchwork.com" 314 | }, 315 | { 316 | "name": "Symfony Community", 317 | "homepage": "https://symfony.com/contributors" 318 | } 319 | ], 320 | "description": "Generic abstractions related to HTTP clients", 321 | "homepage": "https://symfony.com", 322 | "keywords": [ 323 | "abstractions", 324 | "contracts", 325 | "decoupling", 326 | "interfaces", 327 | "interoperability", 328 | "standards" 329 | ], 330 | "support": { 331 | "source": "https://github.com/symfony/http-client-contracts/tree/v3.3.0" 332 | }, 333 | "funding": [ 334 | { 335 | "url": "https://symfony.com/sponsor", 336 | "type": "custom" 337 | }, 338 | { 339 | "url": "https://github.com/fabpot", 340 | "type": "github" 341 | }, 342 | { 343 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 344 | "type": "tidelift" 345 | } 346 | ], 347 | "time": "2023-05-23T14:45:45+00:00" 348 | }, 349 | { 350 | "name": "symfony/service-contracts", 351 | "version": "v3.3.0", 352 | "source": { 353 | "type": "git", 354 | "url": "https://github.com/symfony/service-contracts.git", 355 | "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4" 356 | }, 357 | "dist": { 358 | "type": "zip", 359 | "url": "https://api.github.com/repos/symfony/service-contracts/zipball/40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", 360 | "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", 361 | "shasum": "" 362 | }, 363 | "require": { 364 | "php": ">=8.1", 365 | "psr/container": "^2.0" 366 | }, 367 | "conflict": { 368 | "ext-psr": "<1.1|>=2" 369 | }, 370 | "type": "library", 371 | "extra": { 372 | "branch-alias": { 373 | "dev-main": "3.4-dev" 374 | }, 375 | "thanks": { 376 | "name": "symfony/contracts", 377 | "url": "https://github.com/symfony/contracts" 378 | } 379 | }, 380 | "autoload": { 381 | "psr-4": { 382 | "Symfony\\Contracts\\Service\\": "" 383 | }, 384 | "exclude-from-classmap": [ 385 | "/Test/" 386 | ] 387 | }, 388 | "notification-url": "https://packagist.org/downloads/", 389 | "license": [ 390 | "MIT" 391 | ], 392 | "authors": [ 393 | { 394 | "name": "Nicolas Grekas", 395 | "email": "p@tchwork.com" 396 | }, 397 | { 398 | "name": "Symfony Community", 399 | "homepage": "https://symfony.com/contributors" 400 | } 401 | ], 402 | "description": "Generic abstractions related to writing services", 403 | "homepage": "https://symfony.com", 404 | "keywords": [ 405 | "abstractions", 406 | "contracts", 407 | "decoupling", 408 | "interfaces", 409 | "interoperability", 410 | "standards" 411 | ], 412 | "support": { 413 | "source": "https://github.com/symfony/service-contracts/tree/v3.3.0" 414 | }, 415 | "funding": [ 416 | { 417 | "url": "https://symfony.com/sponsor", 418 | "type": "custom" 419 | }, 420 | { 421 | "url": "https://github.com/fabpot", 422 | "type": "github" 423 | }, 424 | { 425 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 426 | "type": "tidelift" 427 | } 428 | ], 429 | "time": "2023-05-23T14:45:45+00:00" 430 | } 431 | ], 432 | "packages-dev": [ 433 | { 434 | "name": "composer/pcre", 435 | "version": "3.1.0", 436 | "source": { 437 | "type": "git", 438 | "url": "https://github.com/composer/pcre.git", 439 | "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2" 440 | }, 441 | "dist": { 442 | "type": "zip", 443 | "url": "https://api.github.com/repos/composer/pcre/zipball/4bff79ddd77851fe3cdd11616ed3f92841ba5bd2", 444 | "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2", 445 | "shasum": "" 446 | }, 447 | "require": { 448 | "php": "^7.4 || ^8.0" 449 | }, 450 | "require-dev": { 451 | "phpstan/phpstan": "^1.3", 452 | "phpstan/phpstan-strict-rules": "^1.1", 453 | "symfony/phpunit-bridge": "^5" 454 | }, 455 | "type": "library", 456 | "extra": { 457 | "branch-alias": { 458 | "dev-main": "3.x-dev" 459 | } 460 | }, 461 | "autoload": { 462 | "psr-4": { 463 | "Composer\\Pcre\\": "src" 464 | } 465 | }, 466 | "notification-url": "https://packagist.org/downloads/", 467 | "license": [ 468 | "MIT" 469 | ], 470 | "authors": [ 471 | { 472 | "name": "Jordi Boggiano", 473 | "email": "j.boggiano@seld.be", 474 | "homepage": "http://seld.be" 475 | } 476 | ], 477 | "description": "PCRE wrapping library that offers type-safe preg_* replacements.", 478 | "keywords": [ 479 | "PCRE", 480 | "preg", 481 | "regex", 482 | "regular expression" 483 | ], 484 | "support": { 485 | "issues": "https://github.com/composer/pcre/issues", 486 | "source": "https://github.com/composer/pcre/tree/3.1.0" 487 | }, 488 | "funding": [ 489 | { 490 | "url": "https://packagist.com", 491 | "type": "custom" 492 | }, 493 | { 494 | "url": "https://github.com/composer", 495 | "type": "github" 496 | }, 497 | { 498 | "url": "https://tidelift.com/funding/github/packagist/composer/composer", 499 | "type": "tidelift" 500 | } 501 | ], 502 | "time": "2022-11-17T09:50:14+00:00" 503 | }, 504 | { 505 | "name": "composer/semver", 506 | "version": "3.3.2", 507 | "source": { 508 | "type": "git", 509 | "url": "https://github.com/composer/semver.git", 510 | "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9" 511 | }, 512 | "dist": { 513 | "type": "zip", 514 | "url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9", 515 | "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9", 516 | "shasum": "" 517 | }, 518 | "require": { 519 | "php": "^5.3.2 || ^7.0 || ^8.0" 520 | }, 521 | "require-dev": { 522 | "phpstan/phpstan": "^1.4", 523 | "symfony/phpunit-bridge": "^4.2 || ^5" 524 | }, 525 | "type": "library", 526 | "extra": { 527 | "branch-alias": { 528 | "dev-main": "3.x-dev" 529 | } 530 | }, 531 | "autoload": { 532 | "psr-4": { 533 | "Composer\\Semver\\": "src" 534 | } 535 | }, 536 | "notification-url": "https://packagist.org/downloads/", 537 | "license": [ 538 | "MIT" 539 | ], 540 | "authors": [ 541 | { 542 | "name": "Nils Adermann", 543 | "email": "naderman@naderman.de", 544 | "homepage": "http://www.naderman.de" 545 | }, 546 | { 547 | "name": "Jordi Boggiano", 548 | "email": "j.boggiano@seld.be", 549 | "homepage": "http://seld.be" 550 | }, 551 | { 552 | "name": "Rob Bast", 553 | "email": "rob.bast@gmail.com", 554 | "homepage": "http://robbast.nl" 555 | } 556 | ], 557 | "description": "Semver library that offers utilities, version constraint parsing and validation.", 558 | "keywords": [ 559 | "semantic", 560 | "semver", 561 | "validation", 562 | "versioning" 563 | ], 564 | "support": { 565 | "irc": "irc://irc.freenode.org/composer", 566 | "issues": "https://github.com/composer/semver/issues", 567 | "source": "https://github.com/composer/semver/tree/3.3.2" 568 | }, 569 | "funding": [ 570 | { 571 | "url": "https://packagist.com", 572 | "type": "custom" 573 | }, 574 | { 575 | "url": "https://github.com/composer", 576 | "type": "github" 577 | }, 578 | { 579 | "url": "https://tidelift.com/funding/github/packagist/composer/composer", 580 | "type": "tidelift" 581 | } 582 | ], 583 | "time": "2022-04-01T19:23:25+00:00" 584 | }, 585 | { 586 | "name": "composer/xdebug-handler", 587 | "version": "3.0.3", 588 | "source": { 589 | "type": "git", 590 | "url": "https://github.com/composer/xdebug-handler.git", 591 | "reference": "ced299686f41dce890debac69273b47ffe98a40c" 592 | }, 593 | "dist": { 594 | "type": "zip", 595 | "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c", 596 | "reference": "ced299686f41dce890debac69273b47ffe98a40c", 597 | "shasum": "" 598 | }, 599 | "require": { 600 | "composer/pcre": "^1 || ^2 || ^3", 601 | "php": "^7.2.5 || ^8.0", 602 | "psr/log": "^1 || ^2 || ^3" 603 | }, 604 | "require-dev": { 605 | "phpstan/phpstan": "^1.0", 606 | "phpstan/phpstan-strict-rules": "^1.1", 607 | "symfony/phpunit-bridge": "^6.0" 608 | }, 609 | "type": "library", 610 | "autoload": { 611 | "psr-4": { 612 | "Composer\\XdebugHandler\\": "src" 613 | } 614 | }, 615 | "notification-url": "https://packagist.org/downloads/", 616 | "license": [ 617 | "MIT" 618 | ], 619 | "authors": [ 620 | { 621 | "name": "John Stevenson", 622 | "email": "john-stevenson@blueyonder.co.uk" 623 | } 624 | ], 625 | "description": "Restarts a process without Xdebug.", 626 | "keywords": [ 627 | "Xdebug", 628 | "performance" 629 | ], 630 | "support": { 631 | "irc": "irc://irc.freenode.org/composer", 632 | "issues": "https://github.com/composer/xdebug-handler/issues", 633 | "source": "https://github.com/composer/xdebug-handler/tree/3.0.3" 634 | }, 635 | "funding": [ 636 | { 637 | "url": "https://packagist.com", 638 | "type": "custom" 639 | }, 640 | { 641 | "url": "https://github.com/composer", 642 | "type": "github" 643 | }, 644 | { 645 | "url": "https://tidelift.com/funding/github/packagist/composer/composer", 646 | "type": "tidelift" 647 | } 648 | ], 649 | "time": "2022-02-25T21:32:43+00:00" 650 | }, 651 | { 652 | "name": "doctrine/annotations", 653 | "version": "2.0.1", 654 | "source": { 655 | "type": "git", 656 | "url": "https://github.com/doctrine/annotations.git", 657 | "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f" 658 | }, 659 | "dist": { 660 | "type": "zip", 661 | "url": "https://api.github.com/repos/doctrine/annotations/zipball/e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", 662 | "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", 663 | "shasum": "" 664 | }, 665 | "require": { 666 | "doctrine/lexer": "^2 || ^3", 667 | "ext-tokenizer": "*", 668 | "php": "^7.2 || ^8.0", 669 | "psr/cache": "^1 || ^2 || ^3" 670 | }, 671 | "require-dev": { 672 | "doctrine/cache": "^2.0", 673 | "doctrine/coding-standard": "^10", 674 | "phpstan/phpstan": "^1.8.0", 675 | "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", 676 | "symfony/cache": "^5.4 || ^6", 677 | "vimeo/psalm": "^4.10" 678 | }, 679 | "suggest": { 680 | "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" 681 | }, 682 | "type": "library", 683 | "autoload": { 684 | "psr-4": { 685 | "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" 686 | } 687 | }, 688 | "notification-url": "https://packagist.org/downloads/", 689 | "license": [ 690 | "MIT" 691 | ], 692 | "authors": [ 693 | { 694 | "name": "Guilherme Blanco", 695 | "email": "guilhermeblanco@gmail.com" 696 | }, 697 | { 698 | "name": "Roman Borschel", 699 | "email": "roman@code-factory.org" 700 | }, 701 | { 702 | "name": "Benjamin Eberlei", 703 | "email": "kontakt@beberlei.de" 704 | }, 705 | { 706 | "name": "Jonathan Wage", 707 | "email": "jonwage@gmail.com" 708 | }, 709 | { 710 | "name": "Johannes Schmitt", 711 | "email": "schmittjoh@gmail.com" 712 | } 713 | ], 714 | "description": "Docblock Annotations Parser", 715 | "homepage": "https://www.doctrine-project.org/projects/annotations.html", 716 | "keywords": [ 717 | "annotations", 718 | "docblock", 719 | "parser" 720 | ], 721 | "support": { 722 | "issues": "https://github.com/doctrine/annotations/issues", 723 | "source": "https://github.com/doctrine/annotations/tree/2.0.1" 724 | }, 725 | "time": "2023-02-02T22:02:53+00:00" 726 | }, 727 | { 728 | "name": "doctrine/deprecations", 729 | "version": "v1.1.1", 730 | "source": { 731 | "type": "git", 732 | "url": "https://github.com/doctrine/deprecations.git", 733 | "reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3" 734 | }, 735 | "dist": { 736 | "type": "zip", 737 | "url": "https://api.github.com/repos/doctrine/deprecations/zipball/612a3ee5ab0d5dd97b7cf3874a6efe24325efac3", 738 | "reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3", 739 | "shasum": "" 740 | }, 741 | "require": { 742 | "php": "^7.1 || ^8.0" 743 | }, 744 | "require-dev": { 745 | "doctrine/coding-standard": "^9", 746 | "phpstan/phpstan": "1.4.10 || 1.10.15", 747 | "phpstan/phpstan-phpunit": "^1.0", 748 | "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", 749 | "psalm/plugin-phpunit": "0.18.4", 750 | "psr/log": "^1 || ^2 || ^3", 751 | "vimeo/psalm": "4.30.0 || 5.12.0" 752 | }, 753 | "suggest": { 754 | "psr/log": "Allows logging deprecations via PSR-3 logger implementation" 755 | }, 756 | "type": "library", 757 | "autoload": { 758 | "psr-4": { 759 | "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" 760 | } 761 | }, 762 | "notification-url": "https://packagist.org/downloads/", 763 | "license": [ 764 | "MIT" 765 | ], 766 | "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", 767 | "homepage": "https://www.doctrine-project.org/", 768 | "support": { 769 | "issues": "https://github.com/doctrine/deprecations/issues", 770 | "source": "https://github.com/doctrine/deprecations/tree/v1.1.1" 771 | }, 772 | "time": "2023-06-03T09:27:29+00:00" 773 | }, 774 | { 775 | "name": "doctrine/instantiator", 776 | "version": "2.0.0", 777 | "source": { 778 | "type": "git", 779 | "url": "https://github.com/doctrine/instantiator.git", 780 | "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" 781 | }, 782 | "dist": { 783 | "type": "zip", 784 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", 785 | "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", 786 | "shasum": "" 787 | }, 788 | "require": { 789 | "php": "^8.1" 790 | }, 791 | "require-dev": { 792 | "doctrine/coding-standard": "^11", 793 | "ext-pdo": "*", 794 | "ext-phar": "*", 795 | "phpbench/phpbench": "^1.2", 796 | "phpstan/phpstan": "^1.9.4", 797 | "phpstan/phpstan-phpunit": "^1.3", 798 | "phpunit/phpunit": "^9.5.27", 799 | "vimeo/psalm": "^5.4" 800 | }, 801 | "type": "library", 802 | "autoload": { 803 | "psr-4": { 804 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 805 | } 806 | }, 807 | "notification-url": "https://packagist.org/downloads/", 808 | "license": [ 809 | "MIT" 810 | ], 811 | "authors": [ 812 | { 813 | "name": "Marco Pivetta", 814 | "email": "ocramius@gmail.com", 815 | "homepage": "https://ocramius.github.io/" 816 | } 817 | ], 818 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 819 | "homepage": "https://www.doctrine-project.org/projects/instantiator.html", 820 | "keywords": [ 821 | "constructor", 822 | "instantiate" 823 | ], 824 | "support": { 825 | "issues": "https://github.com/doctrine/instantiator/issues", 826 | "source": "https://github.com/doctrine/instantiator/tree/2.0.0" 827 | }, 828 | "funding": [ 829 | { 830 | "url": "https://www.doctrine-project.org/sponsorship.html", 831 | "type": "custom" 832 | }, 833 | { 834 | "url": "https://www.patreon.com/phpdoctrine", 835 | "type": "patreon" 836 | }, 837 | { 838 | "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", 839 | "type": "tidelift" 840 | } 841 | ], 842 | "time": "2022-12-30T00:23:10+00:00" 843 | }, 844 | { 845 | "name": "doctrine/lexer", 846 | "version": "3.0.0", 847 | "source": { 848 | "type": "git", 849 | "url": "https://github.com/doctrine/lexer.git", 850 | "reference": "84a527db05647743d50373e0ec53a152f2cde568" 851 | }, 852 | "dist": { 853 | "type": "zip", 854 | "url": "https://api.github.com/repos/doctrine/lexer/zipball/84a527db05647743d50373e0ec53a152f2cde568", 855 | "reference": "84a527db05647743d50373e0ec53a152f2cde568", 856 | "shasum": "" 857 | }, 858 | "require": { 859 | "php": "^8.1" 860 | }, 861 | "require-dev": { 862 | "doctrine/coding-standard": "^10", 863 | "phpstan/phpstan": "^1.9", 864 | "phpunit/phpunit": "^9.5", 865 | "psalm/plugin-phpunit": "^0.18.3", 866 | "vimeo/psalm": "^5.0" 867 | }, 868 | "type": "library", 869 | "autoload": { 870 | "psr-4": { 871 | "Doctrine\\Common\\Lexer\\": "src" 872 | } 873 | }, 874 | "notification-url": "https://packagist.org/downloads/", 875 | "license": [ 876 | "MIT" 877 | ], 878 | "authors": [ 879 | { 880 | "name": "Guilherme Blanco", 881 | "email": "guilhermeblanco@gmail.com" 882 | }, 883 | { 884 | "name": "Roman Borschel", 885 | "email": "roman@code-factory.org" 886 | }, 887 | { 888 | "name": "Johannes Schmitt", 889 | "email": "schmittjoh@gmail.com" 890 | } 891 | ], 892 | "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", 893 | "homepage": "https://www.doctrine-project.org/projects/lexer.html", 894 | "keywords": [ 895 | "annotations", 896 | "docblock", 897 | "lexer", 898 | "parser", 899 | "php" 900 | ], 901 | "support": { 902 | "issues": "https://github.com/doctrine/lexer/issues", 903 | "source": "https://github.com/doctrine/lexer/tree/3.0.0" 904 | }, 905 | "funding": [ 906 | { 907 | "url": "https://www.doctrine-project.org/sponsorship.html", 908 | "type": "custom" 909 | }, 910 | { 911 | "url": "https://www.patreon.com/phpdoctrine", 912 | "type": "patreon" 913 | }, 914 | { 915 | "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", 916 | "type": "tidelift" 917 | } 918 | ], 919 | "time": "2022-12-15T16:57:16+00:00" 920 | }, 921 | { 922 | "name": "friendsofphp/php-cs-fixer", 923 | "version": "v3.23.0", 924 | "source": { 925 | "type": "git", 926 | "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", 927 | "reference": "35af3cbbacfa91e164b252a28ec0b644f1ed4e78" 928 | }, 929 | "dist": { 930 | "type": "zip", 931 | "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/35af3cbbacfa91e164b252a28ec0b644f1ed4e78", 932 | "reference": "35af3cbbacfa91e164b252a28ec0b644f1ed4e78", 933 | "shasum": "" 934 | }, 935 | "require": { 936 | "composer/semver": "^3.3", 937 | "composer/xdebug-handler": "^3.0.3", 938 | "doctrine/annotations": "^2", 939 | "doctrine/lexer": "^2 || ^3", 940 | "ext-json": "*", 941 | "ext-tokenizer": "*", 942 | "php": "^7.4 || ^8.0", 943 | "sebastian/diff": "^4.0 || ^5.0", 944 | "symfony/console": "^5.4 || ^6.0", 945 | "symfony/event-dispatcher": "^5.4 || ^6.0", 946 | "symfony/filesystem": "^5.4 || ^6.0", 947 | "symfony/finder": "^5.4 || ^6.0", 948 | "symfony/options-resolver": "^5.4 || ^6.0", 949 | "symfony/polyfill-mbstring": "^1.27", 950 | "symfony/polyfill-php80": "^1.27", 951 | "symfony/polyfill-php81": "^1.27", 952 | "symfony/process": "^5.4 || ^6.0", 953 | "symfony/stopwatch": "^5.4 || ^6.0" 954 | }, 955 | "require-dev": { 956 | "facile-it/paraunit": "^1.3 || ^2.0", 957 | "justinrainbow/json-schema": "^5.2", 958 | "keradus/cli-executor": "^2.0", 959 | "mikey179/vfsstream": "^1.6.11", 960 | "php-coveralls/php-coveralls": "^2.5.3", 961 | "php-cs-fixer/accessible-object": "^1.1", 962 | "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.2", 963 | "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.2.1", 964 | "phpspec/prophecy": "^1.16", 965 | "phpspec/prophecy-phpunit": "^2.0", 966 | "phpunit/phpunit": "^9.5", 967 | "phpunitgoodpractices/polyfill": "^1.6", 968 | "phpunitgoodpractices/traits": "^1.9.2", 969 | "symfony/phpunit-bridge": "^6.2.3", 970 | "symfony/yaml": "^5.4 || ^6.0" 971 | }, 972 | "suggest": { 973 | "ext-dom": "For handling output formats in XML", 974 | "ext-mbstring": "For handling non-UTF8 characters." 975 | }, 976 | "bin": [ 977 | "php-cs-fixer" 978 | ], 979 | "type": "application", 980 | "autoload": { 981 | "psr-4": { 982 | "PhpCsFixer\\": "src/" 983 | } 984 | }, 985 | "notification-url": "https://packagist.org/downloads/", 986 | "license": [ 987 | "MIT" 988 | ], 989 | "authors": [ 990 | { 991 | "name": "Fabien Potencier", 992 | "email": "fabien@symfony.com" 993 | }, 994 | { 995 | "name": "Dariusz Rumiński", 996 | "email": "dariusz.ruminski@gmail.com" 997 | } 998 | ], 999 | "description": "A tool to automatically fix PHP code style", 1000 | "keywords": [ 1001 | "Static code analysis", 1002 | "fixer", 1003 | "standards", 1004 | "static analysis" 1005 | ], 1006 | "support": { 1007 | "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", 1008 | "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.23.0" 1009 | }, 1010 | "funding": [ 1011 | { 1012 | "url": "https://github.com/keradus", 1013 | "type": "github" 1014 | } 1015 | ], 1016 | "time": "2023-08-14T12:27:35+00:00" 1017 | }, 1018 | { 1019 | "name": "phpdocumentor/reflection-common", 1020 | "version": "2.2.0", 1021 | "source": { 1022 | "type": "git", 1023 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git", 1024 | "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" 1025 | }, 1026 | "dist": { 1027 | "type": "zip", 1028 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", 1029 | "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", 1030 | "shasum": "" 1031 | }, 1032 | "require": { 1033 | "php": "^7.2 || ^8.0" 1034 | }, 1035 | "type": "library", 1036 | "extra": { 1037 | "branch-alias": { 1038 | "dev-2.x": "2.x-dev" 1039 | } 1040 | }, 1041 | "autoload": { 1042 | "psr-4": { 1043 | "phpDocumentor\\Reflection\\": "src/" 1044 | } 1045 | }, 1046 | "notification-url": "https://packagist.org/downloads/", 1047 | "license": [ 1048 | "MIT" 1049 | ], 1050 | "authors": [ 1051 | { 1052 | "name": "Jaap van Otterdijk", 1053 | "email": "opensource@ijaap.nl" 1054 | } 1055 | ], 1056 | "description": "Common reflection classes used by phpdocumentor to reflect the code structure", 1057 | "homepage": "http://www.phpdoc.org", 1058 | "keywords": [ 1059 | "FQSEN", 1060 | "phpDocumentor", 1061 | "phpdoc", 1062 | "reflection", 1063 | "static analysis" 1064 | ], 1065 | "support": { 1066 | "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", 1067 | "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" 1068 | }, 1069 | "time": "2020-06-27T09:03:43+00:00" 1070 | }, 1071 | { 1072 | "name": "phpdocumentor/reflection-docblock", 1073 | "version": "5.3.0", 1074 | "source": { 1075 | "type": "git", 1076 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", 1077 | "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" 1078 | }, 1079 | "dist": { 1080 | "type": "zip", 1081 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", 1082 | "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", 1083 | "shasum": "" 1084 | }, 1085 | "require": { 1086 | "ext-filter": "*", 1087 | "php": "^7.2 || ^8.0", 1088 | "phpdocumentor/reflection-common": "^2.2", 1089 | "phpdocumentor/type-resolver": "^1.3", 1090 | "webmozart/assert": "^1.9.1" 1091 | }, 1092 | "require-dev": { 1093 | "mockery/mockery": "~1.3.2", 1094 | "psalm/phar": "^4.8" 1095 | }, 1096 | "type": "library", 1097 | "extra": { 1098 | "branch-alias": { 1099 | "dev-master": "5.x-dev" 1100 | } 1101 | }, 1102 | "autoload": { 1103 | "psr-4": { 1104 | "phpDocumentor\\Reflection\\": "src" 1105 | } 1106 | }, 1107 | "notification-url": "https://packagist.org/downloads/", 1108 | "license": [ 1109 | "MIT" 1110 | ], 1111 | "authors": [ 1112 | { 1113 | "name": "Mike van Riel", 1114 | "email": "me@mikevanriel.com" 1115 | }, 1116 | { 1117 | "name": "Jaap van Otterdijk", 1118 | "email": "account@ijaap.nl" 1119 | } 1120 | ], 1121 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", 1122 | "support": { 1123 | "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", 1124 | "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" 1125 | }, 1126 | "time": "2021-10-19T17:43:47+00:00" 1127 | }, 1128 | { 1129 | "name": "phpdocumentor/type-resolver", 1130 | "version": "1.7.3", 1131 | "source": { 1132 | "type": "git", 1133 | "url": "https://github.com/phpDocumentor/TypeResolver.git", 1134 | "reference": "3219c6ee25c9ea71e3d9bbaf39c67c9ebd499419" 1135 | }, 1136 | "dist": { 1137 | "type": "zip", 1138 | "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/3219c6ee25c9ea71e3d9bbaf39c67c9ebd499419", 1139 | "reference": "3219c6ee25c9ea71e3d9bbaf39c67c9ebd499419", 1140 | "shasum": "" 1141 | }, 1142 | "require": { 1143 | "doctrine/deprecations": "^1.0", 1144 | "php": "^7.4 || ^8.0", 1145 | "phpdocumentor/reflection-common": "^2.0", 1146 | "phpstan/phpdoc-parser": "^1.13" 1147 | }, 1148 | "require-dev": { 1149 | "ext-tokenizer": "*", 1150 | "phpbench/phpbench": "^1.2", 1151 | "phpstan/extension-installer": "^1.1", 1152 | "phpstan/phpstan": "^1.8", 1153 | "phpstan/phpstan-phpunit": "^1.1", 1154 | "phpunit/phpunit": "^9.5", 1155 | "rector/rector": "^0.13.9", 1156 | "vimeo/psalm": "^4.25" 1157 | }, 1158 | "type": "library", 1159 | "extra": { 1160 | "branch-alias": { 1161 | "dev-1.x": "1.x-dev" 1162 | } 1163 | }, 1164 | "autoload": { 1165 | "psr-4": { 1166 | "phpDocumentor\\Reflection\\": "src" 1167 | } 1168 | }, 1169 | "notification-url": "https://packagist.org/downloads/", 1170 | "license": [ 1171 | "MIT" 1172 | ], 1173 | "authors": [ 1174 | { 1175 | "name": "Mike van Riel", 1176 | "email": "me@mikevanriel.com" 1177 | } 1178 | ], 1179 | "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", 1180 | "support": { 1181 | "issues": "https://github.com/phpDocumentor/TypeResolver/issues", 1182 | "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.7.3" 1183 | }, 1184 | "time": "2023-08-12T11:01:26+00:00" 1185 | }, 1186 | { 1187 | "name": "phpspec/php-diff", 1188 | "version": "v1.1.3", 1189 | "source": { 1190 | "type": "git", 1191 | "url": "https://github.com/phpspec/php-diff.git", 1192 | "reference": "fc1156187f9f6c8395886fe85ed88a0a245d72e9" 1193 | }, 1194 | "dist": { 1195 | "type": "zip", 1196 | "url": "https://api.github.com/repos/phpspec/php-diff/zipball/fc1156187f9f6c8395886fe85ed88a0a245d72e9", 1197 | "reference": "fc1156187f9f6c8395886fe85ed88a0a245d72e9", 1198 | "shasum": "" 1199 | }, 1200 | "type": "library", 1201 | "extra": { 1202 | "branch-alias": { 1203 | "dev-master": "1.0.x-dev" 1204 | } 1205 | }, 1206 | "autoload": { 1207 | "psr-0": { 1208 | "Diff": "lib/" 1209 | } 1210 | }, 1211 | "notification-url": "https://packagist.org/downloads/", 1212 | "license": [ 1213 | "BSD-3-Clause" 1214 | ], 1215 | "authors": [ 1216 | { 1217 | "name": "Chris Boulton", 1218 | "homepage": "http://github.com/chrisboulton" 1219 | } 1220 | ], 1221 | "description": "A comprehensive library for generating differences between two hashable objects (strings or arrays).", 1222 | "support": { 1223 | "source": "https://github.com/phpspec/php-diff/tree/v1.1.3" 1224 | }, 1225 | "time": "2020-09-18T13:47:07+00:00" 1226 | }, 1227 | { 1228 | "name": "phpspec/phpspec", 1229 | "version": "7.4.0", 1230 | "source": { 1231 | "type": "git", 1232 | "url": "https://github.com/phpspec/phpspec.git", 1233 | "reference": "28faa87d1151a15848166226f33de61cb7107d0d" 1234 | }, 1235 | "dist": { 1236 | "type": "zip", 1237 | "url": "https://api.github.com/repos/phpspec/phpspec/zipball/28faa87d1151a15848166226f33de61cb7107d0d", 1238 | "reference": "28faa87d1151a15848166226f33de61cb7107d0d", 1239 | "shasum": "" 1240 | }, 1241 | "require": { 1242 | "doctrine/instantiator": "^1.0.5 || ^2", 1243 | "ext-tokenizer": "*", 1244 | "php": "^7.3 || 8.0.* || 8.1.* || 8.2.*", 1245 | "phpspec/php-diff": "^1.0.0", 1246 | "phpspec/prophecy": "^1.9", 1247 | "sebastian/exporter": "^3.0 || ^4.0 || ^5.0", 1248 | "symfony/console": "^3.4 || ^4.4 || ^5.0 || ^6.0", 1249 | "symfony/event-dispatcher": "^3.4 || ^4.4 || ^5.0 || ^6.0", 1250 | "symfony/finder": "^3.4 || ^4.4 || ^5.0 || ^6.0", 1251 | "symfony/process": "^3.4 || ^4.4 || ^5.0 || ^6.0", 1252 | "symfony/yaml": "^3.4 || ^4.4 || ^5.0 || ^6.0" 1253 | }, 1254 | "conflict": { 1255 | "sebastian/comparator": "<1.2.4" 1256 | }, 1257 | "require-dev": { 1258 | "behat/behat": "^3.3", 1259 | "phpunit/phpunit": "^8.0 || ^9.0 || ^10.0", 1260 | "symfony/filesystem": "^3.4 || ^4.0 || ^5.0 || ^6.0", 1261 | "vimeo/psalm": "^4.3 || ^5.2" 1262 | }, 1263 | "suggest": { 1264 | "phpspec/nyan-formatters": "Adds Nyan formatters" 1265 | }, 1266 | "bin": [ 1267 | "bin/phpspec" 1268 | ], 1269 | "type": "library", 1270 | "extra": { 1271 | "branch-alias": { 1272 | "dev-main": "7.4.x-dev" 1273 | } 1274 | }, 1275 | "autoload": { 1276 | "psr-0": { 1277 | "PhpSpec": "src/" 1278 | } 1279 | }, 1280 | "notification-url": "https://packagist.org/downloads/", 1281 | "license": [ 1282 | "MIT" 1283 | ], 1284 | "authors": [ 1285 | { 1286 | "name": "Konstantin Kudryashov", 1287 | "email": "ever.zet@gmail.com", 1288 | "homepage": "http://everzet.com" 1289 | }, 1290 | { 1291 | "name": "Marcello Duarte", 1292 | "homepage": "http://marcelloduarte.net/" 1293 | }, 1294 | { 1295 | "name": "Ciaran McNulty", 1296 | "homepage": "https://ciaranmcnulty.com/" 1297 | } 1298 | ], 1299 | "description": "Specification-oriented BDD framework for PHP 7.1+", 1300 | "homepage": "http://phpspec.net/", 1301 | "keywords": [ 1302 | "BDD", 1303 | "SpecBDD", 1304 | "TDD", 1305 | "spec", 1306 | "specification", 1307 | "testing", 1308 | "tests" 1309 | ], 1310 | "support": { 1311 | "issues": "https://github.com/phpspec/phpspec/issues", 1312 | "source": "https://github.com/phpspec/phpspec/tree/7.4.0" 1313 | }, 1314 | "time": "2023-04-21T13:17:48+00:00" 1315 | }, 1316 | { 1317 | "name": "phpspec/prophecy", 1318 | "version": "v1.17.0", 1319 | "source": { 1320 | "type": "git", 1321 | "url": "https://github.com/phpspec/prophecy.git", 1322 | "reference": "15873c65b207b07765dbc3c95d20fdf4a320cbe2" 1323 | }, 1324 | "dist": { 1325 | "type": "zip", 1326 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/15873c65b207b07765dbc3c95d20fdf4a320cbe2", 1327 | "reference": "15873c65b207b07765dbc3c95d20fdf4a320cbe2", 1328 | "shasum": "" 1329 | }, 1330 | "require": { 1331 | "doctrine/instantiator": "^1.2 || ^2.0", 1332 | "php": "^7.2 || 8.0.* || 8.1.* || 8.2.*", 1333 | "phpdocumentor/reflection-docblock": "^5.2", 1334 | "sebastian/comparator": "^3.0 || ^4.0", 1335 | "sebastian/recursion-context": "^3.0 || ^4.0" 1336 | }, 1337 | "require-dev": { 1338 | "phpspec/phpspec": "^6.0 || ^7.0", 1339 | "phpstan/phpstan": "^1.9", 1340 | "phpunit/phpunit": "^8.0 || ^9.0" 1341 | }, 1342 | "type": "library", 1343 | "extra": { 1344 | "branch-alias": { 1345 | "dev-master": "1.x-dev" 1346 | } 1347 | }, 1348 | "autoload": { 1349 | "psr-4": { 1350 | "Prophecy\\": "src/Prophecy" 1351 | } 1352 | }, 1353 | "notification-url": "https://packagist.org/downloads/", 1354 | "license": [ 1355 | "MIT" 1356 | ], 1357 | "authors": [ 1358 | { 1359 | "name": "Konstantin Kudryashov", 1360 | "email": "ever.zet@gmail.com", 1361 | "homepage": "http://everzet.com" 1362 | }, 1363 | { 1364 | "name": "Marcello Duarte", 1365 | "email": "marcello.duarte@gmail.com" 1366 | } 1367 | ], 1368 | "description": "Highly opinionated mocking framework for PHP 5.3+", 1369 | "homepage": "https://github.com/phpspec/prophecy", 1370 | "keywords": [ 1371 | "Double", 1372 | "Dummy", 1373 | "fake", 1374 | "mock", 1375 | "spy", 1376 | "stub" 1377 | ], 1378 | "support": { 1379 | "issues": "https://github.com/phpspec/prophecy/issues", 1380 | "source": "https://github.com/phpspec/prophecy/tree/v1.17.0" 1381 | }, 1382 | "time": "2023-02-02T15:41:36+00:00" 1383 | }, 1384 | { 1385 | "name": "phpstan/phpdoc-parser", 1386 | "version": "1.23.1", 1387 | "source": { 1388 | "type": "git", 1389 | "url": "https://github.com/phpstan/phpdoc-parser.git", 1390 | "reference": "846ae76eef31c6d7790fac9bc399ecee45160b26" 1391 | }, 1392 | "dist": { 1393 | "type": "zip", 1394 | "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/846ae76eef31c6d7790fac9bc399ecee45160b26", 1395 | "reference": "846ae76eef31c6d7790fac9bc399ecee45160b26", 1396 | "shasum": "" 1397 | }, 1398 | "require": { 1399 | "php": "^7.2 || ^8.0" 1400 | }, 1401 | "require-dev": { 1402 | "doctrine/annotations": "^2.0", 1403 | "nikic/php-parser": "^4.15", 1404 | "php-parallel-lint/php-parallel-lint": "^1.2", 1405 | "phpstan/extension-installer": "^1.0", 1406 | "phpstan/phpstan": "^1.5", 1407 | "phpstan/phpstan-phpunit": "^1.1", 1408 | "phpstan/phpstan-strict-rules": "^1.0", 1409 | "phpunit/phpunit": "^9.5", 1410 | "symfony/process": "^5.2" 1411 | }, 1412 | "type": "library", 1413 | "autoload": { 1414 | "psr-4": { 1415 | "PHPStan\\PhpDocParser\\": [ 1416 | "src/" 1417 | ] 1418 | } 1419 | }, 1420 | "notification-url": "https://packagist.org/downloads/", 1421 | "license": [ 1422 | "MIT" 1423 | ], 1424 | "description": "PHPDoc parser with support for nullable, intersection and generic types", 1425 | "support": { 1426 | "issues": "https://github.com/phpstan/phpdoc-parser/issues", 1427 | "source": "https://github.com/phpstan/phpdoc-parser/tree/1.23.1" 1428 | }, 1429 | "time": "2023-08-03T16:32:59+00:00" 1430 | }, 1431 | { 1432 | "name": "phpstan/phpstan", 1433 | "version": "1.10.32", 1434 | "source": { 1435 | "type": "git", 1436 | "url": "https://github.com/phpstan/phpstan.git", 1437 | "reference": "c47e47d3ab03137c0e121e77c4d2cb58672f6d44" 1438 | }, 1439 | "dist": { 1440 | "type": "zip", 1441 | "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c47e47d3ab03137c0e121e77c4d2cb58672f6d44", 1442 | "reference": "c47e47d3ab03137c0e121e77c4d2cb58672f6d44", 1443 | "shasum": "" 1444 | }, 1445 | "require": { 1446 | "php": "^7.2|^8.0" 1447 | }, 1448 | "conflict": { 1449 | "phpstan/phpstan-shim": "*" 1450 | }, 1451 | "bin": [ 1452 | "phpstan", 1453 | "phpstan.phar" 1454 | ], 1455 | "type": "library", 1456 | "autoload": { 1457 | "files": [ 1458 | "bootstrap.php" 1459 | ] 1460 | }, 1461 | "notification-url": "https://packagist.org/downloads/", 1462 | "license": [ 1463 | "MIT" 1464 | ], 1465 | "description": "PHPStan - PHP Static Analysis Tool", 1466 | "keywords": [ 1467 | "dev", 1468 | "static analysis" 1469 | ], 1470 | "support": { 1471 | "docs": "https://phpstan.org/user-guide/getting-started", 1472 | "forum": "https://github.com/phpstan/phpstan/discussions", 1473 | "issues": "https://github.com/phpstan/phpstan/issues", 1474 | "security": "https://github.com/phpstan/phpstan/security/policy", 1475 | "source": "https://github.com/phpstan/phpstan-src" 1476 | }, 1477 | "funding": [ 1478 | { 1479 | "url": "https://github.com/ondrejmirtes", 1480 | "type": "github" 1481 | }, 1482 | { 1483 | "url": "https://github.com/phpstan", 1484 | "type": "github" 1485 | }, 1486 | { 1487 | "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", 1488 | "type": "tidelift" 1489 | } 1490 | ], 1491 | "time": "2023-08-24T21:54:50+00:00" 1492 | }, 1493 | { 1494 | "name": "psr/cache", 1495 | "version": "3.0.0", 1496 | "source": { 1497 | "type": "git", 1498 | "url": "https://github.com/php-fig/cache.git", 1499 | "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" 1500 | }, 1501 | "dist": { 1502 | "type": "zip", 1503 | "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", 1504 | "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", 1505 | "shasum": "" 1506 | }, 1507 | "require": { 1508 | "php": ">=8.0.0" 1509 | }, 1510 | "type": "library", 1511 | "extra": { 1512 | "branch-alias": { 1513 | "dev-master": "1.0.x-dev" 1514 | } 1515 | }, 1516 | "autoload": { 1517 | "psr-4": { 1518 | "Psr\\Cache\\": "src/" 1519 | } 1520 | }, 1521 | "notification-url": "https://packagist.org/downloads/", 1522 | "license": [ 1523 | "MIT" 1524 | ], 1525 | "authors": [ 1526 | { 1527 | "name": "PHP-FIG", 1528 | "homepage": "https://www.php-fig.org/" 1529 | } 1530 | ], 1531 | "description": "Common interface for caching libraries", 1532 | "keywords": [ 1533 | "cache", 1534 | "psr", 1535 | "psr-6" 1536 | ], 1537 | "support": { 1538 | "source": "https://github.com/php-fig/cache/tree/3.0.0" 1539 | }, 1540 | "time": "2021-02-03T23:26:27+00:00" 1541 | }, 1542 | { 1543 | "name": "psr/event-dispatcher", 1544 | "version": "1.0.0", 1545 | "source": { 1546 | "type": "git", 1547 | "url": "https://github.com/php-fig/event-dispatcher.git", 1548 | "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" 1549 | }, 1550 | "dist": { 1551 | "type": "zip", 1552 | "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", 1553 | "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", 1554 | "shasum": "" 1555 | }, 1556 | "require": { 1557 | "php": ">=7.2.0" 1558 | }, 1559 | "type": "library", 1560 | "extra": { 1561 | "branch-alias": { 1562 | "dev-master": "1.0.x-dev" 1563 | } 1564 | }, 1565 | "autoload": { 1566 | "psr-4": { 1567 | "Psr\\EventDispatcher\\": "src/" 1568 | } 1569 | }, 1570 | "notification-url": "https://packagist.org/downloads/", 1571 | "license": [ 1572 | "MIT" 1573 | ], 1574 | "authors": [ 1575 | { 1576 | "name": "PHP-FIG", 1577 | "homepage": "http://www.php-fig.org/" 1578 | } 1579 | ], 1580 | "description": "Standard interfaces for event handling.", 1581 | "keywords": [ 1582 | "events", 1583 | "psr", 1584 | "psr-14" 1585 | ], 1586 | "support": { 1587 | "issues": "https://github.com/php-fig/event-dispatcher/issues", 1588 | "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" 1589 | }, 1590 | "time": "2019-01-08T18:20:26+00:00" 1591 | }, 1592 | { 1593 | "name": "sebastian/comparator", 1594 | "version": "4.0.8", 1595 | "source": { 1596 | "type": "git", 1597 | "url": "https://github.com/sebastianbergmann/comparator.git", 1598 | "reference": "fa0f136dd2334583309d32b62544682ee972b51a" 1599 | }, 1600 | "dist": { 1601 | "type": "zip", 1602 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", 1603 | "reference": "fa0f136dd2334583309d32b62544682ee972b51a", 1604 | "shasum": "" 1605 | }, 1606 | "require": { 1607 | "php": ">=7.3", 1608 | "sebastian/diff": "^4.0", 1609 | "sebastian/exporter": "^4.0" 1610 | }, 1611 | "require-dev": { 1612 | "phpunit/phpunit": "^9.3" 1613 | }, 1614 | "type": "library", 1615 | "extra": { 1616 | "branch-alias": { 1617 | "dev-master": "4.0-dev" 1618 | } 1619 | }, 1620 | "autoload": { 1621 | "classmap": [ 1622 | "src/" 1623 | ] 1624 | }, 1625 | "notification-url": "https://packagist.org/downloads/", 1626 | "license": [ 1627 | "BSD-3-Clause" 1628 | ], 1629 | "authors": [ 1630 | { 1631 | "name": "Sebastian Bergmann", 1632 | "email": "sebastian@phpunit.de" 1633 | }, 1634 | { 1635 | "name": "Jeff Welch", 1636 | "email": "whatthejeff@gmail.com" 1637 | }, 1638 | { 1639 | "name": "Volker Dusch", 1640 | "email": "github@wallbash.com" 1641 | }, 1642 | { 1643 | "name": "Bernhard Schussek", 1644 | "email": "bschussek@2bepublished.at" 1645 | } 1646 | ], 1647 | "description": "Provides the functionality to compare PHP values for equality", 1648 | "homepage": "https://github.com/sebastianbergmann/comparator", 1649 | "keywords": [ 1650 | "comparator", 1651 | "compare", 1652 | "equality" 1653 | ], 1654 | "support": { 1655 | "issues": "https://github.com/sebastianbergmann/comparator/issues", 1656 | "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" 1657 | }, 1658 | "funding": [ 1659 | { 1660 | "url": "https://github.com/sebastianbergmann", 1661 | "type": "github" 1662 | } 1663 | ], 1664 | "time": "2022-09-14T12:41:17+00:00" 1665 | }, 1666 | { 1667 | "name": "sebastian/diff", 1668 | "version": "4.0.5", 1669 | "source": { 1670 | "type": "git", 1671 | "url": "https://github.com/sebastianbergmann/diff.git", 1672 | "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" 1673 | }, 1674 | "dist": { 1675 | "type": "zip", 1676 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", 1677 | "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", 1678 | "shasum": "" 1679 | }, 1680 | "require": { 1681 | "php": ">=7.3" 1682 | }, 1683 | "require-dev": { 1684 | "phpunit/phpunit": "^9.3", 1685 | "symfony/process": "^4.2 || ^5" 1686 | }, 1687 | "type": "library", 1688 | "extra": { 1689 | "branch-alias": { 1690 | "dev-master": "4.0-dev" 1691 | } 1692 | }, 1693 | "autoload": { 1694 | "classmap": [ 1695 | "src/" 1696 | ] 1697 | }, 1698 | "notification-url": "https://packagist.org/downloads/", 1699 | "license": [ 1700 | "BSD-3-Clause" 1701 | ], 1702 | "authors": [ 1703 | { 1704 | "name": "Sebastian Bergmann", 1705 | "email": "sebastian@phpunit.de" 1706 | }, 1707 | { 1708 | "name": "Kore Nordmann", 1709 | "email": "mail@kore-nordmann.de" 1710 | } 1711 | ], 1712 | "description": "Diff implementation", 1713 | "homepage": "https://github.com/sebastianbergmann/diff", 1714 | "keywords": [ 1715 | "diff", 1716 | "udiff", 1717 | "unidiff", 1718 | "unified diff" 1719 | ], 1720 | "support": { 1721 | "issues": "https://github.com/sebastianbergmann/diff/issues", 1722 | "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" 1723 | }, 1724 | "funding": [ 1725 | { 1726 | "url": "https://github.com/sebastianbergmann", 1727 | "type": "github" 1728 | } 1729 | ], 1730 | "time": "2023-05-07T05:35:17+00:00" 1731 | }, 1732 | { 1733 | "name": "sebastian/exporter", 1734 | "version": "4.0.5", 1735 | "source": { 1736 | "type": "git", 1737 | "url": "https://github.com/sebastianbergmann/exporter.git", 1738 | "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" 1739 | }, 1740 | "dist": { 1741 | "type": "zip", 1742 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", 1743 | "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", 1744 | "shasum": "" 1745 | }, 1746 | "require": { 1747 | "php": ">=7.3", 1748 | "sebastian/recursion-context": "^4.0" 1749 | }, 1750 | "require-dev": { 1751 | "ext-mbstring": "*", 1752 | "phpunit/phpunit": "^9.3" 1753 | }, 1754 | "type": "library", 1755 | "extra": { 1756 | "branch-alias": { 1757 | "dev-master": "4.0-dev" 1758 | } 1759 | }, 1760 | "autoload": { 1761 | "classmap": [ 1762 | "src/" 1763 | ] 1764 | }, 1765 | "notification-url": "https://packagist.org/downloads/", 1766 | "license": [ 1767 | "BSD-3-Clause" 1768 | ], 1769 | "authors": [ 1770 | { 1771 | "name": "Sebastian Bergmann", 1772 | "email": "sebastian@phpunit.de" 1773 | }, 1774 | { 1775 | "name": "Jeff Welch", 1776 | "email": "whatthejeff@gmail.com" 1777 | }, 1778 | { 1779 | "name": "Volker Dusch", 1780 | "email": "github@wallbash.com" 1781 | }, 1782 | { 1783 | "name": "Adam Harvey", 1784 | "email": "aharvey@php.net" 1785 | }, 1786 | { 1787 | "name": "Bernhard Schussek", 1788 | "email": "bschussek@gmail.com" 1789 | } 1790 | ], 1791 | "description": "Provides the functionality to export PHP variables for visualization", 1792 | "homepage": "https://www.github.com/sebastianbergmann/exporter", 1793 | "keywords": [ 1794 | "export", 1795 | "exporter" 1796 | ], 1797 | "support": { 1798 | "issues": "https://github.com/sebastianbergmann/exporter/issues", 1799 | "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" 1800 | }, 1801 | "funding": [ 1802 | { 1803 | "url": "https://github.com/sebastianbergmann", 1804 | "type": "github" 1805 | } 1806 | ], 1807 | "time": "2022-09-14T06:03:37+00:00" 1808 | }, 1809 | { 1810 | "name": "sebastian/recursion-context", 1811 | "version": "4.0.5", 1812 | "source": { 1813 | "type": "git", 1814 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1815 | "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" 1816 | }, 1817 | "dist": { 1818 | "type": "zip", 1819 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", 1820 | "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", 1821 | "shasum": "" 1822 | }, 1823 | "require": { 1824 | "php": ">=7.3" 1825 | }, 1826 | "require-dev": { 1827 | "phpunit/phpunit": "^9.3" 1828 | }, 1829 | "type": "library", 1830 | "extra": { 1831 | "branch-alias": { 1832 | "dev-master": "4.0-dev" 1833 | } 1834 | }, 1835 | "autoload": { 1836 | "classmap": [ 1837 | "src/" 1838 | ] 1839 | }, 1840 | "notification-url": "https://packagist.org/downloads/", 1841 | "license": [ 1842 | "BSD-3-Clause" 1843 | ], 1844 | "authors": [ 1845 | { 1846 | "name": "Sebastian Bergmann", 1847 | "email": "sebastian@phpunit.de" 1848 | }, 1849 | { 1850 | "name": "Jeff Welch", 1851 | "email": "whatthejeff@gmail.com" 1852 | }, 1853 | { 1854 | "name": "Adam Harvey", 1855 | "email": "aharvey@php.net" 1856 | } 1857 | ], 1858 | "description": "Provides functionality to recursively process PHP variables", 1859 | "homepage": "https://github.com/sebastianbergmann/recursion-context", 1860 | "support": { 1861 | "issues": "https://github.com/sebastianbergmann/recursion-context/issues", 1862 | "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" 1863 | }, 1864 | "funding": [ 1865 | { 1866 | "url": "https://github.com/sebastianbergmann", 1867 | "type": "github" 1868 | } 1869 | ], 1870 | "time": "2023-02-03T06:07:39+00:00" 1871 | }, 1872 | { 1873 | "name": "symfony/console", 1874 | "version": "v6.3.2", 1875 | "source": { 1876 | "type": "git", 1877 | "url": "https://github.com/symfony/console.git", 1878 | "reference": "aa5d64ad3f63f2e48964fc81ee45cb318a723898" 1879 | }, 1880 | "dist": { 1881 | "type": "zip", 1882 | "url": "https://api.github.com/repos/symfony/console/zipball/aa5d64ad3f63f2e48964fc81ee45cb318a723898", 1883 | "reference": "aa5d64ad3f63f2e48964fc81ee45cb318a723898", 1884 | "shasum": "" 1885 | }, 1886 | "require": { 1887 | "php": ">=8.1", 1888 | "symfony/deprecation-contracts": "^2.5|^3", 1889 | "symfony/polyfill-mbstring": "~1.0", 1890 | "symfony/service-contracts": "^2.5|^3", 1891 | "symfony/string": "^5.4|^6.0" 1892 | }, 1893 | "conflict": { 1894 | "symfony/dependency-injection": "<5.4", 1895 | "symfony/dotenv": "<5.4", 1896 | "symfony/event-dispatcher": "<5.4", 1897 | "symfony/lock": "<5.4", 1898 | "symfony/process": "<5.4" 1899 | }, 1900 | "provide": { 1901 | "psr/log-implementation": "1.0|2.0|3.0" 1902 | }, 1903 | "require-dev": { 1904 | "psr/log": "^1|^2|^3", 1905 | "symfony/config": "^5.4|^6.0", 1906 | "symfony/dependency-injection": "^5.4|^6.0", 1907 | "symfony/event-dispatcher": "^5.4|^6.0", 1908 | "symfony/lock": "^5.4|^6.0", 1909 | "symfony/process": "^5.4|^6.0", 1910 | "symfony/var-dumper": "^5.4|^6.0" 1911 | }, 1912 | "type": "library", 1913 | "autoload": { 1914 | "psr-4": { 1915 | "Symfony\\Component\\Console\\": "" 1916 | }, 1917 | "exclude-from-classmap": [ 1918 | "/Tests/" 1919 | ] 1920 | }, 1921 | "notification-url": "https://packagist.org/downloads/", 1922 | "license": [ 1923 | "MIT" 1924 | ], 1925 | "authors": [ 1926 | { 1927 | "name": "Fabien Potencier", 1928 | "email": "fabien@symfony.com" 1929 | }, 1930 | { 1931 | "name": "Symfony Community", 1932 | "homepage": "https://symfony.com/contributors" 1933 | } 1934 | ], 1935 | "description": "Eases the creation of beautiful and testable command line interfaces", 1936 | "homepage": "https://symfony.com", 1937 | "keywords": [ 1938 | "cli", 1939 | "command-line", 1940 | "console", 1941 | "terminal" 1942 | ], 1943 | "support": { 1944 | "source": "https://github.com/symfony/console/tree/v6.3.2" 1945 | }, 1946 | "funding": [ 1947 | { 1948 | "url": "https://symfony.com/sponsor", 1949 | "type": "custom" 1950 | }, 1951 | { 1952 | "url": "https://github.com/fabpot", 1953 | "type": "github" 1954 | }, 1955 | { 1956 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1957 | "type": "tidelift" 1958 | } 1959 | ], 1960 | "time": "2023-07-19T20:17:28+00:00" 1961 | }, 1962 | { 1963 | "name": "symfony/event-dispatcher", 1964 | "version": "v6.3.2", 1965 | "source": { 1966 | "type": "git", 1967 | "url": "https://github.com/symfony/event-dispatcher.git", 1968 | "reference": "adb01fe097a4ee930db9258a3cc906b5beb5cf2e" 1969 | }, 1970 | "dist": { 1971 | "type": "zip", 1972 | "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/adb01fe097a4ee930db9258a3cc906b5beb5cf2e", 1973 | "reference": "adb01fe097a4ee930db9258a3cc906b5beb5cf2e", 1974 | "shasum": "" 1975 | }, 1976 | "require": { 1977 | "php": ">=8.1", 1978 | "symfony/event-dispatcher-contracts": "^2.5|^3" 1979 | }, 1980 | "conflict": { 1981 | "symfony/dependency-injection": "<5.4", 1982 | "symfony/service-contracts": "<2.5" 1983 | }, 1984 | "provide": { 1985 | "psr/event-dispatcher-implementation": "1.0", 1986 | "symfony/event-dispatcher-implementation": "2.0|3.0" 1987 | }, 1988 | "require-dev": { 1989 | "psr/log": "^1|^2|^3", 1990 | "symfony/config": "^5.4|^6.0", 1991 | "symfony/dependency-injection": "^5.4|^6.0", 1992 | "symfony/error-handler": "^5.4|^6.0", 1993 | "symfony/expression-language": "^5.4|^6.0", 1994 | "symfony/http-foundation": "^5.4|^6.0", 1995 | "symfony/service-contracts": "^2.5|^3", 1996 | "symfony/stopwatch": "^5.4|^6.0" 1997 | }, 1998 | "type": "library", 1999 | "autoload": { 2000 | "psr-4": { 2001 | "Symfony\\Component\\EventDispatcher\\": "" 2002 | }, 2003 | "exclude-from-classmap": [ 2004 | "/Tests/" 2005 | ] 2006 | }, 2007 | "notification-url": "https://packagist.org/downloads/", 2008 | "license": [ 2009 | "MIT" 2010 | ], 2011 | "authors": [ 2012 | { 2013 | "name": "Fabien Potencier", 2014 | "email": "fabien@symfony.com" 2015 | }, 2016 | { 2017 | "name": "Symfony Community", 2018 | "homepage": "https://symfony.com/contributors" 2019 | } 2020 | ], 2021 | "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", 2022 | "homepage": "https://symfony.com", 2023 | "support": { 2024 | "source": "https://github.com/symfony/event-dispatcher/tree/v6.3.2" 2025 | }, 2026 | "funding": [ 2027 | { 2028 | "url": "https://symfony.com/sponsor", 2029 | "type": "custom" 2030 | }, 2031 | { 2032 | "url": "https://github.com/fabpot", 2033 | "type": "github" 2034 | }, 2035 | { 2036 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2037 | "type": "tidelift" 2038 | } 2039 | ], 2040 | "time": "2023-07-06T06:56:43+00:00" 2041 | }, 2042 | { 2043 | "name": "symfony/event-dispatcher-contracts", 2044 | "version": "v3.3.0", 2045 | "source": { 2046 | "type": "git", 2047 | "url": "https://github.com/symfony/event-dispatcher-contracts.git", 2048 | "reference": "a76aed96a42d2b521153fb382d418e30d18b59df" 2049 | }, 2050 | "dist": { 2051 | "type": "zip", 2052 | "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/a76aed96a42d2b521153fb382d418e30d18b59df", 2053 | "reference": "a76aed96a42d2b521153fb382d418e30d18b59df", 2054 | "shasum": "" 2055 | }, 2056 | "require": { 2057 | "php": ">=8.1", 2058 | "psr/event-dispatcher": "^1" 2059 | }, 2060 | "type": "library", 2061 | "extra": { 2062 | "branch-alias": { 2063 | "dev-main": "3.4-dev" 2064 | }, 2065 | "thanks": { 2066 | "name": "symfony/contracts", 2067 | "url": "https://github.com/symfony/contracts" 2068 | } 2069 | }, 2070 | "autoload": { 2071 | "psr-4": { 2072 | "Symfony\\Contracts\\EventDispatcher\\": "" 2073 | } 2074 | }, 2075 | "notification-url": "https://packagist.org/downloads/", 2076 | "license": [ 2077 | "MIT" 2078 | ], 2079 | "authors": [ 2080 | { 2081 | "name": "Nicolas Grekas", 2082 | "email": "p@tchwork.com" 2083 | }, 2084 | { 2085 | "name": "Symfony Community", 2086 | "homepage": "https://symfony.com/contributors" 2087 | } 2088 | ], 2089 | "description": "Generic abstractions related to dispatching event", 2090 | "homepage": "https://symfony.com", 2091 | "keywords": [ 2092 | "abstractions", 2093 | "contracts", 2094 | "decoupling", 2095 | "interfaces", 2096 | "interoperability", 2097 | "standards" 2098 | ], 2099 | "support": { 2100 | "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.3.0" 2101 | }, 2102 | "funding": [ 2103 | { 2104 | "url": "https://symfony.com/sponsor", 2105 | "type": "custom" 2106 | }, 2107 | { 2108 | "url": "https://github.com/fabpot", 2109 | "type": "github" 2110 | }, 2111 | { 2112 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2113 | "type": "tidelift" 2114 | } 2115 | ], 2116 | "time": "2023-05-23T14:45:45+00:00" 2117 | }, 2118 | { 2119 | "name": "symfony/filesystem", 2120 | "version": "v6.3.1", 2121 | "source": { 2122 | "type": "git", 2123 | "url": "https://github.com/symfony/filesystem.git", 2124 | "reference": "edd36776956f2a6fcf577edb5b05eb0e3bdc52ae" 2125 | }, 2126 | "dist": { 2127 | "type": "zip", 2128 | "url": "https://api.github.com/repos/symfony/filesystem/zipball/edd36776956f2a6fcf577edb5b05eb0e3bdc52ae", 2129 | "reference": "edd36776956f2a6fcf577edb5b05eb0e3bdc52ae", 2130 | "shasum": "" 2131 | }, 2132 | "require": { 2133 | "php": ">=8.1", 2134 | "symfony/polyfill-ctype": "~1.8", 2135 | "symfony/polyfill-mbstring": "~1.8" 2136 | }, 2137 | "type": "library", 2138 | "autoload": { 2139 | "psr-4": { 2140 | "Symfony\\Component\\Filesystem\\": "" 2141 | }, 2142 | "exclude-from-classmap": [ 2143 | "/Tests/" 2144 | ] 2145 | }, 2146 | "notification-url": "https://packagist.org/downloads/", 2147 | "license": [ 2148 | "MIT" 2149 | ], 2150 | "authors": [ 2151 | { 2152 | "name": "Fabien Potencier", 2153 | "email": "fabien@symfony.com" 2154 | }, 2155 | { 2156 | "name": "Symfony Community", 2157 | "homepage": "https://symfony.com/contributors" 2158 | } 2159 | ], 2160 | "description": "Provides basic utilities for the filesystem", 2161 | "homepage": "https://symfony.com", 2162 | "support": { 2163 | "source": "https://github.com/symfony/filesystem/tree/v6.3.1" 2164 | }, 2165 | "funding": [ 2166 | { 2167 | "url": "https://symfony.com/sponsor", 2168 | "type": "custom" 2169 | }, 2170 | { 2171 | "url": "https://github.com/fabpot", 2172 | "type": "github" 2173 | }, 2174 | { 2175 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2176 | "type": "tidelift" 2177 | } 2178 | ], 2179 | "time": "2023-06-01T08:30:39+00:00" 2180 | }, 2181 | { 2182 | "name": "symfony/finder", 2183 | "version": "v6.3.3", 2184 | "source": { 2185 | "type": "git", 2186 | "url": "https://github.com/symfony/finder.git", 2187 | "reference": "9915db259f67d21eefee768c1abcf1cc61b1fc9e" 2188 | }, 2189 | "dist": { 2190 | "type": "zip", 2191 | "url": "https://api.github.com/repos/symfony/finder/zipball/9915db259f67d21eefee768c1abcf1cc61b1fc9e", 2192 | "reference": "9915db259f67d21eefee768c1abcf1cc61b1fc9e", 2193 | "shasum": "" 2194 | }, 2195 | "require": { 2196 | "php": ">=8.1" 2197 | }, 2198 | "require-dev": { 2199 | "symfony/filesystem": "^6.0" 2200 | }, 2201 | "type": "library", 2202 | "autoload": { 2203 | "psr-4": { 2204 | "Symfony\\Component\\Finder\\": "" 2205 | }, 2206 | "exclude-from-classmap": [ 2207 | "/Tests/" 2208 | ] 2209 | }, 2210 | "notification-url": "https://packagist.org/downloads/", 2211 | "license": [ 2212 | "MIT" 2213 | ], 2214 | "authors": [ 2215 | { 2216 | "name": "Fabien Potencier", 2217 | "email": "fabien@symfony.com" 2218 | }, 2219 | { 2220 | "name": "Symfony Community", 2221 | "homepage": "https://symfony.com/contributors" 2222 | } 2223 | ], 2224 | "description": "Finds files and directories via an intuitive fluent interface", 2225 | "homepage": "https://symfony.com", 2226 | "support": { 2227 | "source": "https://github.com/symfony/finder/tree/v6.3.3" 2228 | }, 2229 | "funding": [ 2230 | { 2231 | "url": "https://symfony.com/sponsor", 2232 | "type": "custom" 2233 | }, 2234 | { 2235 | "url": "https://github.com/fabpot", 2236 | "type": "github" 2237 | }, 2238 | { 2239 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2240 | "type": "tidelift" 2241 | } 2242 | ], 2243 | "time": "2023-07-31T08:31:44+00:00" 2244 | }, 2245 | { 2246 | "name": "symfony/options-resolver", 2247 | "version": "v6.3.0", 2248 | "source": { 2249 | "type": "git", 2250 | "url": "https://github.com/symfony/options-resolver.git", 2251 | "reference": "a10f19f5198d589d5c33333cffe98dc9820332dd" 2252 | }, 2253 | "dist": { 2254 | "type": "zip", 2255 | "url": "https://api.github.com/repos/symfony/options-resolver/zipball/a10f19f5198d589d5c33333cffe98dc9820332dd", 2256 | "reference": "a10f19f5198d589d5c33333cffe98dc9820332dd", 2257 | "shasum": "" 2258 | }, 2259 | "require": { 2260 | "php": ">=8.1", 2261 | "symfony/deprecation-contracts": "^2.5|^3" 2262 | }, 2263 | "type": "library", 2264 | "autoload": { 2265 | "psr-4": { 2266 | "Symfony\\Component\\OptionsResolver\\": "" 2267 | }, 2268 | "exclude-from-classmap": [ 2269 | "/Tests/" 2270 | ] 2271 | }, 2272 | "notification-url": "https://packagist.org/downloads/", 2273 | "license": [ 2274 | "MIT" 2275 | ], 2276 | "authors": [ 2277 | { 2278 | "name": "Fabien Potencier", 2279 | "email": "fabien@symfony.com" 2280 | }, 2281 | { 2282 | "name": "Symfony Community", 2283 | "homepage": "https://symfony.com/contributors" 2284 | } 2285 | ], 2286 | "description": "Provides an improved replacement for the array_replace PHP function", 2287 | "homepage": "https://symfony.com", 2288 | "keywords": [ 2289 | "config", 2290 | "configuration", 2291 | "options" 2292 | ], 2293 | "support": { 2294 | "source": "https://github.com/symfony/options-resolver/tree/v6.3.0" 2295 | }, 2296 | "funding": [ 2297 | { 2298 | "url": "https://symfony.com/sponsor", 2299 | "type": "custom" 2300 | }, 2301 | { 2302 | "url": "https://github.com/fabpot", 2303 | "type": "github" 2304 | }, 2305 | { 2306 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2307 | "type": "tidelift" 2308 | } 2309 | ], 2310 | "time": "2023-05-12T14:21:09+00:00" 2311 | }, 2312 | { 2313 | "name": "symfony/polyfill-ctype", 2314 | "version": "v1.27.0", 2315 | "source": { 2316 | "type": "git", 2317 | "url": "https://github.com/symfony/polyfill-ctype.git", 2318 | "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" 2319 | }, 2320 | "dist": { 2321 | "type": "zip", 2322 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", 2323 | "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", 2324 | "shasum": "" 2325 | }, 2326 | "require": { 2327 | "php": ">=7.1" 2328 | }, 2329 | "provide": { 2330 | "ext-ctype": "*" 2331 | }, 2332 | "suggest": { 2333 | "ext-ctype": "For best performance" 2334 | }, 2335 | "type": "library", 2336 | "extra": { 2337 | "branch-alias": { 2338 | "dev-main": "1.27-dev" 2339 | }, 2340 | "thanks": { 2341 | "name": "symfony/polyfill", 2342 | "url": "https://github.com/symfony/polyfill" 2343 | } 2344 | }, 2345 | "autoload": { 2346 | "files": [ 2347 | "bootstrap.php" 2348 | ], 2349 | "psr-4": { 2350 | "Symfony\\Polyfill\\Ctype\\": "" 2351 | } 2352 | }, 2353 | "notification-url": "https://packagist.org/downloads/", 2354 | "license": [ 2355 | "MIT" 2356 | ], 2357 | "authors": [ 2358 | { 2359 | "name": "Gert de Pagter", 2360 | "email": "BackEndTea@gmail.com" 2361 | }, 2362 | { 2363 | "name": "Symfony Community", 2364 | "homepage": "https://symfony.com/contributors" 2365 | } 2366 | ], 2367 | "description": "Symfony polyfill for ctype functions", 2368 | "homepage": "https://symfony.com", 2369 | "keywords": [ 2370 | "compatibility", 2371 | "ctype", 2372 | "polyfill", 2373 | "portable" 2374 | ], 2375 | "support": { 2376 | "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" 2377 | }, 2378 | "funding": [ 2379 | { 2380 | "url": "https://symfony.com/sponsor", 2381 | "type": "custom" 2382 | }, 2383 | { 2384 | "url": "https://github.com/fabpot", 2385 | "type": "github" 2386 | }, 2387 | { 2388 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2389 | "type": "tidelift" 2390 | } 2391 | ], 2392 | "time": "2022-11-03T14:55:06+00:00" 2393 | }, 2394 | { 2395 | "name": "symfony/polyfill-intl-grapheme", 2396 | "version": "v1.27.0", 2397 | "source": { 2398 | "type": "git", 2399 | "url": "https://github.com/symfony/polyfill-intl-grapheme.git", 2400 | "reference": "511a08c03c1960e08a883f4cffcacd219b758354" 2401 | }, 2402 | "dist": { 2403 | "type": "zip", 2404 | "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354", 2405 | "reference": "511a08c03c1960e08a883f4cffcacd219b758354", 2406 | "shasum": "" 2407 | }, 2408 | "require": { 2409 | "php": ">=7.1" 2410 | }, 2411 | "suggest": { 2412 | "ext-intl": "For best performance" 2413 | }, 2414 | "type": "library", 2415 | "extra": { 2416 | "branch-alias": { 2417 | "dev-main": "1.27-dev" 2418 | }, 2419 | "thanks": { 2420 | "name": "symfony/polyfill", 2421 | "url": "https://github.com/symfony/polyfill" 2422 | } 2423 | }, 2424 | "autoload": { 2425 | "files": [ 2426 | "bootstrap.php" 2427 | ], 2428 | "psr-4": { 2429 | "Symfony\\Polyfill\\Intl\\Grapheme\\": "" 2430 | } 2431 | }, 2432 | "notification-url": "https://packagist.org/downloads/", 2433 | "license": [ 2434 | "MIT" 2435 | ], 2436 | "authors": [ 2437 | { 2438 | "name": "Nicolas Grekas", 2439 | "email": "p@tchwork.com" 2440 | }, 2441 | { 2442 | "name": "Symfony Community", 2443 | "homepage": "https://symfony.com/contributors" 2444 | } 2445 | ], 2446 | "description": "Symfony polyfill for intl's grapheme_* functions", 2447 | "homepage": "https://symfony.com", 2448 | "keywords": [ 2449 | "compatibility", 2450 | "grapheme", 2451 | "intl", 2452 | "polyfill", 2453 | "portable", 2454 | "shim" 2455 | ], 2456 | "support": { 2457 | "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0" 2458 | }, 2459 | "funding": [ 2460 | { 2461 | "url": "https://symfony.com/sponsor", 2462 | "type": "custom" 2463 | }, 2464 | { 2465 | "url": "https://github.com/fabpot", 2466 | "type": "github" 2467 | }, 2468 | { 2469 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2470 | "type": "tidelift" 2471 | } 2472 | ], 2473 | "time": "2022-11-03T14:55:06+00:00" 2474 | }, 2475 | { 2476 | "name": "symfony/polyfill-intl-normalizer", 2477 | "version": "v1.27.0", 2478 | "source": { 2479 | "type": "git", 2480 | "url": "https://github.com/symfony/polyfill-intl-normalizer.git", 2481 | "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" 2482 | }, 2483 | "dist": { 2484 | "type": "zip", 2485 | "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", 2486 | "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", 2487 | "shasum": "" 2488 | }, 2489 | "require": { 2490 | "php": ">=7.1" 2491 | }, 2492 | "suggest": { 2493 | "ext-intl": "For best performance" 2494 | }, 2495 | "type": "library", 2496 | "extra": { 2497 | "branch-alias": { 2498 | "dev-main": "1.27-dev" 2499 | }, 2500 | "thanks": { 2501 | "name": "symfony/polyfill", 2502 | "url": "https://github.com/symfony/polyfill" 2503 | } 2504 | }, 2505 | "autoload": { 2506 | "files": [ 2507 | "bootstrap.php" 2508 | ], 2509 | "psr-4": { 2510 | "Symfony\\Polyfill\\Intl\\Normalizer\\": "" 2511 | }, 2512 | "classmap": [ 2513 | "Resources/stubs" 2514 | ] 2515 | }, 2516 | "notification-url": "https://packagist.org/downloads/", 2517 | "license": [ 2518 | "MIT" 2519 | ], 2520 | "authors": [ 2521 | { 2522 | "name": "Nicolas Grekas", 2523 | "email": "p@tchwork.com" 2524 | }, 2525 | { 2526 | "name": "Symfony Community", 2527 | "homepage": "https://symfony.com/contributors" 2528 | } 2529 | ], 2530 | "description": "Symfony polyfill for intl's Normalizer class and related functions", 2531 | "homepage": "https://symfony.com", 2532 | "keywords": [ 2533 | "compatibility", 2534 | "intl", 2535 | "normalizer", 2536 | "polyfill", 2537 | "portable", 2538 | "shim" 2539 | ], 2540 | "support": { 2541 | "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" 2542 | }, 2543 | "funding": [ 2544 | { 2545 | "url": "https://symfony.com/sponsor", 2546 | "type": "custom" 2547 | }, 2548 | { 2549 | "url": "https://github.com/fabpot", 2550 | "type": "github" 2551 | }, 2552 | { 2553 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2554 | "type": "tidelift" 2555 | } 2556 | ], 2557 | "time": "2022-11-03T14:55:06+00:00" 2558 | }, 2559 | { 2560 | "name": "symfony/polyfill-mbstring", 2561 | "version": "v1.27.0", 2562 | "source": { 2563 | "type": "git", 2564 | "url": "https://github.com/symfony/polyfill-mbstring.git", 2565 | "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" 2566 | }, 2567 | "dist": { 2568 | "type": "zip", 2569 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", 2570 | "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", 2571 | "shasum": "" 2572 | }, 2573 | "require": { 2574 | "php": ">=7.1" 2575 | }, 2576 | "provide": { 2577 | "ext-mbstring": "*" 2578 | }, 2579 | "suggest": { 2580 | "ext-mbstring": "For best performance" 2581 | }, 2582 | "type": "library", 2583 | "extra": { 2584 | "branch-alias": { 2585 | "dev-main": "1.27-dev" 2586 | }, 2587 | "thanks": { 2588 | "name": "symfony/polyfill", 2589 | "url": "https://github.com/symfony/polyfill" 2590 | } 2591 | }, 2592 | "autoload": { 2593 | "files": [ 2594 | "bootstrap.php" 2595 | ], 2596 | "psr-4": { 2597 | "Symfony\\Polyfill\\Mbstring\\": "" 2598 | } 2599 | }, 2600 | "notification-url": "https://packagist.org/downloads/", 2601 | "license": [ 2602 | "MIT" 2603 | ], 2604 | "authors": [ 2605 | { 2606 | "name": "Nicolas Grekas", 2607 | "email": "p@tchwork.com" 2608 | }, 2609 | { 2610 | "name": "Symfony Community", 2611 | "homepage": "https://symfony.com/contributors" 2612 | } 2613 | ], 2614 | "description": "Symfony polyfill for the Mbstring extension", 2615 | "homepage": "https://symfony.com", 2616 | "keywords": [ 2617 | "compatibility", 2618 | "mbstring", 2619 | "polyfill", 2620 | "portable", 2621 | "shim" 2622 | ], 2623 | "support": { 2624 | "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" 2625 | }, 2626 | "funding": [ 2627 | { 2628 | "url": "https://symfony.com/sponsor", 2629 | "type": "custom" 2630 | }, 2631 | { 2632 | "url": "https://github.com/fabpot", 2633 | "type": "github" 2634 | }, 2635 | { 2636 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2637 | "type": "tidelift" 2638 | } 2639 | ], 2640 | "time": "2022-11-03T14:55:06+00:00" 2641 | }, 2642 | { 2643 | "name": "symfony/polyfill-php80", 2644 | "version": "v1.27.0", 2645 | "source": { 2646 | "type": "git", 2647 | "url": "https://github.com/symfony/polyfill-php80.git", 2648 | "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" 2649 | }, 2650 | "dist": { 2651 | "type": "zip", 2652 | "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", 2653 | "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", 2654 | "shasum": "" 2655 | }, 2656 | "require": { 2657 | "php": ">=7.1" 2658 | }, 2659 | "type": "library", 2660 | "extra": { 2661 | "branch-alias": { 2662 | "dev-main": "1.27-dev" 2663 | }, 2664 | "thanks": { 2665 | "name": "symfony/polyfill", 2666 | "url": "https://github.com/symfony/polyfill" 2667 | } 2668 | }, 2669 | "autoload": { 2670 | "files": [ 2671 | "bootstrap.php" 2672 | ], 2673 | "psr-4": { 2674 | "Symfony\\Polyfill\\Php80\\": "" 2675 | }, 2676 | "classmap": [ 2677 | "Resources/stubs" 2678 | ] 2679 | }, 2680 | "notification-url": "https://packagist.org/downloads/", 2681 | "license": [ 2682 | "MIT" 2683 | ], 2684 | "authors": [ 2685 | { 2686 | "name": "Ion Bazan", 2687 | "email": "ion.bazan@gmail.com" 2688 | }, 2689 | { 2690 | "name": "Nicolas Grekas", 2691 | "email": "p@tchwork.com" 2692 | }, 2693 | { 2694 | "name": "Symfony Community", 2695 | "homepage": "https://symfony.com/contributors" 2696 | } 2697 | ], 2698 | "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", 2699 | "homepage": "https://symfony.com", 2700 | "keywords": [ 2701 | "compatibility", 2702 | "polyfill", 2703 | "portable", 2704 | "shim" 2705 | ], 2706 | "support": { 2707 | "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" 2708 | }, 2709 | "funding": [ 2710 | { 2711 | "url": "https://symfony.com/sponsor", 2712 | "type": "custom" 2713 | }, 2714 | { 2715 | "url": "https://github.com/fabpot", 2716 | "type": "github" 2717 | }, 2718 | { 2719 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2720 | "type": "tidelift" 2721 | } 2722 | ], 2723 | "time": "2022-11-03T14:55:06+00:00" 2724 | }, 2725 | { 2726 | "name": "symfony/polyfill-php81", 2727 | "version": "v1.27.0", 2728 | "source": { 2729 | "type": "git", 2730 | "url": "https://github.com/symfony/polyfill-php81.git", 2731 | "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a" 2732 | }, 2733 | "dist": { 2734 | "type": "zip", 2735 | "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/707403074c8ea6e2edaf8794b0157a0bfa52157a", 2736 | "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a", 2737 | "shasum": "" 2738 | }, 2739 | "require": { 2740 | "php": ">=7.1" 2741 | }, 2742 | "type": "library", 2743 | "extra": { 2744 | "branch-alias": { 2745 | "dev-main": "1.27-dev" 2746 | }, 2747 | "thanks": { 2748 | "name": "symfony/polyfill", 2749 | "url": "https://github.com/symfony/polyfill" 2750 | } 2751 | }, 2752 | "autoload": { 2753 | "files": [ 2754 | "bootstrap.php" 2755 | ], 2756 | "psr-4": { 2757 | "Symfony\\Polyfill\\Php81\\": "" 2758 | }, 2759 | "classmap": [ 2760 | "Resources/stubs" 2761 | ] 2762 | }, 2763 | "notification-url": "https://packagist.org/downloads/", 2764 | "license": [ 2765 | "MIT" 2766 | ], 2767 | "authors": [ 2768 | { 2769 | "name": "Nicolas Grekas", 2770 | "email": "p@tchwork.com" 2771 | }, 2772 | { 2773 | "name": "Symfony Community", 2774 | "homepage": "https://symfony.com/contributors" 2775 | } 2776 | ], 2777 | "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", 2778 | "homepage": "https://symfony.com", 2779 | "keywords": [ 2780 | "compatibility", 2781 | "polyfill", 2782 | "portable", 2783 | "shim" 2784 | ], 2785 | "support": { 2786 | "source": "https://github.com/symfony/polyfill-php81/tree/v1.27.0" 2787 | }, 2788 | "funding": [ 2789 | { 2790 | "url": "https://symfony.com/sponsor", 2791 | "type": "custom" 2792 | }, 2793 | { 2794 | "url": "https://github.com/fabpot", 2795 | "type": "github" 2796 | }, 2797 | { 2798 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2799 | "type": "tidelift" 2800 | } 2801 | ], 2802 | "time": "2022-11-03T14:55:06+00:00" 2803 | }, 2804 | { 2805 | "name": "symfony/process", 2806 | "version": "v6.3.2", 2807 | "source": { 2808 | "type": "git", 2809 | "url": "https://github.com/symfony/process.git", 2810 | "reference": "c5ce962db0d9b6e80247ca5eb9af6472bd4d7b5d" 2811 | }, 2812 | "dist": { 2813 | "type": "zip", 2814 | "url": "https://api.github.com/repos/symfony/process/zipball/c5ce962db0d9b6e80247ca5eb9af6472bd4d7b5d", 2815 | "reference": "c5ce962db0d9b6e80247ca5eb9af6472bd4d7b5d", 2816 | "shasum": "" 2817 | }, 2818 | "require": { 2819 | "php": ">=8.1" 2820 | }, 2821 | "type": "library", 2822 | "autoload": { 2823 | "psr-4": { 2824 | "Symfony\\Component\\Process\\": "" 2825 | }, 2826 | "exclude-from-classmap": [ 2827 | "/Tests/" 2828 | ] 2829 | }, 2830 | "notification-url": "https://packagist.org/downloads/", 2831 | "license": [ 2832 | "MIT" 2833 | ], 2834 | "authors": [ 2835 | { 2836 | "name": "Fabien Potencier", 2837 | "email": "fabien@symfony.com" 2838 | }, 2839 | { 2840 | "name": "Symfony Community", 2841 | "homepage": "https://symfony.com/contributors" 2842 | } 2843 | ], 2844 | "description": "Executes commands in sub-processes", 2845 | "homepage": "https://symfony.com", 2846 | "support": { 2847 | "source": "https://github.com/symfony/process/tree/v6.3.2" 2848 | }, 2849 | "funding": [ 2850 | { 2851 | "url": "https://symfony.com/sponsor", 2852 | "type": "custom" 2853 | }, 2854 | { 2855 | "url": "https://github.com/fabpot", 2856 | "type": "github" 2857 | }, 2858 | { 2859 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2860 | "type": "tidelift" 2861 | } 2862 | ], 2863 | "time": "2023-07-12T16:00:22+00:00" 2864 | }, 2865 | { 2866 | "name": "symfony/stopwatch", 2867 | "version": "v6.3.0", 2868 | "source": { 2869 | "type": "git", 2870 | "url": "https://github.com/symfony/stopwatch.git", 2871 | "reference": "fc47f1015ec80927ff64ba9094dfe8b9d48fe9f2" 2872 | }, 2873 | "dist": { 2874 | "type": "zip", 2875 | "url": "https://api.github.com/repos/symfony/stopwatch/zipball/fc47f1015ec80927ff64ba9094dfe8b9d48fe9f2", 2876 | "reference": "fc47f1015ec80927ff64ba9094dfe8b9d48fe9f2", 2877 | "shasum": "" 2878 | }, 2879 | "require": { 2880 | "php": ">=8.1", 2881 | "symfony/service-contracts": "^2.5|^3" 2882 | }, 2883 | "type": "library", 2884 | "autoload": { 2885 | "psr-4": { 2886 | "Symfony\\Component\\Stopwatch\\": "" 2887 | }, 2888 | "exclude-from-classmap": [ 2889 | "/Tests/" 2890 | ] 2891 | }, 2892 | "notification-url": "https://packagist.org/downloads/", 2893 | "license": [ 2894 | "MIT" 2895 | ], 2896 | "authors": [ 2897 | { 2898 | "name": "Fabien Potencier", 2899 | "email": "fabien@symfony.com" 2900 | }, 2901 | { 2902 | "name": "Symfony Community", 2903 | "homepage": "https://symfony.com/contributors" 2904 | } 2905 | ], 2906 | "description": "Provides a way to profile code", 2907 | "homepage": "https://symfony.com", 2908 | "support": { 2909 | "source": "https://github.com/symfony/stopwatch/tree/v6.3.0" 2910 | }, 2911 | "funding": [ 2912 | { 2913 | "url": "https://symfony.com/sponsor", 2914 | "type": "custom" 2915 | }, 2916 | { 2917 | "url": "https://github.com/fabpot", 2918 | "type": "github" 2919 | }, 2920 | { 2921 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2922 | "type": "tidelift" 2923 | } 2924 | ], 2925 | "time": "2023-02-16T10:14:28+00:00" 2926 | }, 2927 | { 2928 | "name": "symfony/string", 2929 | "version": "v6.3.2", 2930 | "source": { 2931 | "type": "git", 2932 | "url": "https://github.com/symfony/string.git", 2933 | "reference": "53d1a83225002635bca3482fcbf963001313fb68" 2934 | }, 2935 | "dist": { 2936 | "type": "zip", 2937 | "url": "https://api.github.com/repos/symfony/string/zipball/53d1a83225002635bca3482fcbf963001313fb68", 2938 | "reference": "53d1a83225002635bca3482fcbf963001313fb68", 2939 | "shasum": "" 2940 | }, 2941 | "require": { 2942 | "php": ">=8.1", 2943 | "symfony/polyfill-ctype": "~1.8", 2944 | "symfony/polyfill-intl-grapheme": "~1.0", 2945 | "symfony/polyfill-intl-normalizer": "~1.0", 2946 | "symfony/polyfill-mbstring": "~1.0" 2947 | }, 2948 | "conflict": { 2949 | "symfony/translation-contracts": "<2.5" 2950 | }, 2951 | "require-dev": { 2952 | "symfony/error-handler": "^5.4|^6.0", 2953 | "symfony/http-client": "^5.4|^6.0", 2954 | "symfony/intl": "^6.2", 2955 | "symfony/translation-contracts": "^2.5|^3.0", 2956 | "symfony/var-exporter": "^5.4|^6.0" 2957 | }, 2958 | "type": "library", 2959 | "autoload": { 2960 | "files": [ 2961 | "Resources/functions.php" 2962 | ], 2963 | "psr-4": { 2964 | "Symfony\\Component\\String\\": "" 2965 | }, 2966 | "exclude-from-classmap": [ 2967 | "/Tests/" 2968 | ] 2969 | }, 2970 | "notification-url": "https://packagist.org/downloads/", 2971 | "license": [ 2972 | "MIT" 2973 | ], 2974 | "authors": [ 2975 | { 2976 | "name": "Nicolas Grekas", 2977 | "email": "p@tchwork.com" 2978 | }, 2979 | { 2980 | "name": "Symfony Community", 2981 | "homepage": "https://symfony.com/contributors" 2982 | } 2983 | ], 2984 | "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", 2985 | "homepage": "https://symfony.com", 2986 | "keywords": [ 2987 | "grapheme", 2988 | "i18n", 2989 | "string", 2990 | "unicode", 2991 | "utf-8", 2992 | "utf8" 2993 | ], 2994 | "support": { 2995 | "source": "https://github.com/symfony/string/tree/v6.3.2" 2996 | }, 2997 | "funding": [ 2998 | { 2999 | "url": "https://symfony.com/sponsor", 3000 | "type": "custom" 3001 | }, 3002 | { 3003 | "url": "https://github.com/fabpot", 3004 | "type": "github" 3005 | }, 3006 | { 3007 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 3008 | "type": "tidelift" 3009 | } 3010 | ], 3011 | "time": "2023-07-05T08:41:27+00:00" 3012 | }, 3013 | { 3014 | "name": "symfony/yaml", 3015 | "version": "v6.3.3", 3016 | "source": { 3017 | "type": "git", 3018 | "url": "https://github.com/symfony/yaml.git", 3019 | "reference": "e23292e8c07c85b971b44c1c4b87af52133e2add" 3020 | }, 3021 | "dist": { 3022 | "type": "zip", 3023 | "url": "https://api.github.com/repos/symfony/yaml/zipball/e23292e8c07c85b971b44c1c4b87af52133e2add", 3024 | "reference": "e23292e8c07c85b971b44c1c4b87af52133e2add", 3025 | "shasum": "" 3026 | }, 3027 | "require": { 3028 | "php": ">=8.1", 3029 | "symfony/deprecation-contracts": "^2.5|^3", 3030 | "symfony/polyfill-ctype": "^1.8" 3031 | }, 3032 | "conflict": { 3033 | "symfony/console": "<5.4" 3034 | }, 3035 | "require-dev": { 3036 | "symfony/console": "^5.4|^6.0" 3037 | }, 3038 | "bin": [ 3039 | "Resources/bin/yaml-lint" 3040 | ], 3041 | "type": "library", 3042 | "autoload": { 3043 | "psr-4": { 3044 | "Symfony\\Component\\Yaml\\": "" 3045 | }, 3046 | "exclude-from-classmap": [ 3047 | "/Tests/" 3048 | ] 3049 | }, 3050 | "notification-url": "https://packagist.org/downloads/", 3051 | "license": [ 3052 | "MIT" 3053 | ], 3054 | "authors": [ 3055 | { 3056 | "name": "Fabien Potencier", 3057 | "email": "fabien@symfony.com" 3058 | }, 3059 | { 3060 | "name": "Symfony Community", 3061 | "homepage": "https://symfony.com/contributors" 3062 | } 3063 | ], 3064 | "description": "Loads and dumps YAML files", 3065 | "homepage": "https://symfony.com", 3066 | "support": { 3067 | "source": "https://github.com/symfony/yaml/tree/v6.3.3" 3068 | }, 3069 | "funding": [ 3070 | { 3071 | "url": "https://symfony.com/sponsor", 3072 | "type": "custom" 3073 | }, 3074 | { 3075 | "url": "https://github.com/fabpot", 3076 | "type": "github" 3077 | }, 3078 | { 3079 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 3080 | "type": "tidelift" 3081 | } 3082 | ], 3083 | "time": "2023-07-31T07:08:24+00:00" 3084 | }, 3085 | { 3086 | "name": "webmozart/assert", 3087 | "version": "1.11.0", 3088 | "source": { 3089 | "type": "git", 3090 | "url": "https://github.com/webmozarts/assert.git", 3091 | "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" 3092 | }, 3093 | "dist": { 3094 | "type": "zip", 3095 | "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", 3096 | "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", 3097 | "shasum": "" 3098 | }, 3099 | "require": { 3100 | "ext-ctype": "*", 3101 | "php": "^7.2 || ^8.0" 3102 | }, 3103 | "conflict": { 3104 | "phpstan/phpstan": "<0.12.20", 3105 | "vimeo/psalm": "<4.6.1 || 4.6.2" 3106 | }, 3107 | "require-dev": { 3108 | "phpunit/phpunit": "^8.5.13" 3109 | }, 3110 | "type": "library", 3111 | "extra": { 3112 | "branch-alias": { 3113 | "dev-master": "1.10-dev" 3114 | } 3115 | }, 3116 | "autoload": { 3117 | "psr-4": { 3118 | "Webmozart\\Assert\\": "src/" 3119 | } 3120 | }, 3121 | "notification-url": "https://packagist.org/downloads/", 3122 | "license": [ 3123 | "MIT" 3124 | ], 3125 | "authors": [ 3126 | { 3127 | "name": "Bernhard Schussek", 3128 | "email": "bschussek@gmail.com" 3129 | } 3130 | ], 3131 | "description": "Assertions to validate method input/output with nice error messages.", 3132 | "keywords": [ 3133 | "assert", 3134 | "check", 3135 | "validate" 3136 | ], 3137 | "support": { 3138 | "issues": "https://github.com/webmozarts/assert/issues", 3139 | "source": "https://github.com/webmozarts/assert/tree/1.11.0" 3140 | }, 3141 | "time": "2022-06-03T18:03:27+00:00" 3142 | } 3143 | ], 3144 | "aliases": [], 3145 | "minimum-stability": "stable", 3146 | "stability-flags": [], 3147 | "prefer-stable": false, 3148 | "prefer-lowest": false, 3149 | "platform": { 3150 | "php": "^8.1" 3151 | }, 3152 | "platform-dev": [], 3153 | "plugin-api-version": "2.3.0" 3154 | } 3155 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | php: 5 | image: 'akeneo/crowdin-api:php-8.1' 6 | build: 7 | context: . 8 | user: 'docker' 9 | working_dir: '/srv/app' 10 | environment: 11 | PHP_IDE_CONFIG: 'serverName=crowdin_api' 12 | XDEBUG_MODE: '${XDEBUG_MODE:-off}' 13 | XDEBUG_CONFIG: 'remote_host=172.17.0.1' 14 | GIT_SSH_COMMAND: 'ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' 15 | volumes: 16 | - './:/srv/app' 17 | -------------------------------------------------------------------------------- /docker/xdebug.ini: -------------------------------------------------------------------------------- 1 | xdebug.max_nesting_level=1000 2 | xdebug.idekey=XDEBUG_IDE_KEY 3 | xdebug.discover_client_host = 1 4 | xdebug.client_port = 9000 5 | xdebug.mode = off 6 | xdebug.start_with_request=yes 7 | xdebug.client_host=172.17.0.1 8 | 9 | ; priority=99 10 | -------------------------------------------------------------------------------- /spec/Akeneo/Crowdin/Api/AddDirectorySpec.php: -------------------------------------------------------------------------------- 1 | getHttpClient()->willReturn($http); 16 | $client->getProjectIdentifier()->willReturn('sylius'); 17 | $client->getProjectApiKey()->willReturn('1234'); 18 | $this->beConstructedWith($client); 19 | } 20 | 21 | public function it_should_be_an_api() 22 | { 23 | $this->shouldBeAnInstanceOf(AbstractApi::class); 24 | } 25 | 26 | public function it_should_not_add_with_no_directory( 27 | HttpClientInterface $http, 28 | ResponseInterface $response 29 | ) { 30 | $content = ''; 31 | $response->getContent()->willReturn($content); 32 | 33 | $http->request('POST', 'project/sylius/add-directory?key=1234')->willReturn($response); 34 | $this->shouldThrow()->duringExecute(); 35 | } 36 | 37 | public function it_adds_a_directory(HttpClientInterface $http, ResponseInterface $response) 38 | { 39 | $this->setDirectory('directory-to-create'); 40 | $content = ''; 41 | $response->getContent()->willReturn($content); 42 | $http->request( 43 | 'POST', 44 | 'project/sylius/add-directory', 45 | [ 46 | 'headers' => ['Authorization' => 'Bearer 1234'], 47 | 'body' => ['name' => 'directory-to-create'] 48 | ] 49 | )->willReturn($response); 50 | 51 | $this->execute()->shouldBe($content); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /spec/Akeneo/Crowdin/Api/AddFileSpec.php: -------------------------------------------------------------------------------- 1 | getHttpClient()->willReturn($http); 21 | $client->getProjectIdentifier()->willReturn('sylius'); 22 | $client->getProjectApiKey()->willReturn('1234'); 23 | $this->beConstructedWith($client, $fileReader); 24 | } 25 | 26 | public function it_should_be_an_api() 27 | { 28 | $this->shouldBeAnInstanceOf(AbstractApi::class); 29 | } 30 | 31 | public function it_should_not_allow_not_existing_file() 32 | { 33 | $this->shouldThrow(\InvalidArgumentException::class)->duringAddTranslation( 34 | 'crowdin/path/file.yml', 35 | '/tmp/my-file.yml' 36 | ); 37 | } 38 | 39 | public function it_has_files() 40 | { 41 | $this->addTranslation(__DIR__ . '/../../../fixtures/messages.en.yml', 'crowdin/path/file.csv'); 42 | $this->getTranslations()->shouldHaveCount(1); 43 | } 44 | 45 | public function it_should_not_add_with_no_file(HttpClientInterface $http, ResponseInterface $response) 46 | { 47 | $content = ''; 48 | $response->getContent()->willReturn($content); 49 | 50 | $http->request( 51 | 'POST', 52 | 'project/sylius/add-file', 53 | ['headers' => ['Authorization' => 'Bearer 1234']] 54 | )->willReturn($response); 55 | $this->shouldThrow('\InvalidArgumentException')->duringExecute(); 56 | } 57 | 58 | public function it_adds_a_file(FileReader $fileReader, HttpClientInterface $http, ResponseInterface $response) 59 | { 60 | $localPath = __DIR__ . '/../../../fixtures/messages.en.yml'; 61 | $this->addTranslation($localPath, 'path/to/crowdin.yml'); 62 | $content = ''; 63 | $response->getContent()->willReturn($content); 64 | $fakeResource = '[fake resource]'; 65 | $fileReader->readTranslation(Argument::any())->willReturn($fakeResource); 66 | $http->request( 67 | 'POST', 68 | 'project/sylius/add-file', 69 | [ 70 | 'headers' => [ 71 | 'Content-Type' => 'multipart/form-data', 72 | 'Authorization' => 'Bearer 1234' 73 | ], 74 | 'body' => [ 75 | 'files[path/to/crowdin.yml]' => $fakeResource, 76 | ], 77 | ] 78 | )->shouldBeCalled()->willReturn($response); 79 | 80 | $this->execute()->shouldBe($content); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /spec/Akeneo/Crowdin/Api/ChangeDirectorySpec.php: -------------------------------------------------------------------------------- 1 | getHttpClient()->willReturn($http); 16 | $client->getProjectIdentifier()->willReturn('sylius'); 17 | $client->getProjectApiKey()->willReturn('1234'); 18 | $this->beConstructedWith($client); 19 | } 20 | 21 | public function it_should_be_an_api() 22 | { 23 | $this->shouldBeAnInstanceOf('Akeneo\Crowdin\Api\AbstractApi'); 24 | } 25 | 26 | public function it_should_have_a_name() 27 | { 28 | $this->shouldThrow()->during('execute', []); 29 | } 30 | 31 | public function it_should_set_name( 32 | HttpClientInterface $http, 33 | ResponseInterface $response 34 | ) { 35 | $this->setName('myname'); 36 | $path = 'project/sylius/change-directory'; 37 | $data = [ 38 | 'headers' => ['Authorization' => 'Bearer 1234'], 39 | 'body' => ['name' => 'myname'] 40 | ]; 41 | $http->request('POST', $path, $data)->willReturn($response); 42 | $response->getContent(Argument::any())->willReturn('content'); 43 | 44 | $this->execute()->shouldReturn('content'); 45 | } 46 | 47 | public function it_should_set_data( 48 | HttpClientInterface $http, 49 | ResponseInterface $response 50 | ) { 51 | $this->setName('myName'); 52 | $this->setBranch('myBranch'); 53 | $this->setExportPattern('myExportPattern'); 54 | $this->setTitle('myTitle'); 55 | $this->setNewName('myNewName'); 56 | $path = 'project/sylius/change-directory'; 57 | $data = [ 58 | 'headers' => ['Authorization' => 'Bearer 1234'], 59 | 'body' => [ 60 | 'name' => 'myName', 61 | 'branch' => 'myBranch', 62 | 'export_pattern' => 'myExportPattern', 63 | 'title' => 'myTitle', 64 | 'new_name' => 'myNewName', 65 | ], 66 | ]; 67 | $http->request('POST', $path, $data)->willReturn($response); 68 | $response->getContent()->willReturn('content'); 69 | 70 | $this->execute()->shouldReturn('content'); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /spec/Akeneo/Crowdin/Api/DeleteDirectorySpec.php: -------------------------------------------------------------------------------- 1 | getHttpClient()->willReturn($http); 15 | $client->getProjectIdentifier()->willReturn('sylius'); 16 | $client->getProjectApiKey()->willReturn('1234'); 17 | $this->beConstructedWith($client); 18 | } 19 | 20 | public function it_should_be_an_api() 21 | { 22 | $this->shouldBeAnInstanceOf('Akeneo\Crowdin\Api\AbstractApi'); 23 | } 24 | 25 | public function it_should_not_delete_with_no_directory(HttpClientInterface $http, ResponseInterface $response) 26 | { 27 | $content = ''; 28 | $response->getContent()->willReturn($content); 29 | 30 | $http->request('POST', 'project/sylius/delete-directory', ['headers' => ['Authorization' => 'Bearer 1234']])->willReturn($response); 31 | $this->shouldThrow()->duringExecute(); 32 | } 33 | 34 | public function it_deletes_a_directory(HttpClientInterface $http, ResponseInterface $response) 35 | { 36 | $this->setDirectory('directory-to-delete'); 37 | $content = ''; 38 | $response->getContent()->willReturn($content); 39 | $http->request( 40 | 'POST', 41 | 'project/sylius/delete-directory', 42 | [ 43 | 'headers' => ['Authorization' => 'Bearer 1234'], 44 | 'body' => ['name' => 'directory-to-delete'] 45 | ] 46 | )->willReturn($response); 47 | 48 | $this->execute()->shouldBe($content); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /spec/Akeneo/Crowdin/Api/DeleteFileSpec.php: -------------------------------------------------------------------------------- 1 | getHttpClient()->willReturn($http); 18 | $client->getProjectIdentifier()->willReturn('sylius'); 19 | $client->getProjectApiKey()->willReturn('1234'); 20 | $this->beConstructedWith($client); 21 | } 22 | 23 | public function it_should_be_an_api() 24 | { 25 | $this->shouldBeAnInstanceOf('Akeneo\Crowdin\Api\AbstractApi'); 26 | } 27 | 28 | public function it_should_not_delete_with_no_file(HttpClientInterface $http, ResponseInterface $response) 29 | { 30 | $content = ''; 31 | $response->getContent()->willReturn($content); 32 | 33 | $http->request('POST', 'project/sylius/delete-file', ['headers' => ['Authorization' => 'Bearer 1234']])->willReturn($response); 34 | $this->shouldThrow()->duringExecute(); 35 | } 36 | 37 | public function it_deletes_a_file(HttpClientInterface $http, ResponseInterface $response) 38 | { 39 | $this->setFile('path/to/my/file'); 40 | $content = ''; 41 | $response->getContent()->willReturn($content); 42 | $http->request( 43 | 'POST', 44 | 'project/sylius/delete-file', 45 | [ 46 | 'headers' => ['Authorization' => 'Bearer 1234'], 47 | 'body' => ['file' => 'path/to/my/file'] 48 | ] 49 | )->willReturn($response); 50 | 51 | $this->execute()->shouldBe($content); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /spec/Akeneo/Crowdin/Api/DownloadSpec.php: -------------------------------------------------------------------------------- 1 | getHttpClient()->willReturn($http); 15 | $client->getProjectIdentifier()->willReturn('akeneo'); 16 | $client->getProjectApiKey()->willReturn('1234'); 17 | $this->beConstructedWith($client); 18 | } 19 | 20 | public function it_should_be_an_api() 21 | { 22 | $this->shouldBeAnInstanceOf('Akeneo\Crowdin\Api\AbstractApi'); 23 | } 24 | 25 | public function it_has_a_package_with_all_languages() 26 | { 27 | $this->setPackage('all.zip'); 28 | $this->getPackage()->shouldReturn('all.zip'); 29 | } 30 | 31 | public function it_has_a_package_with_one_language() 32 | { 33 | $this->setPackage('fr.zip'); 34 | $this->getPackage()->shouldReturn('fr.zip'); 35 | } 36 | 37 | public function it_has_a_copy_destination() 38 | { 39 | $this->setCopyDestination('/tmp/'); 40 | $this->getCopyDestination()->shouldReturn('/tmp/'); 41 | } 42 | 43 | public function it_downloads_all_translations(HttpClientInterface $http, ResponseInterface $response) 44 | { 45 | $this->setCopyDestination('/tmp'); 46 | $this->setPackage('all.zip'); 47 | $http->request('GET', 'project/akeneo/download/all.zip', ['headers' => ['Authorization' => 'Bearer 1234']]) 48 | ->willReturn($response); 49 | $response->getContent()->willReturn('translations content'); 50 | $this->execute()->shouldBe('translations content'); 51 | } 52 | 53 | public function it_downloads_french_translations(HttpClientInterface $http, ResponseInterface $response) 54 | { 55 | $this->setCopyDestination('/tmp'); 56 | $this->setPackage('fr.zip'); 57 | $http->request('GET', 'project/akeneo/download/fr.zip', ['headers' => ['Authorization' => 'Bearer 1234']]) 58 | ->willReturn($response); 59 | $response->getContent()->willReturn('translations content'); 60 | $this->execute()->shouldBe('translations content'); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /spec/Akeneo/Crowdin/Api/ExportSpec.php: -------------------------------------------------------------------------------- 1 | getHttpClient()->willReturn($http); 15 | $client->getProjectIdentifier()->willReturn('akeneo'); 16 | $client->getProjectApiKey()->willReturn('1234'); 17 | $this->beConstructedWith($client); 18 | } 19 | 20 | public function it_should_be_an_api() 21 | { 22 | $this->shouldBeAnInstanceOf('Akeneo\Crowdin\Api\AbstractApi'); 23 | } 24 | 25 | public function it_builds_last_translations(HttpClientInterface $http, ResponseInterface $response) 26 | { 27 | $content = ''; 28 | $response->getContent()->willReturn($content); 29 | $http->request('GET', 'project/akeneo/export', ['headers' => ['Authorization' => 'Bearer 1234']])->willReturn($response); 30 | $this->execute()->shouldBe($content); 31 | } 32 | 33 | public function it_skips_build_if_less_than_half_an_hour(HttpClientInterface $http, ResponseInterface $response) 34 | { 35 | $content = ''; 36 | $response->getContent()->willReturn($content); 37 | $http->request('GET', 'project/akeneo/export', ['headers' => ['Authorization' => 'Bearer 1234']])->willReturn($response); 38 | $this->execute()->shouldBe($content); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /spec/Akeneo/Crowdin/Api/InfoSpec.php: -------------------------------------------------------------------------------- 1 | getHttpClient()->willReturn($http); 16 | $client->getProjectIdentifier()->willReturn('akeneo'); 17 | $client->getProjectApiKey()->willReturn('1234'); 18 | $http->request('GET', 'project/akeneo/info', ['headers' => ['Authorization' => 'Bearer 1234']])->willReturn($response); 19 | $response->getContent()->willReturn(''); 20 | $this->beConstructedWith($client); 21 | } 22 | 23 | public function it_should_be_an_api() 24 | { 25 | $this->shouldBeAnInstanceOf(AbstractApi::class); 26 | } 27 | 28 | public function it_gets_project_info() 29 | { 30 | $this->execute()->shouldBe(''); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /spec/Akeneo/Crowdin/Api/LanguageStatusSpec.php: -------------------------------------------------------------------------------- 1 | getHttpClient()->willReturn($http); 15 | $client->getProjectIdentifier()->willReturn('akeneo'); 16 | $client->getProjectApiKey()->willReturn('1234'); 17 | $this->beConstructedWith($client); 18 | } 19 | 20 | public function it_should_be_an_api() 21 | { 22 | $this->shouldBeAnInstanceOf('Akeneo\Crowdin\Api\AbstractApi'); 23 | } 24 | 25 | public function it_gets_project_language_status( 26 | HttpClientInterface $http, 27 | ResponseInterface $response 28 | ) { 29 | $this->setLanguage('fr')->shouldBe($this); 30 | $http->request( 31 | 'POST', 32 | 'project/akeneo/language-status', 33 | [ 34 | 'headers' => ['Authorization' => 'Bearer 1234'], 35 | 'body' => ['language' => 'fr'] 36 | ] 37 | )->willReturn($response); 38 | $response->getContent()->willReturn(''); 39 | $this->execute()->shouldBe(''); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /spec/Akeneo/Crowdin/Api/StatusSpec.php: -------------------------------------------------------------------------------- 1 | getHttpClient()->willReturn($http); 15 | $client->getProjectIdentifier()->willReturn('akeneo'); 16 | $client->getProjectApiKey()->willReturn('1234'); 17 | $http->request('GET', 'project/akeneo/status', ['headers' => ['Authorization' => 'Bearer 1234']])->willReturn($response); 18 | $response->getContent()->willReturn(''); 19 | $this->beConstructedWith($client); 20 | } 21 | 22 | public function it_should_be_an_api() 23 | { 24 | $this->shouldBeAnInstanceOf('Akeneo\Crowdin\Api\AbstractApi'); 25 | } 26 | 27 | public function it_gets_project_translations_status() 28 | { 29 | $this->execute()->shouldBe(''); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /spec/Akeneo/Crowdin/Api/SupportedLanguagesSpec.php: -------------------------------------------------------------------------------- 1 | getHttpClient()->willReturn($http); 19 | $client->getProjectApiKey()->willReturn('1234'); 20 | $http->request('GET', 'supported-languages', ['headers' => ['Authorization' => 'Bearer 1234']])->willReturn($response); 21 | $response->getContent()->willReturn(''); 22 | $this->beConstructedWith($client); 23 | } 24 | 25 | public function it_should_be_an_api() 26 | { 27 | $this->shouldBeAnInstanceOf('Akeneo\Crowdin\Api\AbstractApi'); 28 | } 29 | 30 | public function it_gets_supported_languages() 31 | { 32 | $this->execute()->shouldBe(''); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /spec/Akeneo/Crowdin/Api/UpdateFileSpec.php: -------------------------------------------------------------------------------- 1 | getHttpClient()->willReturn($http); 20 | $client->getProjectIdentifier()->willReturn('akeneo'); 21 | $client->getProjectApiKey()->willReturn('1234'); 22 | $this->beConstructedWith($client, $fileReader); 23 | } 24 | 25 | public function it_should_be_an_api() 26 | { 27 | $this->shouldBeAnInstanceOf('Akeneo\Crowdin\Api\AbstractApi'); 28 | } 29 | 30 | public function it_should_not_allow_not_existing_file() 31 | { 32 | $this->shouldThrow('\InvalidArgumentException')->duringAddTranslation( 33 | 'crowdin/path/file.yml', 34 | '/tmp/my-file.yml' 35 | ); 36 | } 37 | 38 | public function it_has_files() 39 | { 40 | $this->addTranslation(__DIR__ . '/../../../fixtures/messages.en.yml', 'crowdin/path/file.csv'); 41 | $this->getTranslations()->shouldHaveCount(1); 42 | } 43 | 44 | public function it_should_not_allow_update_with_no_file(HttpClientInterface $http, ResponseInterface $response) 45 | { 46 | $content = ''; 47 | $response->getContent(true)->willReturn($content); 48 | $http->request( 49 | 'POST', 50 | 'project/akeneo/update-file', 51 | ['headers' => ['Authorization' => 'Bearer 1234']] 52 | )->willReturn($response); 53 | $this->shouldThrow()->duringExecute(); 54 | } 55 | 56 | public function it_updates_some_translation_files( 57 | $fileReader, 58 | HttpClientInterface $http, 59 | ResponseInterface $response 60 | ) { 61 | $localPath = __DIR__ . '/../../../fixtures/messages.en.yml'; 62 | $this->addTranslation($localPath, 'path/to/crowdin.yml'); 63 | $content = ''; 64 | $response->getContent()->willReturn($content); 65 | $fakeResource = '[fake resource]'; 66 | $fileReader->readTranslation(Argument::any())->willReturn($fakeResource); 67 | 68 | $http->request( 69 | 'POST', 70 | 'project/akeneo/update-file', 71 | [ 72 | 'headers' => [ 73 | 'Authorization' => 'Bearer 1234', 74 | 'Content-Type' => 'multipart/form-data' 75 | ], 76 | 'body' => [ 77 | 'files[path/to/crowdin.yml]' => $fakeResource, 78 | ], 79 | ] 80 | )->willReturn($response); 81 | $this->execute()->shouldBe($content); 82 | } 83 | 84 | public function it_sends_additionnal_parameters( 85 | FileReader $fileReader, 86 | HttpClientInterface $http, 87 | ResponseInterface $response 88 | ) { 89 | $fakeResource = '[fake resource]'; 90 | $fileReader->readTranslation(Argument::any())->willReturn($fakeResource); 91 | 92 | $http->request( 93 | 'POST', 94 | Argument::any(), 95 | [ 96 | 'headers' => [ 97 | 'Authorization' => 'Bearer 1234', 98 | 'Content-Type' => 'multipart/form-data' 99 | ], 100 | 'body' => [ 101 | 'foo' => 'bar', 102 | 'files[path/to/crowdin.yml]' => $fakeResource, 103 | ], 104 | ] 105 | )->shouldBeCalled()->willReturn($response); 106 | $response->getContent()->willReturn('content'); 107 | 108 | $this->addTranslation(__DIR__ . '/../../../fixtures/messages.en.yml', 'path/to/crowdin.yml'); 109 | $this->setParameters(['foo' => 'bar']); 110 | $this->execute(); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /spec/Akeneo/Crowdin/Api/UploadTranslationSpec.php: -------------------------------------------------------------------------------- 1 | getHttpClient()->willReturn($http); 20 | $client->getProjectIdentifier()->willReturn('sylius'); 21 | $client->getProjectApiKey()->willReturn('1234'); 22 | $this->beConstructedWith($client, $fileReader); 23 | } 24 | 25 | public function it_should_be_an_api() 26 | { 27 | $this->shouldBeAnInstanceOf('Akeneo\Crowdin\Api\AbstractApi'); 28 | } 29 | 30 | public function it_should_not_allow_not_existing_translation() 31 | { 32 | $this->shouldThrow('\InvalidArgumentException')->duringAddTranslation( 33 | 'crowdin/path/file.yml', 34 | '/tmp/my-file.yml' 35 | ); 36 | } 37 | 38 | public function it_has_translations() 39 | { 40 | $this->addTranslation('spec/fixtures/messages.en.yml', 'crowdin/path/file.csv'); 41 | $this->getTranslations()->shouldHaveCount(1); 42 | } 43 | 44 | public function it_does_not_import_duplicates_by_default() 45 | { 46 | $this->areDuplicatesImported()->shouldBe(false); 47 | } 48 | 49 | public function it_does_not_import_equal_suggestions_by_default() 50 | { 51 | $this->areEqualSuggestionsImported()->shouldBe(false); 52 | } 53 | 54 | public function it_does_not_auto_approve_imported_by_default() 55 | { 56 | $this->areImportsAutoApproved()->shouldBe(false); 57 | } 58 | 59 | public function it_should_not_allow_upload_with_no_translation( 60 | HttpClientInterface $http, 61 | ResponseInterface $response 62 | ) { 63 | $this->setLocale('fr'); 64 | $content = ''; 65 | $response->getContent()->willReturn($content); 66 | $http->request( 67 | 'POST', 68 | 'project/sylius/upload-translation', 69 | ['headers' => ['Authorization' => 'Bearer 1234']] 70 | )->willReturn($response); 71 | 72 | $this->shouldThrow('\InvalidArgumentException')->duringExecute(); 73 | } 74 | 75 | public function it_should_not_allow_upload_with_no_locale(HttpClientInterface $http, ResponseInterface $response) 76 | { 77 | $this->addTranslation('spec/fixtures/messages.en.yml', 'crowdin/path/file.yml'); 78 | $content = ''; 79 | $response->getContent()->willReturn($content); 80 | $http->request( 81 | 'POST', 82 | 'project/sylius/upload-translation', 83 | ['headers' => ['Authorization' => 'Bearer 1234']] 84 | )->willReturn($response); 85 | 86 | $this->shouldThrow()->duringExecute(); 87 | } 88 | 89 | public function it_uploads_some_translations( 90 | FileReader $fileReader, 91 | HttpClientInterface $http, 92 | ResponseInterface $response 93 | ) { 94 | $this->addTranslation('spec/fixtures/messages.en.yml', 'crowdin/path/file.yml'); 95 | $this->setLocale('fr'); 96 | $content = ''; 97 | $response->getContent()->willReturn($content); 98 | $fakeResource = '[fake resource]'; 99 | $fileReader->readTranslation(Argument::any())->willReturn($fakeResource); 100 | $http->request( 101 | 'POST', 102 | 'project/sylius/upload-translation', 103 | [ 104 | 'headers' => [ 105 | 'Authorization' => 'Bearer 1234', 106 | 'Content-Type' => 'multipart/form-data' 107 | ], 108 | 'body' => [ 109 | 'import_duplicates' => 0, 110 | 'import_eq_suggestions' => 0, 111 | 'auto_approve_imported' => 0, 112 | 'language' => 'fr', 113 | 'files[crowdin/path/file.yml]' => $fakeResource, 114 | ], 115 | ] 116 | )->willReturn($response); 117 | $this->execute()->shouldBe($content); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /spec/Akeneo/Crowdin/ClientSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith('Akeneo', 'my_key'); 14 | } 15 | 16 | public function it_has_a_project_identifier() 17 | { 18 | $this->getProjectIdentifier()->shouldReturn('Akeneo'); 19 | } 20 | 21 | public function it_has_a_project_api_key() 22 | { 23 | $this->getProjectApiKey()->shouldReturn('my_key'); 24 | } 25 | 26 | public function it_has_a_http_client() 27 | { 28 | $this->getHttpClient()->shouldBeAnInstanceOf(HttpClientInterface::class); 29 | } 30 | 31 | public function it_allow_defined_api_method() 32 | { 33 | $this->api('download')->shouldReturnAnInstanceOf(Download::class); 34 | } 35 | 36 | public function it_should_not_allow_undefined_api_method() 37 | { 38 | $this->shouldThrow(\InvalidArgumentException::class)->duringApi('unknow'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /spec/Akeneo/Crowdin/FileReaderSpec.php: -------------------------------------------------------------------------------- 1 | getLocalPath()->willReturn(__DIR__ . self::EXISTING_FILE_PATH); 17 | $this->readTranslation($translation)->shouldBeResource(); 18 | } 19 | 20 | public function it_throws_an_exception_when_file_cannot_be_found(Translation $translation) 21 | { 22 | $translation->getLocalPath()->willReturn(__DIR__ . self::NOT_EXISTING_FILE_PATH); 23 | $this 24 | ->shouldThrow('Akeneo\Crowdin\FileNotFoundException') 25 | ->during('readTranslation', [$translation]); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /spec/Akeneo/Crowdin/TranslationSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith(__DIR__ . '/../../fixtures/messages.en.yml', 'crowdin_path'); 14 | } 15 | 16 | public function it_has_a_local_path() 17 | { 18 | $this->getLocalPath()->shouldReturn(__DIR__ . '/../../fixtures/messages.en.yml'); 19 | } 20 | 21 | public function it_has_a_crowdin_path() 22 | { 23 | $this->getCrowdinPath()->shouldReturn('crowdin_path'); 24 | } 25 | 26 | public function it_has_no_title_by_default() 27 | { 28 | $this->getTitle()->shouldReturn(null); 29 | } 30 | 31 | public function it_has_no_export_pattern_by_default() 32 | { 33 | $this->getExportPattern()->shouldReturn(null); 34 | } 35 | 36 | public function its_local_path_should_be_mutable() 37 | { 38 | $this->setLocalPath(__DIR__ . '/../../fixtures/messages.en.yml'); 39 | $this->getLocalPath()->shouldReturn(__DIR__ . '/../../fixtures/messages.en.yml'); 40 | } 41 | 42 | public function its_crowdin_path_should_be_mutable() 43 | { 44 | $this->setCrowdinPath('my/path/to/crowdin.yml'); 45 | $this->getCrowdinPath()->shouldReturn('my/path/to/crowdin.yml'); 46 | } 47 | 48 | public function its_title_path_should_be_mutable() 49 | { 50 | $this->setTitle('The title of my translation'); 51 | $this->getTitle()->shouldReturn('The title of my translation'); 52 | } 53 | 54 | public function its_export_pattern_should_be_mutable() 55 | { 56 | $this->setExportPattern('my/path/to/crowdin%two_letters_code%.yml'); 57 | $this->getExportPattern()->shouldReturn('my/path/to/crowdin%two_letters_code%.yml'); 58 | } 59 | 60 | public function it_should_trow_an_exception_when_a_local_file_does_not_exist() 61 | { 62 | $this 63 | ->shouldThrow(new InvalidArgumentException('File local_path does not exist')) 64 | ->duringSetLocalPath('local_path') 65 | ; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /spec/fixtures/messages.en.yml: -------------------------------------------------------------------------------- 1 | # Entities 2 | attribute: attribute 3 | attribute group: attribute group 4 | category: category 5 | tree: tree 6 | channel: channel 7 | currency: currency 8 | export profile: export profile 9 | family: family 10 | import profile: import profile 11 | locale: locale 12 | product: product 13 | group: group 14 | variant group: variant group 15 | association: association 16 | group type: group type 17 | 18 | # Page header titles 19 | attribute: 20 | overview: attribute overview 21 | create: create attribute 22 | edit: edit attribute 23 | attribute group: 24 | overview: attribute group overview 25 | create: create attribute group 26 | edit: edit attribute group 27 | category: 28 | overview: category overview 29 | create: create category 30 | edit: edit category 31 | tree: 32 | create: create tree 33 | edit: edit tree 34 | channel: 35 | overview: channel overview 36 | create: create channel 37 | edit: edit channel 38 | currency: 39 | overview: currency overview 40 | create: create currency 41 | edit: edit currency 42 | export profile: 43 | overview: export profile overview 44 | create: create export profile 45 | edit: edit export profile 46 | export report: 47 | overview: export reports 48 | family: 49 | overview: family overview 50 | create: create family 51 | edit: edit family 52 | import profile: 53 | overview: import profile overview 54 | create: create import profile 55 | edit: edit import profile 56 | import report: 57 | overview: import reports 58 | locale: 59 | overview: locale overview 60 | create: create locale 61 | edit: edit locale 62 | product: 63 | overview: product overview 64 | create: create product 65 | edit: edit product 66 | group: 67 | overview: group overview 68 | create: create group 69 | edit: edit group 70 | group type: 71 | overview: group type overview 72 | create: create group type 73 | edit: edit group type 74 | variant group: 75 | overview: variant group overview 76 | create: create variant group 77 | edit: edit variant group 78 | association: 79 | overview: association overview 80 | create: create association 81 | edit: edit association 82 | 83 | # Custom titles 84 | products: products 85 | 86 | # Buttons 87 | btn: 88 | create: 89 | attribute: create attribute 90 | attribute group: create attribute group 91 | category: create category 92 | tree: create tree 93 | channel: create channel 94 | currency: create currency 95 | export profile: create export profile 96 | family: create family 97 | import profile: create import profile 98 | locale: create locale 99 | product: create product 100 | group: create group 101 | variant group: create variant group 102 | association: create association 103 | group type: create group type 104 | edit: edit 105 | save: save 106 | remove: remove 107 | delete: delete 108 | cancel: cancel 109 | next: next 110 | back: back 111 | confirm: confirm 112 | to grid: back to grid 113 | to parent: back to parent 114 | 115 | # Confirmation messages 116 | confirmation: 117 | delete: Delete confirmation 118 | leave: Are you sure you want to leave this page? 119 | discard changes: You will lose changes to the %entity% if you leave the page. 120 | remove: 121 | item: Are you sure you want to delete this item? 122 | attribute: Are you sure you want to delete the attribute %name%? 123 | attribute group: Are you sure you want to delete the attribute group %name%? 124 | category: Are you sure you want to delete the category %name%? 125 | family: Are you sure you want to delete the family %name%? 126 | product: Are you sure you want to delete the product %name%? 127 | export profile: Are you sure you want to delete the export profile %name%? 128 | import profile: Are you sure you want to delete the import profile %name%? 129 | channel: Are you sure you want to delete the channel %name%? 130 | group: Are you sure you want to delete the group %name%? 131 | variant group: Are you sure you want to delete the variant group %name%? 132 | association: Are you sure you want to delete the association %name%? 133 | group type: Are you sure you want to delete the group type %name%? 134 | 135 | # Flash messages 136 | flash: 137 | entity: 138 | removed: Item was deleted 139 | removed: Item was deleted 140 | attribute: 141 | created: Attribute successfully created 142 | updated: Attribute successfully updated 143 | removed: Attribute successfully removed 144 | identifier not removable: Identifier attribute can not be removed 145 | used by groups: This attribute can not be removed because it is used by %count% groups 146 | option created: Option successfully created 147 | error creating option: Error ocurred when trying to create the option, please try again 148 | attribute group: 149 | created: Attribute group successfully created 150 | updated: Attribute group successfully updated 151 | removed: Attribute group successfully removed 152 | attributes added: Attributes successfully added to the attribute group 153 | attribute removed: Attribute successfully removed from the attribute group 154 | category: 155 | created: Category successfully created 156 | updated: Category successfully updated 157 | removed: Category successfully removed 158 | tree: 159 | created: Tree successfully created 160 | updated: Tree successfully updated 161 | removed: Tree successfully removed 162 | not removable: Trees connected to channels cannot be removed. 163 | channel: 164 | saved: Channel successfully saved 165 | removed: Channel successfully removed 166 | not removable: The last channel cannot be removed 167 | currency: 168 | updated: Currency successfully updated 169 | family: 170 | created: Family successfully created 171 | updated: Family successfully updated 172 | removed: Family successfully removed 173 | identifier not removable: Identifier attribute can not be removed from a family 174 | label attribute not removable: This attribute can not be removed because it is used as the label of the family 175 | attribute not found: The attribute does not belong to this family 176 | attributes added: Attributes successfully added to the family 177 | attribute removed: Attribute successfully removed from the family 178 | product: 179 | created: Product successfully created 180 | updated: Product successfully updated 181 | removed: Product successfully removed 182 | invalid: Please check your entry and try again 183 | attributes added: Attributes successfully added to the product 184 | attribute removed: Attribute successfully removed from the product 185 | attribute not removable: The attribute can not be removed from this product 186 | import: 187 | removed: The import has been removed 188 | export: 189 | removed: The export has been removed 190 | group: 191 | created: Group successfully created 192 | updated: Group successfully updated 193 | removed: Group successfully removed 194 | variant group: 195 | created: Variant group successfully created 196 | updated: Variant group successfully updated 197 | removed: Variant group successfully removed 198 | group type: 199 | created: Group type successfully created 200 | updated: Group type successfully updated 201 | removed: Group type successfully removed 202 | cant remove variant: Group type variant can't be removed 203 | cant remove used: Group type is used by some groups and can't be removed 204 | 205 | association: 206 | created: Association successfully created 207 | updated: Association successfully updated 208 | removed: Association successfully removed 209 | error ocurred: Action failed. Please retry. 210 | 211 | # Info messages 212 | info: 213 | updated: There are unsaved changes. 214 | category: 215 | remove children: All sub-categories will be deleted. 216 | keep products: Products in these categories will not be deleted. 217 | product: 218 | no available attributes: There are no more attributes to add 219 | change family: Change the product family 220 | change family to: Change the family to 221 | merge attributes: Current attributes will be merged with the ones in the new family. 222 | keep attributes: No attributes will be removed. 223 | number of associations: %productCount% products and %groupCount% groups 224 | enable: Enable 225 | enabled: Enabled 226 | disable: Disable 227 | disabled: Disabled 228 | group: 229 | axis: Variation axis: %attributes% 230 | select products: Please select products that should belong to this group. 231 | selectable products: The following selectable products have a defined value for each axis. 232 | association: 233 | show products: Show products 234 | show groups: Show groups 235 | remove from products: Deleting the association will remove it from %count% products. 236 | 237 | # Form elements 238 | Options: Options 239 | Created at: Created at 240 | Updated at: Updated at 241 | Created: Created 242 | Updated: Updated 243 | N/A: N/A 244 | Add an option: Add an option 245 | Manage products: Manage products 246 | Manage groups: Manage groups 247 | Code: Code 248 | Name: Name 249 | Type: Type 250 | Scope: Scope 251 | Required: Required 252 | Unique: Unique 253 | translatable: Translatable 254 | Scopable: Scopable 255 | Unique value: Unique value 256 | Attribute group: Attribute group 257 | Variants behavior when edited: Variants behavior when edited 258 | Edit: Edit 259 | Remove: Remove 260 | Delete: Delete 261 | Parameters: Parameters 262 | Values: Values 263 | General parameters: General parameters 264 | Backend parameters: Backend parameters 265 | Label: Label 266 | Default label: Default label 267 | Default value: Default value 268 | Attributetype: Attribute type 269 | searchable: Searchable 270 | Defaultvalue: Default value 271 | Variant: Variant 272 | Smart: Smart 273 | Useable as grid column: Usable as grid column 274 | Useable as grid filter: Usable as grid filter 275 | Family: Family 276 | Available locales: Available locales 277 | Locale specific: Locale specific 278 | Label translations: Label translations 279 | default: Default 280 | 281 | Create: Create 282 | Cancel: Cancel 283 | Create a new product: Create a new product 284 | Choose a family: Choose a family 285 | Create: Create 286 | Cancel: Cancel 287 | Create a new product: Create a new product 288 | Create a new association: Create a new association 289 | Choose a family: Choose a family 290 | Create a new option: Create a new option 291 | 292 | # Scopes 293 | Global: Global 294 | Channel: Channel 295 | 296 | # Variants 297 | Always override: Always override 298 | Ask: Ask 299 | 300 | # Attribute group 301 | General Properties: General Properties 302 | Group values: Group values 303 | New group: New group 304 | Groups overview: Groups overview 305 | 306 | 'Y-m-d h:i:s': 'Y-m-d h:i:s' 307 | 308 | # Attribute properties 309 | Max characters: Max characters 310 | Validation rule: Validation rule 311 | Validation regexp: Validation regexp 312 | Wysiwyg enabled: WYSIWYG enabled 313 | Number min: Min number 314 | Number max: Max number 315 | Decimals allowed: Allow decimals 316 | Negative allowed: Allow negative values 317 | Date type: Date type 318 | Date min: Min date 319 | Date max: Max date 320 | Default currency: Default currency 321 | Metric family: Metric family 322 | Default metric unit: Default metric unit 323 | Max file size: Max file size (MB) 324 | Allowed file extensions: Allowed file extensions 325 | 326 | # Family 327 | Attributes: Attributes 328 | 329 | # Category 330 | Title: Title 331 | 332 | # Page titles 333 | Dashboard: Dashboard 334 | Attribute groups: Attribute groups 335 | Attribute groups | Create: Attribute groups | Create 336 | Attribute groups %group.label% | Edit: Attribute groups %group.label% | Edit 337 | Attribute groups | Sort: Attribute groups | Sort 338 | Category trees: Category trees 339 | Category trees | List: Category trees | List 340 | Category trees | Manage: Category trees | Manage 341 | Category trees | View: Category trees | View 342 | Category trees | Create: Category trees | Create 343 | Category trees | Edit: Category trees | Edit 344 | Export profiles: Export profiles 345 | Export profiles | Create: Export profiles | Create 346 | Export profiles %export.label% | Edit: Export profiles %export.label% | Edit 347 | Import profiles: Import profiles 348 | Import profiles | Create: Import profiles | Create 349 | Import profiles %import.label% | Edit: Import profiles %import.label% | Edit 350 | Product attributes: Product attributes 351 | Product attributes | Create: Product attributes | Create 352 | Product attributes %attribute.label% | Edit: Product attributes %attribute.label% | Edit 353 | Products: Products 354 | Products | Create: Products | Create 355 | Products %product.sku% | Edit: Products %product.sku% | Edit 356 | Families: Families 357 | Families | Create: Families | Create 358 | Families %family.label% | Edit: Families %family.label% | Edit 359 | Channels: Channels 360 | Channels | Create: Channels | Create 361 | Channels %channel.label% | Edit: Channels %channel.label% | Edit 362 | Currencies: Currencies 363 | Locales: Locales 364 | Locales %locale.code% | Edit: Locales %locale.code% | Edit 365 | Groups: Groups 366 | Groups | Create: Groups | Create 367 | Groups %group.label% | Edit: Groups %group.label% | Edit 368 | Variant groups: Variant groups 369 | Variant groups | Create: Variant groups | Create 370 | Variant groups %group.label% | Edit: Variant groups %group.label% | Edit 371 | Associations: Associations 372 | Associations | Create: Associations | Create 373 | Associations %association.label% | Edit: Associations %association.label% | Edit 374 | Group types: Group types 375 | Group types | Create: Group types | Create 376 | Group types %grouptype.label% | Edit: Group types %grouptype.label% | Edit 377 | 378 | 379 | pim_catalog_identifier: Identifier 380 | pim_catalog_text: Text 381 | pim_catalog_textarea: Text Area 382 | pim_catalog_number: Number 383 | pim_catalog_price_collection: Price 384 | pim_catalog_multiselect: Multi select 385 | pim_catalog_simpleselect: Simple select 386 | pim_catalog_file: File 387 | pim_catalog_image: Image 388 | pim_catalog_boolean: Yes/No 389 | pim_catalog_date: Date 390 | pim_catalog_metric: Metric 391 | 392 | # Locales 393 | Fallback: Fallback 394 | Activated: Activated 395 | Edit: Edit 396 | Disable: Disable 397 | 398 | # Batch operations 399 | pim_catalog.mass_edit_action: 400 | page_title: Products 401 | page_subtitle: Mass Edit (1 product)|Mass Edit (%count% products) 402 | title: Choose operation 403 | subtitle: Choose the operation you wish to perform on the selected product|Choose the operation you wish to perform on the selected %count% products 404 | confirm: You are about to update a product with the following information, please confirm.|You are about to update %count% products with the following information, please confirm. 405 | 406 | change-status: 407 | label: Change status (enable / disable) 408 | description: The selected products will be enabled or disabled. 409 | success_flash: Product(s) status have been changed 410 | 411 | edit-common-attributes: 412 | label: Edit attributes 413 | description: The selected product's attributes will be edited with following data for the chosen locale. 414 | empty: Please select the attribute(s) you want to edit 415 | success_flash: Product(s) attribute(s) have been updated 416 | 417 | classify: 418 | label: Classify products in categories 419 | description: The products will be positioned into following categories, the existing placement is kept. 420 | success_flash: Product(s) have been classified into selected categories 421 | 422 | change-family: 423 | label: Change the family of products 424 | description: The family of the selected products will be changed to the chosen family 425 | success_flash: The family of selected product(s) has been successfully changed 426 | 427 | # Product completeness 428 | pim_catalog.completeness: 429 | progress_bar.title: '%ratio%% complete (1 required value)|%ratio%% complete (%count% required values)' 430 | subtitle: '{0} Complete|{1} 1 missing value|]1,Inf] %count% missing values' 431 | missing_attributes: Missing value|Missing values 432 | legend: 433 | locale_not_associated: Locale not associated to this channel 434 | 435 | # Product groups 436 | pim_catalog.group: 437 | axis.help: 'Axis are attributes which define variants of the variant group. Axis are attributes with options, not localizable and not scoped. It cannot be changed after the creation of the variant group.' 438 | type: 439 | VARIANT: Variant 440 | X_SELL: X-Sell 441 | ADDITIONAL: Additional 442 | RELATED: Related 443 | 444 | # Product view 445 | Group: Group 446 | View group: View group 447 | Products: Products 448 | -------------------------------------------------------------------------------- /src/Akeneo/Crowdin/Api/AbstractApi.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | abstract class AbstractApi implements ApiInterface 11 | { 12 | protected array $parameters = []; 13 | protected array $urlParameters = []; 14 | 15 | public function __construct(protected Client $client) 16 | { 17 | } 18 | 19 | public function setParameters(array $parameters): void 20 | { 21 | $this->parameters = $parameters; 22 | } 23 | 24 | public function addUrlParameter(string $key, string $value): static 25 | { 26 | $this->urlParameters[$key] = $value; 27 | 28 | return $this; 29 | } 30 | 31 | protected function getUrlQueryString(): string 32 | { 33 | return http_build_query($this->urlParameters); 34 | } 35 | 36 | abstract public function execute(); 37 | } 38 | -------------------------------------------------------------------------------- /src/Akeneo/Crowdin/Api/AddDirectory.php: -------------------------------------------------------------------------------- 1 | 11 | * @see https://crowdin.com/page/api/add-directory 12 | */ 13 | class AddDirectory extends AbstractApi 14 | { 15 | protected string $directory; 16 | protected bool $isBranch = false; 17 | protected ?string $branch = null; 18 | 19 | /** 20 | * {@inheritdoc} 21 | */ 22 | public function execute() 23 | { 24 | if (null == $this->directory) { 25 | throw new InvalidArgumentException('There is no directory to create.'); 26 | } 27 | 28 | $path = sprintf( 29 | "project/%s/add-directory", 30 | $this->client->getProjectIdentifier() 31 | ); 32 | 33 | $parameters = ['name' => $this->directory]; 34 | if ($this->isBranch) { 35 | $parameters['is_branch'] = '1'; 36 | } 37 | if (null !== $this->branch) { 38 | $parameters['branch'] = $this->branch; 39 | } 40 | 41 | $data = [ 42 | 'headers' => [ 43 | 'Authorization' => 'Bearer ' . $this->client->getProjectApiKey() 44 | ], 45 | 'body' => $parameters 46 | ]; 47 | $response = $this->client->getHttpClient()->request('POST', $path, $data); 48 | 49 | return $response->getContent(); 50 | } 51 | 52 | public function setDirectory(mixed $directory): void 53 | { 54 | $this->directory = $directory; 55 | } 56 | 57 | public function getDirectory(): string 58 | { 59 | return $this->directory; 60 | } 61 | 62 | public function setIsBranch(bool $isBranch): void 63 | { 64 | $this->isBranch = $isBranch; 65 | } 66 | 67 | public function getIsBranch(): bool 68 | { 69 | return $this->isBranch; 70 | } 71 | 72 | public function setBranch(string $branch): void 73 | { 74 | $this->branch = $branch; 75 | } 76 | 77 | public function getBranch(): ?string 78 | { 79 | return $this->branch; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Akeneo/Crowdin/Api/AddFile.php: -------------------------------------------------------------------------------- 1 | 14 | * @see https://crowdin.com/page/api/add-file 15 | */ 16 | class AddFile extends AbstractApi 17 | { 18 | /** @var Translation[] */ 19 | protected array $translations = []; 20 | protected ?string $type = null; 21 | protected ?string $branch = null; 22 | 23 | public function __construct(Client $client, protected FileReader $fileReader) 24 | { 25 | parent::__construct($client); 26 | } 27 | 28 | /** 29 | * {@inheritdoc} 30 | */ 31 | public function execute(): string 32 | { 33 | if (0 === count($this->translations)) { 34 | throw new InvalidArgumentException('There is no files to add.'); 35 | } 36 | 37 | $path = sprintf( 38 | "project/%s/add-file", 39 | $this->client->getProjectIdentifier() 40 | ); 41 | 42 | $data = $this->parameters; 43 | if (null !== $this->type) { 44 | $data['type'] = $this->type; 45 | } 46 | if (null !== $this->branch) { 47 | $data['branch'] = $this->branch; 48 | } 49 | foreach ($this->translations as $translation) { 50 | $data[sprintf('files[%s]', $translation->getCrowdinPath())] = $this->fileReader->readTranslation($translation); 51 | if ($translation->getTitle()) { 52 | $data[sprintf('titles[%s]', $translation->getCrowdinPath())] = $translation->getTitle(); 53 | } 54 | if ($translation->getExportPattern()) { 55 | $data[sprintf('export_patterns[%s]', $translation->getCrowdinPath())] = $translation->getExportPattern(); 56 | } 57 | } 58 | 59 | $data = [ 60 | 'headers' => [ 61 | 'Content-Type' => 'multipart/form-data', 62 | 'Authorization' => 'Bearer ' . $this->client->getProjectApiKey(), 63 | ], 64 | 'body' => $data 65 | ]; 66 | $response = $this->client->getHttpClient()->request('POST', $path, $data); 67 | 68 | return $response->getContent(); 69 | } 70 | 71 | public function addTranslation( 72 | string $localPath, 73 | string $crowdinPath, 74 | ?string $exportPattern = null, 75 | ?string $title = null 76 | ): static { 77 | $translation = new Translation($localPath, $crowdinPath); 78 | $translation->setExportPattern($exportPattern); 79 | $translation->setTitle($title); 80 | 81 | $this->translations[] = $translation; 82 | 83 | return $this; 84 | } 85 | 86 | /** 87 | * @return Translation[] 88 | */ 89 | public function getTranslations(): array 90 | { 91 | return $this->translations; 92 | } 93 | 94 | public function getType(): ?string 95 | { 96 | return $this->type; 97 | } 98 | 99 | public function setType(string $type): static 100 | { 101 | $this->type = $type; 102 | 103 | return $this; 104 | } 105 | 106 | public function getBranch(): ?string 107 | { 108 | return $this->branch; 109 | } 110 | 111 | public function setBranch(string $branch): static 112 | { 113 | $this->branch = $branch; 114 | 115 | return $this; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Akeneo/Crowdin/Api/ApiInterface.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | interface ApiInterface 13 | { 14 | /** 15 | * Set the parameters for the api call 16 | */ 17 | public function setParameters(array $parameters): void; 18 | 19 | /** 20 | * Call the api method with provided parameters 21 | * 22 | * @throws InvalidArgumentException 23 | */ 24 | public function execute(); 25 | } 26 | -------------------------------------------------------------------------------- /src/Akeneo/Crowdin/Api/ChangeDirectory.php: -------------------------------------------------------------------------------- 1 | 12 | * @see https://crowdin.com/page/api/change-directory 13 | */ 14 | class ChangeDirectory extends AbstractApi 15 | { 16 | protected string $name; 17 | protected ?string $newName = null; 18 | protected ?string $title = null; 19 | protected ?string $exportPattern = null; 20 | protected ?string $branch = null; 21 | 22 | /** 23 | * {@inheritdoc} 24 | */ 25 | public function execute(): string 26 | { 27 | if (null == $this->name) { 28 | throw new InvalidArgumentException('Argument name is required.'); 29 | } 30 | 31 | $path = sprintf( 32 | "project/%s/change-directory", 33 | $this->client->getProjectIdentifier() 34 | ); 35 | 36 | $data = ['name' => $this->name]; 37 | 38 | if (null !== $this->newName) { 39 | $data['new_name'] = $this->newName; 40 | } 41 | if (null !== $this->title) { 42 | $data['title'] = $this->title; 43 | } 44 | if (null !== $this->exportPattern) { 45 | $data['export_pattern'] = $this->exportPattern; 46 | } 47 | if (null !== $this->branch) { 48 | $data['branch'] = $this->branch; 49 | } 50 | 51 | $data = [ 52 | 'headers' => ['Authorization' => 'Bearer ' . $this->client->getProjectApiKey()], 53 | 'body' => $data 54 | ]; 55 | $response = $this->client->getHttpClient()->request('POST', $path, $data); 56 | 57 | return $response->getContent(); 58 | } 59 | 60 | public function setBranch(string $branch): static 61 | { 62 | $this->branch = $branch; 63 | 64 | return $this; 65 | } 66 | 67 | /** 68 | * Set new directory export pattern. Is used to create directory name and path in resulted translations bundle. 69 | */ 70 | public function setExportPattern(string $exportPattern): static 71 | { 72 | $this->exportPattern = $exportPattern; 73 | 74 | return $this; 75 | } 76 | 77 | /** 78 | * Set new directory title to be displayed in Crowdin UI. 79 | */ 80 | public function setTitle(string $title): static 81 | { 82 | $this->title = $title; 83 | 84 | return $this; 85 | } 86 | 87 | /** 88 | * Set new directory name. 89 | */ 90 | public function setNewName(string $newName): static 91 | { 92 | $this->newName = $newName; 93 | 94 | return $this; 95 | } 96 | 97 | /** 98 | * Set full directory path that should be modified (e.g. /MainPage/AboutUs). 99 | */ 100 | public function setName(string $name): static 101 | { 102 | $this->name = $name; 103 | 104 | return $this; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Akeneo/Crowdin/Api/DeleteDirectory.php: -------------------------------------------------------------------------------- 1 | 11 | * @see https://crowdin.com/page/api/delete-directory 12 | */ 13 | class DeleteDirectory extends AbstractApi 14 | { 15 | protected string $directory; 16 | 17 | /** 18 | * {@inheritdoc} 19 | */ 20 | public function execute(): string 21 | { 22 | if (null == $this->directory) { 23 | throw new InvalidArgumentException('There is no directory to delete.'); 24 | } 25 | 26 | $path = sprintf( 27 | "project/%s/delete-directory", 28 | $this->client->getProjectIdentifier() 29 | ); 30 | 31 | $parameters = ['name' => $this->directory]; 32 | 33 | $data = [ 34 | 'headers' => ['Authorization' => 'Bearer ' . $this->client->getProjectApiKey()], 35 | 'body' => $parameters 36 | ]; 37 | $response = $this->client->getHttpClient()->request('POST', $path, $data); 38 | 39 | return $response->getContent(); 40 | } 41 | 42 | public function setDirectory(string $directory): static 43 | { 44 | $this->directory = $directory; 45 | 46 | return $this; 47 | } 48 | 49 | public function getDirectory(): string 50 | { 51 | return $this->directory; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Akeneo/Crowdin/Api/DeleteFile.php: -------------------------------------------------------------------------------- 1 | 11 | * @see https://crowdin.com/page/api/delete-file 12 | */ 13 | class DeleteFile extends AbstractApi 14 | { 15 | protected string $file; 16 | 17 | /** 18 | * {@inheritdoc} 19 | */ 20 | public function execute(): string 21 | { 22 | if (null == $this->file) { 23 | throw new InvalidArgumentException('There is no file to delete.'); 24 | } 25 | 26 | $path = sprintf( 27 | "project/%s/delete-file", 28 | $this->client->getProjectIdentifier() 29 | ); 30 | 31 | $parameters = ['file' => $this->file]; 32 | 33 | $data = [ 34 | 'headers' => ['Authorization' => 'Bearer ' . $this->client->getProjectApiKey()], 35 | 'body' => $parameters 36 | ]; 37 | $response = $this->client->getHttpClient()->request('POST', $path, $data); 38 | 39 | return $response->getContent(); 40 | } 41 | 42 | public function setFile(string $file): static 43 | { 44 | $this->file = $file; 45 | 46 | return $this; 47 | } 48 | 49 | public function getFile(): string 50 | { 51 | return $this->file; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Akeneo/Crowdin/Api/Download.php: -------------------------------------------------------------------------------- 1 | 9 | * @see https://crowdin.com/page/api/download 10 | */ 11 | class Download extends AbstractApi 12 | { 13 | protected string $package = 'all.zip'; 14 | protected string $copyDestination = '/tmp'; 15 | protected ?string $branch = null; 16 | 17 | /** 18 | * {@inheritdoc} 19 | */ 20 | public function execute(): string 21 | { 22 | $path = sprintf( 23 | "project/%s/download/%s", 24 | $this->client->getProjectIdentifier(), 25 | $this->package 26 | ); 27 | if (null !== $this->branch) { 28 | $path = sprintf('%s?branch=%s', $path, $this->branch); 29 | } 30 | 31 | $filePath = $this->getCopyDestination() . '/' . $this->getPackage(); 32 | $response = $this->client->getHttpClient()->request('GET', $path, ['headers' => ['Authorization' => 'Bearer '.$this->client->getProjectApiKey()]]); 33 | 34 | $fileContent = $response->getContent(); 35 | file_put_contents($filePath, $fileContent); 36 | 37 | return $fileContent; 38 | } 39 | 40 | public function setPackage(string $package): static 41 | { 42 | $this->package = $package; 43 | 44 | return $this; 45 | } 46 | 47 | public function getPackage(): string 48 | { 49 | return $this->package; 50 | } 51 | 52 | public function setCopyDestination(string $dest): static 53 | { 54 | $this->copyDestination = $dest; 55 | 56 | return $this; 57 | } 58 | 59 | public function getCopyDestination(): string 60 | { 61 | return $this->copyDestination; 62 | } 63 | 64 | public function getBranch(): ?string 65 | { 66 | return $this->branch; 67 | } 68 | 69 | public function setBranch(string $branch): static 70 | { 71 | $this->branch = $branch; 72 | 73 | return $this; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Akeneo/Crowdin/Api/Export.php: -------------------------------------------------------------------------------- 1 | 9 | * @see https://crowdin.com/page/api/export 10 | */ 11 | class Export extends AbstractApi 12 | { 13 | protected ?string $branch = null; 14 | 15 | /** 16 | * {@inheritdoc} 17 | */ 18 | public function execute(): string 19 | { 20 | $path = sprintf( 21 | "project/%s/export", 22 | $this->client->getProjectIdentifier() 23 | ); 24 | if (null !== $this->branch) { 25 | $path = sprintf('%s?branch=%s', $path, $this->branch); 26 | } 27 | $response = $this->client->getHttpClient()->request( 28 | 'GET', 29 | $path, 30 | ['headers' => ['Authorization' => 'Bearer ' . $this->client->getProjectApiKey()]] 31 | ); 32 | 33 | return $response->getContent(); 34 | } 35 | 36 | public function getBranch(): ?string 37 | { 38 | return $this->branch; 39 | } 40 | 41 | public function setBranch(string $branch): static 42 | { 43 | $this->branch = $branch; 44 | 45 | return $this; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Akeneo/Crowdin/Api/Info.php: -------------------------------------------------------------------------------- 1 | 9 | * @see https://crowdin.com/page/api/info 10 | */ 11 | class Info extends AbstractApi 12 | { 13 | /** 14 | * {@inheritdoc} 15 | */ 16 | public function execute(): string 17 | { 18 | $path = sprintf( 19 | "project/%s/info", 20 | $this->client->getProjectIdentifier() 21 | ); 22 | $response = $this->client->getHttpClient()->request( 23 | 'GET', 24 | $path, 25 | ['headers' => ['Authorization' => 'Bearer ' . $this->client->getProjectApiKey()]] 26 | ); 27 | 28 | return $response->getContent(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Akeneo/Crowdin/Api/LanguageStatus.php: -------------------------------------------------------------------------------- 1 | 9 | * @see http://crowdin.net/page/api/language-status 10 | */ 11 | class LanguageStatus extends AbstractApi 12 | { 13 | protected string $language; 14 | 15 | public function setLanguage(string $language): static 16 | { 17 | $this->language = $language; 18 | 19 | return $this; 20 | } 21 | 22 | public function getLanguage(): string 23 | { 24 | return $this->language; 25 | } 26 | 27 | public function execute() 28 | { 29 | $path = sprintf( 30 | "project/%s/language-status", 31 | $this->client->getProjectIdentifier() 32 | ); 33 | $parameters = array_merge( 34 | $this->parameters, 35 | [ 36 | 'headers' => ['Authorization' => 'Bearer ' . $this->client->getProjectApiKey()], 37 | 'body' => ['language' => $this->getLanguage()] 38 | ] 39 | ); 40 | $response = $this->client->getHttpClient()->request('POST', $path, $parameters); 41 | 42 | return $response->getContent(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Akeneo/Crowdin/Api/Status.php: -------------------------------------------------------------------------------- 1 | 9 | * @see https://crowdin.com/page/api/status 10 | */ 11 | class Status extends AbstractApi 12 | { 13 | /** 14 | * {@inheritdoc} 15 | */ 16 | public function execute() 17 | { 18 | $path = sprintf( 19 | "project/%s/status", 20 | $this->client->getProjectIdentifier() 21 | ); 22 | $response = $this->client->getHttpClient()->request( 23 | 'GET', 24 | $path, 25 | ['headers' => ['Authorization' => 'Bearer ' . $this->client->getProjectApiKey()]] 26 | ); 27 | 28 | return $response->getContent(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Akeneo/Crowdin/Api/SupportedLanguages.php: -------------------------------------------------------------------------------- 1 | 9 | * @see https://crowdin.com/page/api/supported-languages 10 | */ 11 | class SupportedLanguages extends AbstractApi 12 | { 13 | /** 14 | * {@inheritdoc} 15 | */ 16 | public function execute() 17 | { 18 | $path = 'supported-languages'; 19 | 20 | if (!empty($this->urlParameters)) { 21 | $path .= sprintf('?%s', $this->getUrlQueryString()); 22 | } 23 | 24 | $http = $this->client->getHttpClient(); 25 | $response = $http->request( 26 | 'GET', 27 | $path, 28 | ['headers' => ['Authorization' => 'Bearer ' . $this->client->getProjectApiKey()]] 29 | ); 30 | 31 | return $response->getContent(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Akeneo/Crowdin/Api/UpdateFile.php: -------------------------------------------------------------------------------- 1 | 14 | * @see https://crowdin.com/page/api/update-file 15 | */ 16 | class UpdateFile extends AbstractApi 17 | { 18 | protected FileReader $fileReader; 19 | 20 | /** @var Translation[] */ 21 | protected array $translations = []; 22 | protected ?string $branch = null; 23 | 24 | public function __construct(Client $client, FileReader $fileReader) 25 | { 26 | parent::__construct($client); 27 | $this->fileReader = $fileReader; 28 | } 29 | 30 | public function execute(): string 31 | { 32 | if (count($this->translations) === 0) { 33 | throw new InvalidArgumentException('There is no files to update'); 34 | } 35 | 36 | $path = sprintf( 37 | "project/%s/update-file", 38 | $this->client->getProjectIdentifier() 39 | ); 40 | 41 | $data = $this->parameters; 42 | if (null !== $this->branch) { 43 | $data['branch'] = $this->branch; 44 | } 45 | 46 | foreach ($this->translations as $translation) { 47 | $data[sprintf('files[%s]', $translation->getCrowdinPath())] = $this->fileReader->readTranslation($translation); 48 | if ($translation->getTitle()) { 49 | $data[sprintf('titles[%s]', $translation->getCrowdinPath())] = $translation->getTitle(); 50 | } 51 | if ($translation->getExportPattern()) { 52 | $data[sprintf('export_patterns[%s]', $translation->getCrowdinPath())] = $translation->getExportPattern(); 53 | } 54 | } 55 | 56 | $data = [ 57 | 'headers' => [ 58 | 'Authorization' => 'Bearer ' . $this->client->getProjectApiKey(), 59 | 'Content-Type' => 'multipart/form-data' 60 | ], 61 | 'body' => $data 62 | ]; 63 | $response = $this->client->getHttpClient()->request('POST', $path, $data); 64 | 65 | return $response->getContent(); 66 | } 67 | 68 | public function addTranslation( 69 | string $localPath, 70 | string $crowdinPath, 71 | string $exportPattern = null, 72 | string $title = null 73 | ): static { 74 | $translation = new Translation($localPath, $crowdinPath); 75 | $translation->setExportPattern($exportPattern); 76 | $translation->setTitle($title); 77 | 78 | $this->translations[] = $translation; 79 | 80 | return $this; 81 | } 82 | 83 | /** 84 | * @return Translation[] 85 | */ 86 | public function getTranslations(): array 87 | { 88 | return $this->translations; 89 | } 90 | 91 | public function getBranch(): ?string 92 | { 93 | return $this->branch; 94 | } 95 | 96 | public function setBranch(string $branch): void 97 | { 98 | $this->branch = $branch; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Akeneo/Crowdin/Api/UploadTranslation.php: -------------------------------------------------------------------------------- 1 | 14 | * @see https://crowdin.com/page/api/upload-translation 15 | */ 16 | class UploadTranslation extends AbstractApi 17 | { 18 | protected FileReader $fileReader; 19 | 20 | /** @var Translation[] */ 21 | protected array $translations = []; 22 | 23 | protected ?string $locale = null; 24 | 25 | protected bool $areDuplicatesImported = false; 26 | protected bool $areEqualSuggestionsImported = false; 27 | protected bool $areImportsAutoApproved = false; 28 | protected ?string $branch = null; 29 | 30 | public function __construct(Client $client, FileReader $fileReader) 31 | { 32 | parent::__construct($client); 33 | $this->fileReader = $fileReader; 34 | } 35 | 36 | /** 37 | * {@inheritdoc} 38 | */ 39 | public function execute(): string 40 | { 41 | if (0 === count($this->translations)) { 42 | throw new InvalidArgumentException('There are no translations to upload.'); 43 | } 44 | 45 | if (null === $this->locale) { 46 | throw new InvalidArgumentException('Locale is not set.'); 47 | } 48 | 49 | $path = sprintf( 50 | "project/%s/upload-translation", 51 | $this->client->getProjectIdentifier() 52 | ); 53 | 54 | $data['import_duplicates'] = (int)$this->areDuplicatesImported; 55 | $data['import_eq_suggestions'] = (int)$this->areEqualSuggestionsImported; 56 | $data['auto_approve_imported'] = (int)$this->areImportsAutoApproved; 57 | $data['language'] = $this->locale; 58 | 59 | if (null !== $this->branch) { 60 | $data['branch'] = $this->branch; 61 | } 62 | 63 | foreach ($this->translations as $translation) { 64 | $data[sprintf('files[%s]', $translation->getCrowdinPath())] = $this->fileReader->readTranslation($translation); 65 | } 66 | 67 | $data = [ 68 | 'headers' => [ 69 | 'Authorization' => 'Bearer ' . $this->client->getProjectApiKey(), 70 | 'Content-Type' => 'multipart/form-data' 71 | ], 72 | 'body' => $data 73 | ]; 74 | $response = $this->client->getHttpClient()->request('POST', $path, $data); 75 | 76 | return $response->getContent(); 77 | } 78 | 79 | public function addTranslation( 80 | string $localPath, 81 | string $crowdinPath, 82 | string $exportPattern = null, 83 | string $title = null 84 | ): static { 85 | $translation = new Translation($localPath, $crowdinPath); 86 | $translation->setExportPattern($exportPattern); 87 | $translation->setTitle($title); 88 | 89 | $this->translations[] = $translation; 90 | 91 | return $this; 92 | } 93 | 94 | /** 95 | * @param Translation[] $translations 96 | */ 97 | public function setTranslations(array $translations): static 98 | { 99 | $this->translations = $translations; 100 | 101 | return $this; 102 | } 103 | 104 | /** 105 | * @return Translation[] 106 | */ 107 | public function getTranslations(): array 108 | { 109 | return $this->translations; 110 | } 111 | 112 | /** 113 | * @throws InvalidArgumentException 114 | * 115 | */ 116 | public function setImportsAutoApproved(bool $importsAutoApproved): static 117 | { 118 | $this->areImportsAutoApproved = $importsAutoApproved; 119 | 120 | return $this; 121 | } 122 | 123 | public function areImportsAutoApproved(): bool 124 | { 125 | return $this->areImportsAutoApproved; 126 | } 127 | 128 | /** 129 | * @throws InvalidArgumentException 130 | * 131 | */ 132 | public function setDuplicatesImported(bool $duplicatesImported): static 133 | { 134 | $this->areDuplicatesImported = $duplicatesImported; 135 | 136 | return $this; 137 | } 138 | 139 | public function areDuplicatesImported(): bool 140 | { 141 | return $this->areDuplicatesImported; 142 | } 143 | 144 | /** 145 | * @throws InvalidArgumentException 146 | * 147 | */ 148 | public function setEqualSuggestionsImported(bool $equalSuggestionsImported): static 149 | { 150 | $this->areEqualSuggestionsImported = $equalSuggestionsImported; 151 | 152 | return $this; 153 | } 154 | 155 | public function areEqualSuggestionsImported(): bool 156 | { 157 | return $this->areEqualSuggestionsImported; 158 | } 159 | 160 | public function setLocale(string $locale): static 161 | { 162 | $this->locale = $locale; 163 | 164 | return $this; 165 | } 166 | 167 | public function getLocale(): ?string 168 | { 169 | return $this->locale; 170 | } 171 | 172 | public function getBranch(): ?string 173 | { 174 | return $this->branch; 175 | } 176 | 177 | public function setBranch(string $branch): static 178 | { 179 | $this->branch = $branch; 180 | 181 | return $this; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/Akeneo/Crowdin/Client.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class Client 15 | { 16 | /** @var string base url */ 17 | const BASE_URL = 'https://api.crowdin.com/api/'; 18 | 19 | protected ?HttpClientInterface $httpClient = null; 20 | 21 | public function __construct(protected string $projectIdentifier, protected string $projectApiKey) 22 | { 23 | } 24 | 25 | /** 26 | * @throws InvalidArgumentException 27 | */ 28 | public function api(string $method): object 29 | { 30 | $fileReader = new FileReader(); 31 | 32 | return match ($method) { 33 | 'info' => new Api\Info($this), 34 | 'supported-languages' => new Api\SupportedLanguages($this), 35 | 'status' => new Api\Status($this), 36 | 'download' => new Api\Download($this), 37 | 'add-file' => new Api\AddFile($this, $fileReader), 38 | 'update-file' => new Api\UpdateFile($this, $fileReader), 39 | 'delete-file' => new Api\DeleteFile($this), 40 | 'export' => new Api\Export($this), 41 | 'add-directory' => new Api\AddDirectory($this), 42 | 'delete-directory' => new Api\DeleteDirectory($this), 43 | 'upload-translation' => new Api\UploadTranslation($this, $fileReader), 44 | 'language-status' => new Api\LanguageStatus($this), 45 | default => throw new InvalidArgumentException(sprintf('Undefined api method "%s"', $method)), 46 | }; 47 | } 48 | 49 | public function getProjectIdentifier(): string 50 | { 51 | return $this->projectIdentifier; 52 | } 53 | 54 | public function getProjectApiKey(): string 55 | { 56 | return $this->projectApiKey; 57 | } 58 | 59 | public function getHttpClient(): HttpClientInterface 60 | { 61 | if (null === $this->httpClient) { 62 | $this->httpClient = HttpClient::createForBaseUri(self::BASE_URL); 63 | } 64 | 65 | return $this->httpClient; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Akeneo/Crowdin/FileNotFoundException.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class FileNotFoundException extends \DomainException 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/Akeneo/Crowdin/FileReader.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class FileReader 11 | { 12 | /** 13 | * @param Translation $translation 14 | * 15 | * @throw FileNotFoundException 16 | * 17 | * @return resource 18 | */ 19 | public function readTranslation(Translation $translation) 20 | { 21 | return $this->readStream($translation->getLocalPath()); 22 | } 23 | 24 | /** 25 | * @throw FileNotFoundException 26 | * 27 | * @return resource 28 | */ 29 | protected function readStream($filename) 30 | { 31 | if (!file_exists($filename)) { 32 | throw new FileNotFoundException(sprintf('The file "%s" does not exists.', $filename)); 33 | } 34 | 35 | $stream = fopen($filename, 'r'); 36 | if (!$stream) { 37 | throw new FileNotFoundException(sprintf('The file "%s" can\'t be opened.', $filename)); 38 | } 39 | 40 | return $stream; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Akeneo/Crowdin/Translation.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class Translation 13 | { 14 | protected string $localPath; 15 | protected string $crowdinPath; 16 | protected ?string $title = null; 17 | protected ?string $exportPattern = null; 18 | 19 | public function __construct(string $localPath, string $crowdinPath) 20 | { 21 | $this->setLocalPath($localPath); 22 | $this->setCrowdinPath($crowdinPath); 23 | } 24 | 25 | public function setCrowdinPath(string $crowdinPath): void 26 | { 27 | $this->crowdinPath = $crowdinPath; 28 | } 29 | 30 | public function getCrowdinPath(): string 31 | { 32 | return $this->crowdinPath; 33 | } 34 | 35 | public function setExportPattern(?string $exportPattern): void 36 | { 37 | $this->exportPattern = $exportPattern; 38 | } 39 | 40 | public function getExportPattern(): ?string 41 | { 42 | return $this->exportPattern; 43 | } 44 | 45 | /** 46 | * @throws InvalidArgumentException 47 | */ 48 | public function setLocalPath(string $localPath): void 49 | { 50 | if (!file_exists($localPath)) { 51 | throw new InvalidArgumentException(sprintf('File %s does not exist', $localPath)); 52 | } 53 | 54 | $this->localPath = $localPath; 55 | } 56 | 57 | public function getLocalPath(): string 58 | { 59 | return $this->localPath; 60 | } 61 | 62 | public function setTitle(?string $title): void 63 | { 64 | $this->title = $title; 65 | } 66 | 67 | public function getTitle(): ?string 68 | { 69 | return $this->title; 70 | } 71 | } 72 | --------------------------------------------------------------------------------