├── .editorconfig ├── .gitignore ├── LICENSE.md ├── README.md ├── composer.json ├── composer.lock ├── edit_link.html ├── shaarli2mastodon.css ├── shaarli2mastodon.js ├── shaarli2mastodon.meta ├── shaarli2mastodon.php ├── src ├── HttpRequest.php ├── MastodonClient.php ├── Toot.php └── Utils.php └── tests ├── TootTest.php └── UtilsTest.php /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_size = 2 8 | indent_style = space 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | *.sublime-* 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 kalvn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shaarli2Mastodon 2 | 3 | This plugin allows you to automatically publish links you post on your Mastodon timeline. 4 | 5 | It is largely inspired by [ArthurHoaro's shaarli2twitter](https://github.com/ArthurHoaro/shaarli2twitter) and uses an adapted version of [TootoPHP](https://framagit.org/MaxKoder/TootoPHP). 6 | 7 | ## Requirements 8 | 9 | - PHP 5.3 10 | - PHP cURL extension 11 | - Shaarli >= v0.8.1 in public mode (which is the default mode) 12 | 13 | 14 | ## Installation 15 | ### 1. Create the application in Mastodon 16 | On your Mastodon instance, go to *Preferences > Development > Your applications* and create a new one. 17 | 18 | You can use whatever you want as *name* and *website* but if you have no idea, I suggest *shaarli2mastodon* and *https://github.com/kalvn/shaarli2mastodon*. 19 | 20 | In *Scopes*, chose only *write* permission and validate. 21 | 22 | Your new application should appear in the list. Click on it and copy the app token (the third entry) to your clipboard (CTRL+C). 23 | 24 | ### 2. Install the plugin 25 | You must download and copy the files under `/plugins/shaarli2mastodon` directory of your Shaarli installation. There are several ways to do so. Here, I'll be using Git. 26 | 27 | Run the following command from within the `/plugins` directory: 28 | 29 | ```bash 30 | $ git clone https://github.com/kalvn/shaarli2mastodon 31 | ``` 32 | 33 | Make sure these new files are readable by your web server (Apache, Nginx, etc.). 34 | 35 | Then, on your Shaarli instance, go to *Plugin administration* page and activate the plugin. 36 | 37 | ### 3. Configure the plugin 38 | Your parameters from step 1 will be used here. After plugin activation, you'll see 5 parameters. 39 | 40 | - **MASTODON_INSTANCE**: Your Mastodon instance, example: *mastodon.xyz* 41 | - **MASTODON_APPTOKEN**: Mastodon application token, example: *rODeyYKXVXDq91ecGwTG6BI0yU5mLTSiPjFMv6uJ50I* 42 | - **MASTODON_TOOT_MAX_LENGTH**: Defines the toots max length. By default it is 500 since it's the max length on most Mastodon instances. 43 | - **MASTODON_TOOT_FORMAT**: The format of your toots. Available placeholders: 44 | + *${url}*: URL of link shared 45 | + *${permalink}*: permalink of the share 46 | + *${title}*: title of the share 47 | + *${description}*: description of the share 48 | + *${tags}*: tags of the share, prefixed with # to be valid Mastodon tags 49 | + *${cw}*: content warning. Everything which is before this placeholder will appear before the content warning (visible). Everything which is after this placeholder will appear after the content warning (hidden - you must unfold to see it). 50 | 51 | 52 | ## Tests 53 | 54 | ```bash 55 | composer install 56 | composer test 57 | # or 58 | ./vendor/bin/phpunit tests 59 | ``` 60 | 61 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kalvn/shaarli2mastodon", 3 | "description": "Automatically toot your links from Shaarli to Mastodon.", 4 | "type": "project", 5 | "license": "MIT", 6 | "homepage": "https://github.com/kalvn/shaarli2mastodon", 7 | "support": { 8 | "issues": "https://github.com/kalvn/shaarli2mastodon/issues" 9 | }, 10 | "require": { 11 | "php": ">=5.3" 12 | }, 13 | "require-dev": { 14 | "phpunit/phpunit": "^9" 15 | }, 16 | "autoload": { 17 | "classmap": [ 18 | "src/" 19 | ] 20 | }, 21 | "scripts": { 22 | "test": "phpunit tests" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /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": "c1c23621f6758c4ba571f8b7a43d73d4", 8 | "packages": [], 9 | "packages-dev": [ 10 | { 11 | "name": "doctrine/instantiator", 12 | "version": "1.4.0", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/doctrine/instantiator.git", 16 | "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", 21 | "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "php": "^7.1 || ^8.0" 26 | }, 27 | "require-dev": { 28 | "doctrine/coding-standard": "^8.0", 29 | "ext-pdo": "*", 30 | "ext-phar": "*", 31 | "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", 32 | "phpstan/phpstan": "^0.12", 33 | "phpstan/phpstan-phpunit": "^0.12", 34 | "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" 35 | }, 36 | "type": "library", 37 | "autoload": { 38 | "psr-4": { 39 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 40 | } 41 | }, 42 | "notification-url": "https://packagist.org/downloads/", 43 | "license": [ 44 | "MIT" 45 | ], 46 | "authors": [ 47 | { 48 | "name": "Marco Pivetta", 49 | "email": "ocramius@gmail.com", 50 | "homepage": "https://ocramius.github.io/" 51 | } 52 | ], 53 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 54 | "homepage": "https://www.doctrine-project.org/projects/instantiator.html", 55 | "keywords": [ 56 | "constructor", 57 | "instantiate" 58 | ], 59 | "support": { 60 | "issues": "https://github.com/doctrine/instantiator/issues", 61 | "source": "https://github.com/doctrine/instantiator/tree/1.4.0" 62 | }, 63 | "funding": [ 64 | { 65 | "url": "https://www.doctrine-project.org/sponsorship.html", 66 | "type": "custom" 67 | }, 68 | { 69 | "url": "https://www.patreon.com/phpdoctrine", 70 | "type": "patreon" 71 | }, 72 | { 73 | "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", 74 | "type": "tidelift" 75 | } 76 | ], 77 | "time": "2020-11-10T18:47:58+00:00" 78 | }, 79 | { 80 | "name": "myclabs/deep-copy", 81 | "version": "1.10.2", 82 | "source": { 83 | "type": "git", 84 | "url": "https://github.com/myclabs/DeepCopy.git", 85 | "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220" 86 | }, 87 | "dist": { 88 | "type": "zip", 89 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220", 90 | "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220", 91 | "shasum": "" 92 | }, 93 | "require": { 94 | "php": "^7.1 || ^8.0" 95 | }, 96 | "replace": { 97 | "myclabs/deep-copy": "self.version" 98 | }, 99 | "require-dev": { 100 | "doctrine/collections": "^1.0", 101 | "doctrine/common": "^2.6", 102 | "phpunit/phpunit": "^7.1" 103 | }, 104 | "type": "library", 105 | "autoload": { 106 | "psr-4": { 107 | "DeepCopy\\": "src/DeepCopy/" 108 | }, 109 | "files": [ 110 | "src/DeepCopy/deep_copy.php" 111 | ] 112 | }, 113 | "notification-url": "https://packagist.org/downloads/", 114 | "license": [ 115 | "MIT" 116 | ], 117 | "description": "Create deep copies (clones) of your objects", 118 | "keywords": [ 119 | "clone", 120 | "copy", 121 | "duplicate", 122 | "object", 123 | "object graph" 124 | ], 125 | "support": { 126 | "issues": "https://github.com/myclabs/DeepCopy/issues", 127 | "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2" 128 | }, 129 | "funding": [ 130 | { 131 | "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", 132 | "type": "tidelift" 133 | } 134 | ], 135 | "time": "2020-11-13T09:40:50+00:00" 136 | }, 137 | { 138 | "name": "nikic/php-parser", 139 | "version": "v4.10.4", 140 | "source": { 141 | "type": "git", 142 | "url": "https://github.com/nikic/PHP-Parser.git", 143 | "reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e" 144 | }, 145 | "dist": { 146 | "type": "zip", 147 | "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/c6d052fc58cb876152f89f532b95a8d7907e7f0e", 148 | "reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e", 149 | "shasum": "" 150 | }, 151 | "require": { 152 | "ext-tokenizer": "*", 153 | "php": ">=7.0" 154 | }, 155 | "require-dev": { 156 | "ircmaxell/php-yacc": "^0.0.7", 157 | "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" 158 | }, 159 | "bin": [ 160 | "bin/php-parse" 161 | ], 162 | "type": "library", 163 | "extra": { 164 | "branch-alias": { 165 | "dev-master": "4.9-dev" 166 | } 167 | }, 168 | "autoload": { 169 | "psr-4": { 170 | "PhpParser\\": "lib/PhpParser" 171 | } 172 | }, 173 | "notification-url": "https://packagist.org/downloads/", 174 | "license": [ 175 | "BSD-3-Clause" 176 | ], 177 | "authors": [ 178 | { 179 | "name": "Nikita Popov" 180 | } 181 | ], 182 | "description": "A PHP parser written in PHP", 183 | "keywords": [ 184 | "parser", 185 | "php" 186 | ], 187 | "support": { 188 | "issues": "https://github.com/nikic/PHP-Parser/issues", 189 | "source": "https://github.com/nikic/PHP-Parser/tree/v4.10.4" 190 | }, 191 | "time": "2020-12-20T10:01:03+00:00" 192 | }, 193 | { 194 | "name": "phar-io/manifest", 195 | "version": "2.0.1", 196 | "source": { 197 | "type": "git", 198 | "url": "https://github.com/phar-io/manifest.git", 199 | "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133" 200 | }, 201 | "dist": { 202 | "type": "zip", 203 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", 204 | "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", 205 | "shasum": "" 206 | }, 207 | "require": { 208 | "ext-dom": "*", 209 | "ext-phar": "*", 210 | "ext-xmlwriter": "*", 211 | "phar-io/version": "^3.0.1", 212 | "php": "^7.2 || ^8.0" 213 | }, 214 | "type": "library", 215 | "extra": { 216 | "branch-alias": { 217 | "dev-master": "2.0.x-dev" 218 | } 219 | }, 220 | "autoload": { 221 | "classmap": [ 222 | "src/" 223 | ] 224 | }, 225 | "notification-url": "https://packagist.org/downloads/", 226 | "license": [ 227 | "BSD-3-Clause" 228 | ], 229 | "authors": [ 230 | { 231 | "name": "Arne Blankerts", 232 | "email": "arne@blankerts.de", 233 | "role": "Developer" 234 | }, 235 | { 236 | "name": "Sebastian Heuer", 237 | "email": "sebastian@phpeople.de", 238 | "role": "Developer" 239 | }, 240 | { 241 | "name": "Sebastian Bergmann", 242 | "email": "sebastian@phpunit.de", 243 | "role": "Developer" 244 | } 245 | ], 246 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", 247 | "support": { 248 | "issues": "https://github.com/phar-io/manifest/issues", 249 | "source": "https://github.com/phar-io/manifest/tree/master" 250 | }, 251 | "time": "2020-06-27T14:33:11+00:00" 252 | }, 253 | { 254 | "name": "phar-io/version", 255 | "version": "3.0.4", 256 | "source": { 257 | "type": "git", 258 | "url": "https://github.com/phar-io/version.git", 259 | "reference": "e4782611070e50613683d2b9a57730e9a3ba5451" 260 | }, 261 | "dist": { 262 | "type": "zip", 263 | "url": "https://api.github.com/repos/phar-io/version/zipball/e4782611070e50613683d2b9a57730e9a3ba5451", 264 | "reference": "e4782611070e50613683d2b9a57730e9a3ba5451", 265 | "shasum": "" 266 | }, 267 | "require": { 268 | "php": "^7.2 || ^8.0" 269 | }, 270 | "type": "library", 271 | "autoload": { 272 | "classmap": [ 273 | "src/" 274 | ] 275 | }, 276 | "notification-url": "https://packagist.org/downloads/", 277 | "license": [ 278 | "BSD-3-Clause" 279 | ], 280 | "authors": [ 281 | { 282 | "name": "Arne Blankerts", 283 | "email": "arne@blankerts.de", 284 | "role": "Developer" 285 | }, 286 | { 287 | "name": "Sebastian Heuer", 288 | "email": "sebastian@phpeople.de", 289 | "role": "Developer" 290 | }, 291 | { 292 | "name": "Sebastian Bergmann", 293 | "email": "sebastian@phpunit.de", 294 | "role": "Developer" 295 | } 296 | ], 297 | "description": "Library for handling version information and constraints", 298 | "support": { 299 | "issues": "https://github.com/phar-io/version/issues", 300 | "source": "https://github.com/phar-io/version/tree/3.0.4" 301 | }, 302 | "time": "2020-12-13T23:18:30+00:00" 303 | }, 304 | { 305 | "name": "phpdocumentor/reflection-common", 306 | "version": "2.2.0", 307 | "source": { 308 | "type": "git", 309 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git", 310 | "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" 311 | }, 312 | "dist": { 313 | "type": "zip", 314 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", 315 | "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", 316 | "shasum": "" 317 | }, 318 | "require": { 319 | "php": "^7.2 || ^8.0" 320 | }, 321 | "type": "library", 322 | "extra": { 323 | "branch-alias": { 324 | "dev-2.x": "2.x-dev" 325 | } 326 | }, 327 | "autoload": { 328 | "psr-4": { 329 | "phpDocumentor\\Reflection\\": "src/" 330 | } 331 | }, 332 | "notification-url": "https://packagist.org/downloads/", 333 | "license": [ 334 | "MIT" 335 | ], 336 | "authors": [ 337 | { 338 | "name": "Jaap van Otterdijk", 339 | "email": "opensource@ijaap.nl" 340 | } 341 | ], 342 | "description": "Common reflection classes used by phpdocumentor to reflect the code structure", 343 | "homepage": "http://www.phpdoc.org", 344 | "keywords": [ 345 | "FQSEN", 346 | "phpDocumentor", 347 | "phpdoc", 348 | "reflection", 349 | "static analysis" 350 | ], 351 | "support": { 352 | "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", 353 | "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" 354 | }, 355 | "time": "2020-06-27T09:03:43+00:00" 356 | }, 357 | { 358 | "name": "phpdocumentor/reflection-docblock", 359 | "version": "5.2.2", 360 | "source": { 361 | "type": "git", 362 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", 363 | "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" 364 | }, 365 | "dist": { 366 | "type": "zip", 367 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", 368 | "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", 369 | "shasum": "" 370 | }, 371 | "require": { 372 | "ext-filter": "*", 373 | "php": "^7.2 || ^8.0", 374 | "phpdocumentor/reflection-common": "^2.2", 375 | "phpdocumentor/type-resolver": "^1.3", 376 | "webmozart/assert": "^1.9.1" 377 | }, 378 | "require-dev": { 379 | "mockery/mockery": "~1.3.2" 380 | }, 381 | "type": "library", 382 | "extra": { 383 | "branch-alias": { 384 | "dev-master": "5.x-dev" 385 | } 386 | }, 387 | "autoload": { 388 | "psr-4": { 389 | "phpDocumentor\\Reflection\\": "src" 390 | } 391 | }, 392 | "notification-url": "https://packagist.org/downloads/", 393 | "license": [ 394 | "MIT" 395 | ], 396 | "authors": [ 397 | { 398 | "name": "Mike van Riel", 399 | "email": "me@mikevanriel.com" 400 | }, 401 | { 402 | "name": "Jaap van Otterdijk", 403 | "email": "account@ijaap.nl" 404 | } 405 | ], 406 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", 407 | "support": { 408 | "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", 409 | "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master" 410 | }, 411 | "time": "2020-09-03T19:13:55+00:00" 412 | }, 413 | { 414 | "name": "phpdocumentor/type-resolver", 415 | "version": "1.4.0", 416 | "source": { 417 | "type": "git", 418 | "url": "https://github.com/phpDocumentor/TypeResolver.git", 419 | "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" 420 | }, 421 | "dist": { 422 | "type": "zip", 423 | "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", 424 | "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", 425 | "shasum": "" 426 | }, 427 | "require": { 428 | "php": "^7.2 || ^8.0", 429 | "phpdocumentor/reflection-common": "^2.0" 430 | }, 431 | "require-dev": { 432 | "ext-tokenizer": "*" 433 | }, 434 | "type": "library", 435 | "extra": { 436 | "branch-alias": { 437 | "dev-1.x": "1.x-dev" 438 | } 439 | }, 440 | "autoload": { 441 | "psr-4": { 442 | "phpDocumentor\\Reflection\\": "src" 443 | } 444 | }, 445 | "notification-url": "https://packagist.org/downloads/", 446 | "license": [ 447 | "MIT" 448 | ], 449 | "authors": [ 450 | { 451 | "name": "Mike van Riel", 452 | "email": "me@mikevanriel.com" 453 | } 454 | ], 455 | "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", 456 | "support": { 457 | "issues": "https://github.com/phpDocumentor/TypeResolver/issues", 458 | "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0" 459 | }, 460 | "time": "2020-09-17T18:55:26+00:00" 461 | }, 462 | { 463 | "name": "phpspec/prophecy", 464 | "version": "1.12.2", 465 | "source": { 466 | "type": "git", 467 | "url": "https://github.com/phpspec/prophecy.git", 468 | "reference": "245710e971a030f42e08f4912863805570f23d39" 469 | }, 470 | "dist": { 471 | "type": "zip", 472 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/245710e971a030f42e08f4912863805570f23d39", 473 | "reference": "245710e971a030f42e08f4912863805570f23d39", 474 | "shasum": "" 475 | }, 476 | "require": { 477 | "doctrine/instantiator": "^1.2", 478 | "php": "^7.2 || ~8.0, <8.1", 479 | "phpdocumentor/reflection-docblock": "^5.2", 480 | "sebastian/comparator": "^3.0 || ^4.0", 481 | "sebastian/recursion-context": "^3.0 || ^4.0" 482 | }, 483 | "require-dev": { 484 | "phpspec/phpspec": "^6.0", 485 | "phpunit/phpunit": "^8.0 || ^9.0" 486 | }, 487 | "type": "library", 488 | "extra": { 489 | "branch-alias": { 490 | "dev-master": "1.11.x-dev" 491 | } 492 | }, 493 | "autoload": { 494 | "psr-4": { 495 | "Prophecy\\": "src/Prophecy" 496 | } 497 | }, 498 | "notification-url": "https://packagist.org/downloads/", 499 | "license": [ 500 | "MIT" 501 | ], 502 | "authors": [ 503 | { 504 | "name": "Konstantin Kudryashov", 505 | "email": "ever.zet@gmail.com", 506 | "homepage": "http://everzet.com" 507 | }, 508 | { 509 | "name": "Marcello Duarte", 510 | "email": "marcello.duarte@gmail.com" 511 | } 512 | ], 513 | "description": "Highly opinionated mocking framework for PHP 5.3+", 514 | "homepage": "https://github.com/phpspec/prophecy", 515 | "keywords": [ 516 | "Double", 517 | "Dummy", 518 | "fake", 519 | "mock", 520 | "spy", 521 | "stub" 522 | ], 523 | "support": { 524 | "issues": "https://github.com/phpspec/prophecy/issues", 525 | "source": "https://github.com/phpspec/prophecy/tree/1.12.2" 526 | }, 527 | "time": "2020-12-19T10:15:11+00:00" 528 | }, 529 | { 530 | "name": "phpunit/php-code-coverage", 531 | "version": "9.2.5", 532 | "source": { 533 | "type": "git", 534 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 535 | "reference": "f3e026641cc91909d421802dd3ac7827ebfd97e1" 536 | }, 537 | "dist": { 538 | "type": "zip", 539 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f3e026641cc91909d421802dd3ac7827ebfd97e1", 540 | "reference": "f3e026641cc91909d421802dd3ac7827ebfd97e1", 541 | "shasum": "" 542 | }, 543 | "require": { 544 | "ext-dom": "*", 545 | "ext-libxml": "*", 546 | "ext-xmlwriter": "*", 547 | "nikic/php-parser": "^4.10.2", 548 | "php": ">=7.3", 549 | "phpunit/php-file-iterator": "^3.0.3", 550 | "phpunit/php-text-template": "^2.0.2", 551 | "sebastian/code-unit-reverse-lookup": "^2.0.2", 552 | "sebastian/complexity": "^2.0", 553 | "sebastian/environment": "^5.1.2", 554 | "sebastian/lines-of-code": "^1.0.3", 555 | "sebastian/version": "^3.0.1", 556 | "theseer/tokenizer": "^1.2.0" 557 | }, 558 | "require-dev": { 559 | "phpunit/phpunit": "^9.3" 560 | }, 561 | "suggest": { 562 | "ext-pcov": "*", 563 | "ext-xdebug": "*" 564 | }, 565 | "type": "library", 566 | "extra": { 567 | "branch-alias": { 568 | "dev-master": "9.2-dev" 569 | } 570 | }, 571 | "autoload": { 572 | "classmap": [ 573 | "src/" 574 | ] 575 | }, 576 | "notification-url": "https://packagist.org/downloads/", 577 | "license": [ 578 | "BSD-3-Clause" 579 | ], 580 | "authors": [ 581 | { 582 | "name": "Sebastian Bergmann", 583 | "email": "sebastian@phpunit.de", 584 | "role": "lead" 585 | } 586 | ], 587 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 588 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 589 | "keywords": [ 590 | "coverage", 591 | "testing", 592 | "xunit" 593 | ], 594 | "support": { 595 | "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", 596 | "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.5" 597 | }, 598 | "funding": [ 599 | { 600 | "url": "https://github.com/sebastianbergmann", 601 | "type": "github" 602 | } 603 | ], 604 | "time": "2020-11-28T06:44:49+00:00" 605 | }, 606 | { 607 | "name": "phpunit/php-file-iterator", 608 | "version": "3.0.5", 609 | "source": { 610 | "type": "git", 611 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 612 | "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8" 613 | }, 614 | "dist": { 615 | "type": "zip", 616 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/aa4be8575f26070b100fccb67faabb28f21f66f8", 617 | "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8", 618 | "shasum": "" 619 | }, 620 | "require": { 621 | "php": ">=7.3" 622 | }, 623 | "require-dev": { 624 | "phpunit/phpunit": "^9.3" 625 | }, 626 | "type": "library", 627 | "extra": { 628 | "branch-alias": { 629 | "dev-master": "3.0-dev" 630 | } 631 | }, 632 | "autoload": { 633 | "classmap": [ 634 | "src/" 635 | ] 636 | }, 637 | "notification-url": "https://packagist.org/downloads/", 638 | "license": [ 639 | "BSD-3-Clause" 640 | ], 641 | "authors": [ 642 | { 643 | "name": "Sebastian Bergmann", 644 | "email": "sebastian@phpunit.de", 645 | "role": "lead" 646 | } 647 | ], 648 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 649 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 650 | "keywords": [ 651 | "filesystem", 652 | "iterator" 653 | ], 654 | "support": { 655 | "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", 656 | "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.5" 657 | }, 658 | "funding": [ 659 | { 660 | "url": "https://github.com/sebastianbergmann", 661 | "type": "github" 662 | } 663 | ], 664 | "time": "2020-09-28T05:57:25+00:00" 665 | }, 666 | { 667 | "name": "phpunit/php-invoker", 668 | "version": "3.1.1", 669 | "source": { 670 | "type": "git", 671 | "url": "https://github.com/sebastianbergmann/php-invoker.git", 672 | "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" 673 | }, 674 | "dist": { 675 | "type": "zip", 676 | "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", 677 | "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", 678 | "shasum": "" 679 | }, 680 | "require": { 681 | "php": ">=7.3" 682 | }, 683 | "require-dev": { 684 | "ext-pcntl": "*", 685 | "phpunit/phpunit": "^9.3" 686 | }, 687 | "suggest": { 688 | "ext-pcntl": "*" 689 | }, 690 | "type": "library", 691 | "extra": { 692 | "branch-alias": { 693 | "dev-master": "3.1-dev" 694 | } 695 | }, 696 | "autoload": { 697 | "classmap": [ 698 | "src/" 699 | ] 700 | }, 701 | "notification-url": "https://packagist.org/downloads/", 702 | "license": [ 703 | "BSD-3-Clause" 704 | ], 705 | "authors": [ 706 | { 707 | "name": "Sebastian Bergmann", 708 | "email": "sebastian@phpunit.de", 709 | "role": "lead" 710 | } 711 | ], 712 | "description": "Invoke callables with a timeout", 713 | "homepage": "https://github.com/sebastianbergmann/php-invoker/", 714 | "keywords": [ 715 | "process" 716 | ], 717 | "support": { 718 | "issues": "https://github.com/sebastianbergmann/php-invoker/issues", 719 | "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" 720 | }, 721 | "funding": [ 722 | { 723 | "url": "https://github.com/sebastianbergmann", 724 | "type": "github" 725 | } 726 | ], 727 | "time": "2020-09-28T05:58:55+00:00" 728 | }, 729 | { 730 | "name": "phpunit/php-text-template", 731 | "version": "2.0.4", 732 | "source": { 733 | "type": "git", 734 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 735 | "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" 736 | }, 737 | "dist": { 738 | "type": "zip", 739 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", 740 | "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", 741 | "shasum": "" 742 | }, 743 | "require": { 744 | "php": ">=7.3" 745 | }, 746 | "require-dev": { 747 | "phpunit/phpunit": "^9.3" 748 | }, 749 | "type": "library", 750 | "extra": { 751 | "branch-alias": { 752 | "dev-master": "2.0-dev" 753 | } 754 | }, 755 | "autoload": { 756 | "classmap": [ 757 | "src/" 758 | ] 759 | }, 760 | "notification-url": "https://packagist.org/downloads/", 761 | "license": [ 762 | "BSD-3-Clause" 763 | ], 764 | "authors": [ 765 | { 766 | "name": "Sebastian Bergmann", 767 | "email": "sebastian@phpunit.de", 768 | "role": "lead" 769 | } 770 | ], 771 | "description": "Simple template engine.", 772 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 773 | "keywords": [ 774 | "template" 775 | ], 776 | "support": { 777 | "issues": "https://github.com/sebastianbergmann/php-text-template/issues", 778 | "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" 779 | }, 780 | "funding": [ 781 | { 782 | "url": "https://github.com/sebastianbergmann", 783 | "type": "github" 784 | } 785 | ], 786 | "time": "2020-10-26T05:33:50+00:00" 787 | }, 788 | { 789 | "name": "phpunit/php-timer", 790 | "version": "5.0.3", 791 | "source": { 792 | "type": "git", 793 | "url": "https://github.com/sebastianbergmann/php-timer.git", 794 | "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" 795 | }, 796 | "dist": { 797 | "type": "zip", 798 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", 799 | "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", 800 | "shasum": "" 801 | }, 802 | "require": { 803 | "php": ">=7.3" 804 | }, 805 | "require-dev": { 806 | "phpunit/phpunit": "^9.3" 807 | }, 808 | "type": "library", 809 | "extra": { 810 | "branch-alias": { 811 | "dev-master": "5.0-dev" 812 | } 813 | }, 814 | "autoload": { 815 | "classmap": [ 816 | "src/" 817 | ] 818 | }, 819 | "notification-url": "https://packagist.org/downloads/", 820 | "license": [ 821 | "BSD-3-Clause" 822 | ], 823 | "authors": [ 824 | { 825 | "name": "Sebastian Bergmann", 826 | "email": "sebastian@phpunit.de", 827 | "role": "lead" 828 | } 829 | ], 830 | "description": "Utility class for timing", 831 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 832 | "keywords": [ 833 | "timer" 834 | ], 835 | "support": { 836 | "issues": "https://github.com/sebastianbergmann/php-timer/issues", 837 | "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" 838 | }, 839 | "funding": [ 840 | { 841 | "url": "https://github.com/sebastianbergmann", 842 | "type": "github" 843 | } 844 | ], 845 | "time": "2020-10-26T13:16:10+00:00" 846 | }, 847 | { 848 | "name": "phpunit/phpunit", 849 | "version": "9.5.1", 850 | "source": { 851 | "type": "git", 852 | "url": "https://github.com/sebastianbergmann/phpunit.git", 853 | "reference": "e7bdf4085de85a825f4424eae52c99a1cec2f360" 854 | }, 855 | "dist": { 856 | "type": "zip", 857 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e7bdf4085de85a825f4424eae52c99a1cec2f360", 858 | "reference": "e7bdf4085de85a825f4424eae52c99a1cec2f360", 859 | "shasum": "" 860 | }, 861 | "require": { 862 | "doctrine/instantiator": "^1.3.1", 863 | "ext-dom": "*", 864 | "ext-json": "*", 865 | "ext-libxml": "*", 866 | "ext-mbstring": "*", 867 | "ext-xml": "*", 868 | "ext-xmlwriter": "*", 869 | "myclabs/deep-copy": "^1.10.1", 870 | "phar-io/manifest": "^2.0.1", 871 | "phar-io/version": "^3.0.2", 872 | "php": ">=7.3", 873 | "phpspec/prophecy": "^1.12.1", 874 | "phpunit/php-code-coverage": "^9.2.3", 875 | "phpunit/php-file-iterator": "^3.0.5", 876 | "phpunit/php-invoker": "^3.1.1", 877 | "phpunit/php-text-template": "^2.0.3", 878 | "phpunit/php-timer": "^5.0.2", 879 | "sebastian/cli-parser": "^1.0.1", 880 | "sebastian/code-unit": "^1.0.6", 881 | "sebastian/comparator": "^4.0.5", 882 | "sebastian/diff": "^4.0.3", 883 | "sebastian/environment": "^5.1.3", 884 | "sebastian/exporter": "^4.0.3", 885 | "sebastian/global-state": "^5.0.1", 886 | "sebastian/object-enumerator": "^4.0.3", 887 | "sebastian/resource-operations": "^3.0.3", 888 | "sebastian/type": "^2.3", 889 | "sebastian/version": "^3.0.2" 890 | }, 891 | "require-dev": { 892 | "ext-pdo": "*", 893 | "phpspec/prophecy-phpunit": "^2.0.1" 894 | }, 895 | "suggest": { 896 | "ext-soap": "*", 897 | "ext-xdebug": "*" 898 | }, 899 | "bin": [ 900 | "phpunit" 901 | ], 902 | "type": "library", 903 | "extra": { 904 | "branch-alias": { 905 | "dev-master": "9.5-dev" 906 | } 907 | }, 908 | "autoload": { 909 | "classmap": [ 910 | "src/" 911 | ], 912 | "files": [ 913 | "src/Framework/Assert/Functions.php" 914 | ] 915 | }, 916 | "notification-url": "https://packagist.org/downloads/", 917 | "license": [ 918 | "BSD-3-Clause" 919 | ], 920 | "authors": [ 921 | { 922 | "name": "Sebastian Bergmann", 923 | "email": "sebastian@phpunit.de", 924 | "role": "lead" 925 | } 926 | ], 927 | "description": "The PHP Unit Testing framework.", 928 | "homepage": "https://phpunit.de/", 929 | "keywords": [ 930 | "phpunit", 931 | "testing", 932 | "xunit" 933 | ], 934 | "support": { 935 | "issues": "https://github.com/sebastianbergmann/phpunit/issues", 936 | "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.1" 937 | }, 938 | "funding": [ 939 | { 940 | "url": "https://phpunit.de/donate.html", 941 | "type": "custom" 942 | }, 943 | { 944 | "url": "https://github.com/sebastianbergmann", 945 | "type": "github" 946 | } 947 | ], 948 | "time": "2021-01-17T07:42:25+00:00" 949 | }, 950 | { 951 | "name": "sebastian/cli-parser", 952 | "version": "1.0.1", 953 | "source": { 954 | "type": "git", 955 | "url": "https://github.com/sebastianbergmann/cli-parser.git", 956 | "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" 957 | }, 958 | "dist": { 959 | "type": "zip", 960 | "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", 961 | "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", 962 | "shasum": "" 963 | }, 964 | "require": { 965 | "php": ">=7.3" 966 | }, 967 | "require-dev": { 968 | "phpunit/phpunit": "^9.3" 969 | }, 970 | "type": "library", 971 | "extra": { 972 | "branch-alias": { 973 | "dev-master": "1.0-dev" 974 | } 975 | }, 976 | "autoload": { 977 | "classmap": [ 978 | "src/" 979 | ] 980 | }, 981 | "notification-url": "https://packagist.org/downloads/", 982 | "license": [ 983 | "BSD-3-Clause" 984 | ], 985 | "authors": [ 986 | { 987 | "name": "Sebastian Bergmann", 988 | "email": "sebastian@phpunit.de", 989 | "role": "lead" 990 | } 991 | ], 992 | "description": "Library for parsing CLI options", 993 | "homepage": "https://github.com/sebastianbergmann/cli-parser", 994 | "support": { 995 | "issues": "https://github.com/sebastianbergmann/cli-parser/issues", 996 | "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" 997 | }, 998 | "funding": [ 999 | { 1000 | "url": "https://github.com/sebastianbergmann", 1001 | "type": "github" 1002 | } 1003 | ], 1004 | "time": "2020-09-28T06:08:49+00:00" 1005 | }, 1006 | { 1007 | "name": "sebastian/code-unit", 1008 | "version": "1.0.8", 1009 | "source": { 1010 | "type": "git", 1011 | "url": "https://github.com/sebastianbergmann/code-unit.git", 1012 | "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" 1013 | }, 1014 | "dist": { 1015 | "type": "zip", 1016 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", 1017 | "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", 1018 | "shasum": "" 1019 | }, 1020 | "require": { 1021 | "php": ">=7.3" 1022 | }, 1023 | "require-dev": { 1024 | "phpunit/phpunit": "^9.3" 1025 | }, 1026 | "type": "library", 1027 | "extra": { 1028 | "branch-alias": { 1029 | "dev-master": "1.0-dev" 1030 | } 1031 | }, 1032 | "autoload": { 1033 | "classmap": [ 1034 | "src/" 1035 | ] 1036 | }, 1037 | "notification-url": "https://packagist.org/downloads/", 1038 | "license": [ 1039 | "BSD-3-Clause" 1040 | ], 1041 | "authors": [ 1042 | { 1043 | "name": "Sebastian Bergmann", 1044 | "email": "sebastian@phpunit.de", 1045 | "role": "lead" 1046 | } 1047 | ], 1048 | "description": "Collection of value objects that represent the PHP code units", 1049 | "homepage": "https://github.com/sebastianbergmann/code-unit", 1050 | "support": { 1051 | "issues": "https://github.com/sebastianbergmann/code-unit/issues", 1052 | "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" 1053 | }, 1054 | "funding": [ 1055 | { 1056 | "url": "https://github.com/sebastianbergmann", 1057 | "type": "github" 1058 | } 1059 | ], 1060 | "time": "2020-10-26T13:08:54+00:00" 1061 | }, 1062 | { 1063 | "name": "sebastian/code-unit-reverse-lookup", 1064 | "version": "2.0.3", 1065 | "source": { 1066 | "type": "git", 1067 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", 1068 | "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" 1069 | }, 1070 | "dist": { 1071 | "type": "zip", 1072 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", 1073 | "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", 1074 | "shasum": "" 1075 | }, 1076 | "require": { 1077 | "php": ">=7.3" 1078 | }, 1079 | "require-dev": { 1080 | "phpunit/phpunit": "^9.3" 1081 | }, 1082 | "type": "library", 1083 | "extra": { 1084 | "branch-alias": { 1085 | "dev-master": "2.0-dev" 1086 | } 1087 | }, 1088 | "autoload": { 1089 | "classmap": [ 1090 | "src/" 1091 | ] 1092 | }, 1093 | "notification-url": "https://packagist.org/downloads/", 1094 | "license": [ 1095 | "BSD-3-Clause" 1096 | ], 1097 | "authors": [ 1098 | { 1099 | "name": "Sebastian Bergmann", 1100 | "email": "sebastian@phpunit.de" 1101 | } 1102 | ], 1103 | "description": "Looks up which function or method a line of code belongs to", 1104 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", 1105 | "support": { 1106 | "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", 1107 | "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" 1108 | }, 1109 | "funding": [ 1110 | { 1111 | "url": "https://github.com/sebastianbergmann", 1112 | "type": "github" 1113 | } 1114 | ], 1115 | "time": "2020-09-28T05:30:19+00:00" 1116 | }, 1117 | { 1118 | "name": "sebastian/comparator", 1119 | "version": "4.0.6", 1120 | "source": { 1121 | "type": "git", 1122 | "url": "https://github.com/sebastianbergmann/comparator.git", 1123 | "reference": "55f4261989e546dc112258c7a75935a81a7ce382" 1124 | }, 1125 | "dist": { 1126 | "type": "zip", 1127 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", 1128 | "reference": "55f4261989e546dc112258c7a75935a81a7ce382", 1129 | "shasum": "" 1130 | }, 1131 | "require": { 1132 | "php": ">=7.3", 1133 | "sebastian/diff": "^4.0", 1134 | "sebastian/exporter": "^4.0" 1135 | }, 1136 | "require-dev": { 1137 | "phpunit/phpunit": "^9.3" 1138 | }, 1139 | "type": "library", 1140 | "extra": { 1141 | "branch-alias": { 1142 | "dev-master": "4.0-dev" 1143 | } 1144 | }, 1145 | "autoload": { 1146 | "classmap": [ 1147 | "src/" 1148 | ] 1149 | }, 1150 | "notification-url": "https://packagist.org/downloads/", 1151 | "license": [ 1152 | "BSD-3-Clause" 1153 | ], 1154 | "authors": [ 1155 | { 1156 | "name": "Sebastian Bergmann", 1157 | "email": "sebastian@phpunit.de" 1158 | }, 1159 | { 1160 | "name": "Jeff Welch", 1161 | "email": "whatthejeff@gmail.com" 1162 | }, 1163 | { 1164 | "name": "Volker Dusch", 1165 | "email": "github@wallbash.com" 1166 | }, 1167 | { 1168 | "name": "Bernhard Schussek", 1169 | "email": "bschussek@2bepublished.at" 1170 | } 1171 | ], 1172 | "description": "Provides the functionality to compare PHP values for equality", 1173 | "homepage": "https://github.com/sebastianbergmann/comparator", 1174 | "keywords": [ 1175 | "comparator", 1176 | "compare", 1177 | "equality" 1178 | ], 1179 | "support": { 1180 | "issues": "https://github.com/sebastianbergmann/comparator/issues", 1181 | "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6" 1182 | }, 1183 | "funding": [ 1184 | { 1185 | "url": "https://github.com/sebastianbergmann", 1186 | "type": "github" 1187 | } 1188 | ], 1189 | "time": "2020-10-26T15:49:45+00:00" 1190 | }, 1191 | { 1192 | "name": "sebastian/complexity", 1193 | "version": "2.0.2", 1194 | "source": { 1195 | "type": "git", 1196 | "url": "https://github.com/sebastianbergmann/complexity.git", 1197 | "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" 1198 | }, 1199 | "dist": { 1200 | "type": "zip", 1201 | "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", 1202 | "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", 1203 | "shasum": "" 1204 | }, 1205 | "require": { 1206 | "nikic/php-parser": "^4.7", 1207 | "php": ">=7.3" 1208 | }, 1209 | "require-dev": { 1210 | "phpunit/phpunit": "^9.3" 1211 | }, 1212 | "type": "library", 1213 | "extra": { 1214 | "branch-alias": { 1215 | "dev-master": "2.0-dev" 1216 | } 1217 | }, 1218 | "autoload": { 1219 | "classmap": [ 1220 | "src/" 1221 | ] 1222 | }, 1223 | "notification-url": "https://packagist.org/downloads/", 1224 | "license": [ 1225 | "BSD-3-Clause" 1226 | ], 1227 | "authors": [ 1228 | { 1229 | "name": "Sebastian Bergmann", 1230 | "email": "sebastian@phpunit.de", 1231 | "role": "lead" 1232 | } 1233 | ], 1234 | "description": "Library for calculating the complexity of PHP code units", 1235 | "homepage": "https://github.com/sebastianbergmann/complexity", 1236 | "support": { 1237 | "issues": "https://github.com/sebastianbergmann/complexity/issues", 1238 | "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" 1239 | }, 1240 | "funding": [ 1241 | { 1242 | "url": "https://github.com/sebastianbergmann", 1243 | "type": "github" 1244 | } 1245 | ], 1246 | "time": "2020-10-26T15:52:27+00:00" 1247 | }, 1248 | { 1249 | "name": "sebastian/diff", 1250 | "version": "4.0.4", 1251 | "source": { 1252 | "type": "git", 1253 | "url": "https://github.com/sebastianbergmann/diff.git", 1254 | "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" 1255 | }, 1256 | "dist": { 1257 | "type": "zip", 1258 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", 1259 | "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", 1260 | "shasum": "" 1261 | }, 1262 | "require": { 1263 | "php": ">=7.3" 1264 | }, 1265 | "require-dev": { 1266 | "phpunit/phpunit": "^9.3", 1267 | "symfony/process": "^4.2 || ^5" 1268 | }, 1269 | "type": "library", 1270 | "extra": { 1271 | "branch-alias": { 1272 | "dev-master": "4.0-dev" 1273 | } 1274 | }, 1275 | "autoload": { 1276 | "classmap": [ 1277 | "src/" 1278 | ] 1279 | }, 1280 | "notification-url": "https://packagist.org/downloads/", 1281 | "license": [ 1282 | "BSD-3-Clause" 1283 | ], 1284 | "authors": [ 1285 | { 1286 | "name": "Sebastian Bergmann", 1287 | "email": "sebastian@phpunit.de" 1288 | }, 1289 | { 1290 | "name": "Kore Nordmann", 1291 | "email": "mail@kore-nordmann.de" 1292 | } 1293 | ], 1294 | "description": "Diff implementation", 1295 | "homepage": "https://github.com/sebastianbergmann/diff", 1296 | "keywords": [ 1297 | "diff", 1298 | "udiff", 1299 | "unidiff", 1300 | "unified diff" 1301 | ], 1302 | "support": { 1303 | "issues": "https://github.com/sebastianbergmann/diff/issues", 1304 | "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" 1305 | }, 1306 | "funding": [ 1307 | { 1308 | "url": "https://github.com/sebastianbergmann", 1309 | "type": "github" 1310 | } 1311 | ], 1312 | "time": "2020-10-26T13:10:38+00:00" 1313 | }, 1314 | { 1315 | "name": "sebastian/environment", 1316 | "version": "5.1.3", 1317 | "source": { 1318 | "type": "git", 1319 | "url": "https://github.com/sebastianbergmann/environment.git", 1320 | "reference": "388b6ced16caa751030f6a69e588299fa09200ac" 1321 | }, 1322 | "dist": { 1323 | "type": "zip", 1324 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac", 1325 | "reference": "388b6ced16caa751030f6a69e588299fa09200ac", 1326 | "shasum": "" 1327 | }, 1328 | "require": { 1329 | "php": ">=7.3" 1330 | }, 1331 | "require-dev": { 1332 | "phpunit/phpunit": "^9.3" 1333 | }, 1334 | "suggest": { 1335 | "ext-posix": "*" 1336 | }, 1337 | "type": "library", 1338 | "extra": { 1339 | "branch-alias": { 1340 | "dev-master": "5.1-dev" 1341 | } 1342 | }, 1343 | "autoload": { 1344 | "classmap": [ 1345 | "src/" 1346 | ] 1347 | }, 1348 | "notification-url": "https://packagist.org/downloads/", 1349 | "license": [ 1350 | "BSD-3-Clause" 1351 | ], 1352 | "authors": [ 1353 | { 1354 | "name": "Sebastian Bergmann", 1355 | "email": "sebastian@phpunit.de" 1356 | } 1357 | ], 1358 | "description": "Provides functionality to handle HHVM/PHP environments", 1359 | "homepage": "http://www.github.com/sebastianbergmann/environment", 1360 | "keywords": [ 1361 | "Xdebug", 1362 | "environment", 1363 | "hhvm" 1364 | ], 1365 | "support": { 1366 | "issues": "https://github.com/sebastianbergmann/environment/issues", 1367 | "source": "https://github.com/sebastianbergmann/environment/tree/5.1.3" 1368 | }, 1369 | "funding": [ 1370 | { 1371 | "url": "https://github.com/sebastianbergmann", 1372 | "type": "github" 1373 | } 1374 | ], 1375 | "time": "2020-09-28T05:52:38+00:00" 1376 | }, 1377 | { 1378 | "name": "sebastian/exporter", 1379 | "version": "4.0.3", 1380 | "source": { 1381 | "type": "git", 1382 | "url": "https://github.com/sebastianbergmann/exporter.git", 1383 | "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65" 1384 | }, 1385 | "dist": { 1386 | "type": "zip", 1387 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/d89cc98761b8cb5a1a235a6b703ae50d34080e65", 1388 | "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65", 1389 | "shasum": "" 1390 | }, 1391 | "require": { 1392 | "php": ">=7.3", 1393 | "sebastian/recursion-context": "^4.0" 1394 | }, 1395 | "require-dev": { 1396 | "ext-mbstring": "*", 1397 | "phpunit/phpunit": "^9.3" 1398 | }, 1399 | "type": "library", 1400 | "extra": { 1401 | "branch-alias": { 1402 | "dev-master": "4.0-dev" 1403 | } 1404 | }, 1405 | "autoload": { 1406 | "classmap": [ 1407 | "src/" 1408 | ] 1409 | }, 1410 | "notification-url": "https://packagist.org/downloads/", 1411 | "license": [ 1412 | "BSD-3-Clause" 1413 | ], 1414 | "authors": [ 1415 | { 1416 | "name": "Sebastian Bergmann", 1417 | "email": "sebastian@phpunit.de" 1418 | }, 1419 | { 1420 | "name": "Jeff Welch", 1421 | "email": "whatthejeff@gmail.com" 1422 | }, 1423 | { 1424 | "name": "Volker Dusch", 1425 | "email": "github@wallbash.com" 1426 | }, 1427 | { 1428 | "name": "Adam Harvey", 1429 | "email": "aharvey@php.net" 1430 | }, 1431 | { 1432 | "name": "Bernhard Schussek", 1433 | "email": "bschussek@gmail.com" 1434 | } 1435 | ], 1436 | "description": "Provides the functionality to export PHP variables for visualization", 1437 | "homepage": "http://www.github.com/sebastianbergmann/exporter", 1438 | "keywords": [ 1439 | "export", 1440 | "exporter" 1441 | ], 1442 | "support": { 1443 | "issues": "https://github.com/sebastianbergmann/exporter/issues", 1444 | "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.3" 1445 | }, 1446 | "funding": [ 1447 | { 1448 | "url": "https://github.com/sebastianbergmann", 1449 | "type": "github" 1450 | } 1451 | ], 1452 | "time": "2020-09-28T05:24:23+00:00" 1453 | }, 1454 | { 1455 | "name": "sebastian/global-state", 1456 | "version": "5.0.2", 1457 | "source": { 1458 | "type": "git", 1459 | "url": "https://github.com/sebastianbergmann/global-state.git", 1460 | "reference": "a90ccbddffa067b51f574dea6eb25d5680839455" 1461 | }, 1462 | "dist": { 1463 | "type": "zip", 1464 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/a90ccbddffa067b51f574dea6eb25d5680839455", 1465 | "reference": "a90ccbddffa067b51f574dea6eb25d5680839455", 1466 | "shasum": "" 1467 | }, 1468 | "require": { 1469 | "php": ">=7.3", 1470 | "sebastian/object-reflector": "^2.0", 1471 | "sebastian/recursion-context": "^4.0" 1472 | }, 1473 | "require-dev": { 1474 | "ext-dom": "*", 1475 | "phpunit/phpunit": "^9.3" 1476 | }, 1477 | "suggest": { 1478 | "ext-uopz": "*" 1479 | }, 1480 | "type": "library", 1481 | "extra": { 1482 | "branch-alias": { 1483 | "dev-master": "5.0-dev" 1484 | } 1485 | }, 1486 | "autoload": { 1487 | "classmap": [ 1488 | "src/" 1489 | ] 1490 | }, 1491 | "notification-url": "https://packagist.org/downloads/", 1492 | "license": [ 1493 | "BSD-3-Clause" 1494 | ], 1495 | "authors": [ 1496 | { 1497 | "name": "Sebastian Bergmann", 1498 | "email": "sebastian@phpunit.de" 1499 | } 1500 | ], 1501 | "description": "Snapshotting of global state", 1502 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 1503 | "keywords": [ 1504 | "global state" 1505 | ], 1506 | "support": { 1507 | "issues": "https://github.com/sebastianbergmann/global-state/issues", 1508 | "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.2" 1509 | }, 1510 | "funding": [ 1511 | { 1512 | "url": "https://github.com/sebastianbergmann", 1513 | "type": "github" 1514 | } 1515 | ], 1516 | "time": "2020-10-26T15:55:19+00:00" 1517 | }, 1518 | { 1519 | "name": "sebastian/lines-of-code", 1520 | "version": "1.0.3", 1521 | "source": { 1522 | "type": "git", 1523 | "url": "https://github.com/sebastianbergmann/lines-of-code.git", 1524 | "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" 1525 | }, 1526 | "dist": { 1527 | "type": "zip", 1528 | "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", 1529 | "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", 1530 | "shasum": "" 1531 | }, 1532 | "require": { 1533 | "nikic/php-parser": "^4.6", 1534 | "php": ">=7.3" 1535 | }, 1536 | "require-dev": { 1537 | "phpunit/phpunit": "^9.3" 1538 | }, 1539 | "type": "library", 1540 | "extra": { 1541 | "branch-alias": { 1542 | "dev-master": "1.0-dev" 1543 | } 1544 | }, 1545 | "autoload": { 1546 | "classmap": [ 1547 | "src/" 1548 | ] 1549 | }, 1550 | "notification-url": "https://packagist.org/downloads/", 1551 | "license": [ 1552 | "BSD-3-Clause" 1553 | ], 1554 | "authors": [ 1555 | { 1556 | "name": "Sebastian Bergmann", 1557 | "email": "sebastian@phpunit.de", 1558 | "role": "lead" 1559 | } 1560 | ], 1561 | "description": "Library for counting the lines of code in PHP source code", 1562 | "homepage": "https://github.com/sebastianbergmann/lines-of-code", 1563 | "support": { 1564 | "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", 1565 | "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" 1566 | }, 1567 | "funding": [ 1568 | { 1569 | "url": "https://github.com/sebastianbergmann", 1570 | "type": "github" 1571 | } 1572 | ], 1573 | "time": "2020-11-28T06:42:11+00:00" 1574 | }, 1575 | { 1576 | "name": "sebastian/object-enumerator", 1577 | "version": "4.0.4", 1578 | "source": { 1579 | "type": "git", 1580 | "url": "https://github.com/sebastianbergmann/object-enumerator.git", 1581 | "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" 1582 | }, 1583 | "dist": { 1584 | "type": "zip", 1585 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", 1586 | "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", 1587 | "shasum": "" 1588 | }, 1589 | "require": { 1590 | "php": ">=7.3", 1591 | "sebastian/object-reflector": "^2.0", 1592 | "sebastian/recursion-context": "^4.0" 1593 | }, 1594 | "require-dev": { 1595 | "phpunit/phpunit": "^9.3" 1596 | }, 1597 | "type": "library", 1598 | "extra": { 1599 | "branch-alias": { 1600 | "dev-master": "4.0-dev" 1601 | } 1602 | }, 1603 | "autoload": { 1604 | "classmap": [ 1605 | "src/" 1606 | ] 1607 | }, 1608 | "notification-url": "https://packagist.org/downloads/", 1609 | "license": [ 1610 | "BSD-3-Clause" 1611 | ], 1612 | "authors": [ 1613 | { 1614 | "name": "Sebastian Bergmann", 1615 | "email": "sebastian@phpunit.de" 1616 | } 1617 | ], 1618 | "description": "Traverses array structures and object graphs to enumerate all referenced objects", 1619 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/", 1620 | "support": { 1621 | "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", 1622 | "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" 1623 | }, 1624 | "funding": [ 1625 | { 1626 | "url": "https://github.com/sebastianbergmann", 1627 | "type": "github" 1628 | } 1629 | ], 1630 | "time": "2020-10-26T13:12:34+00:00" 1631 | }, 1632 | { 1633 | "name": "sebastian/object-reflector", 1634 | "version": "2.0.4", 1635 | "source": { 1636 | "type": "git", 1637 | "url": "https://github.com/sebastianbergmann/object-reflector.git", 1638 | "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" 1639 | }, 1640 | "dist": { 1641 | "type": "zip", 1642 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", 1643 | "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", 1644 | "shasum": "" 1645 | }, 1646 | "require": { 1647 | "php": ">=7.3" 1648 | }, 1649 | "require-dev": { 1650 | "phpunit/phpunit": "^9.3" 1651 | }, 1652 | "type": "library", 1653 | "extra": { 1654 | "branch-alias": { 1655 | "dev-master": "2.0-dev" 1656 | } 1657 | }, 1658 | "autoload": { 1659 | "classmap": [ 1660 | "src/" 1661 | ] 1662 | }, 1663 | "notification-url": "https://packagist.org/downloads/", 1664 | "license": [ 1665 | "BSD-3-Clause" 1666 | ], 1667 | "authors": [ 1668 | { 1669 | "name": "Sebastian Bergmann", 1670 | "email": "sebastian@phpunit.de" 1671 | } 1672 | ], 1673 | "description": "Allows reflection of object attributes, including inherited and non-public ones", 1674 | "homepage": "https://github.com/sebastianbergmann/object-reflector/", 1675 | "support": { 1676 | "issues": "https://github.com/sebastianbergmann/object-reflector/issues", 1677 | "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" 1678 | }, 1679 | "funding": [ 1680 | { 1681 | "url": "https://github.com/sebastianbergmann", 1682 | "type": "github" 1683 | } 1684 | ], 1685 | "time": "2020-10-26T13:14:26+00:00" 1686 | }, 1687 | { 1688 | "name": "sebastian/recursion-context", 1689 | "version": "4.0.4", 1690 | "source": { 1691 | "type": "git", 1692 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1693 | "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" 1694 | }, 1695 | "dist": { 1696 | "type": "zip", 1697 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", 1698 | "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", 1699 | "shasum": "" 1700 | }, 1701 | "require": { 1702 | "php": ">=7.3" 1703 | }, 1704 | "require-dev": { 1705 | "phpunit/phpunit": "^9.3" 1706 | }, 1707 | "type": "library", 1708 | "extra": { 1709 | "branch-alias": { 1710 | "dev-master": "4.0-dev" 1711 | } 1712 | }, 1713 | "autoload": { 1714 | "classmap": [ 1715 | "src/" 1716 | ] 1717 | }, 1718 | "notification-url": "https://packagist.org/downloads/", 1719 | "license": [ 1720 | "BSD-3-Clause" 1721 | ], 1722 | "authors": [ 1723 | { 1724 | "name": "Sebastian Bergmann", 1725 | "email": "sebastian@phpunit.de" 1726 | }, 1727 | { 1728 | "name": "Jeff Welch", 1729 | "email": "whatthejeff@gmail.com" 1730 | }, 1731 | { 1732 | "name": "Adam Harvey", 1733 | "email": "aharvey@php.net" 1734 | } 1735 | ], 1736 | "description": "Provides functionality to recursively process PHP variables", 1737 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context", 1738 | "support": { 1739 | "issues": "https://github.com/sebastianbergmann/recursion-context/issues", 1740 | "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" 1741 | }, 1742 | "funding": [ 1743 | { 1744 | "url": "https://github.com/sebastianbergmann", 1745 | "type": "github" 1746 | } 1747 | ], 1748 | "time": "2020-10-26T13:17:30+00:00" 1749 | }, 1750 | { 1751 | "name": "sebastian/resource-operations", 1752 | "version": "3.0.3", 1753 | "source": { 1754 | "type": "git", 1755 | "url": "https://github.com/sebastianbergmann/resource-operations.git", 1756 | "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" 1757 | }, 1758 | "dist": { 1759 | "type": "zip", 1760 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", 1761 | "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", 1762 | "shasum": "" 1763 | }, 1764 | "require": { 1765 | "php": ">=7.3" 1766 | }, 1767 | "require-dev": { 1768 | "phpunit/phpunit": "^9.0" 1769 | }, 1770 | "type": "library", 1771 | "extra": { 1772 | "branch-alias": { 1773 | "dev-master": "3.0-dev" 1774 | } 1775 | }, 1776 | "autoload": { 1777 | "classmap": [ 1778 | "src/" 1779 | ] 1780 | }, 1781 | "notification-url": "https://packagist.org/downloads/", 1782 | "license": [ 1783 | "BSD-3-Clause" 1784 | ], 1785 | "authors": [ 1786 | { 1787 | "name": "Sebastian Bergmann", 1788 | "email": "sebastian@phpunit.de" 1789 | } 1790 | ], 1791 | "description": "Provides a list of PHP built-in functions that operate on resources", 1792 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations", 1793 | "support": { 1794 | "issues": "https://github.com/sebastianbergmann/resource-operations/issues", 1795 | "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" 1796 | }, 1797 | "funding": [ 1798 | { 1799 | "url": "https://github.com/sebastianbergmann", 1800 | "type": "github" 1801 | } 1802 | ], 1803 | "time": "2020-09-28T06:45:17+00:00" 1804 | }, 1805 | { 1806 | "name": "sebastian/type", 1807 | "version": "2.3.1", 1808 | "source": { 1809 | "type": "git", 1810 | "url": "https://github.com/sebastianbergmann/type.git", 1811 | "reference": "81cd61ab7bbf2de744aba0ea61fae32f721df3d2" 1812 | }, 1813 | "dist": { 1814 | "type": "zip", 1815 | "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/81cd61ab7bbf2de744aba0ea61fae32f721df3d2", 1816 | "reference": "81cd61ab7bbf2de744aba0ea61fae32f721df3d2", 1817 | "shasum": "" 1818 | }, 1819 | "require": { 1820 | "php": ">=7.3" 1821 | }, 1822 | "require-dev": { 1823 | "phpunit/phpunit": "^9.3" 1824 | }, 1825 | "type": "library", 1826 | "extra": { 1827 | "branch-alias": { 1828 | "dev-master": "2.3-dev" 1829 | } 1830 | }, 1831 | "autoload": { 1832 | "classmap": [ 1833 | "src/" 1834 | ] 1835 | }, 1836 | "notification-url": "https://packagist.org/downloads/", 1837 | "license": [ 1838 | "BSD-3-Clause" 1839 | ], 1840 | "authors": [ 1841 | { 1842 | "name": "Sebastian Bergmann", 1843 | "email": "sebastian@phpunit.de", 1844 | "role": "lead" 1845 | } 1846 | ], 1847 | "description": "Collection of value objects that represent the types of the PHP type system", 1848 | "homepage": "https://github.com/sebastianbergmann/type", 1849 | "support": { 1850 | "issues": "https://github.com/sebastianbergmann/type/issues", 1851 | "source": "https://github.com/sebastianbergmann/type/tree/2.3.1" 1852 | }, 1853 | "funding": [ 1854 | { 1855 | "url": "https://github.com/sebastianbergmann", 1856 | "type": "github" 1857 | } 1858 | ], 1859 | "time": "2020-10-26T13:18:59+00:00" 1860 | }, 1861 | { 1862 | "name": "sebastian/version", 1863 | "version": "3.0.2", 1864 | "source": { 1865 | "type": "git", 1866 | "url": "https://github.com/sebastianbergmann/version.git", 1867 | "reference": "c6c1022351a901512170118436c764e473f6de8c" 1868 | }, 1869 | "dist": { 1870 | "type": "zip", 1871 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", 1872 | "reference": "c6c1022351a901512170118436c764e473f6de8c", 1873 | "shasum": "" 1874 | }, 1875 | "require": { 1876 | "php": ">=7.3" 1877 | }, 1878 | "type": "library", 1879 | "extra": { 1880 | "branch-alias": { 1881 | "dev-master": "3.0-dev" 1882 | } 1883 | }, 1884 | "autoload": { 1885 | "classmap": [ 1886 | "src/" 1887 | ] 1888 | }, 1889 | "notification-url": "https://packagist.org/downloads/", 1890 | "license": [ 1891 | "BSD-3-Clause" 1892 | ], 1893 | "authors": [ 1894 | { 1895 | "name": "Sebastian Bergmann", 1896 | "email": "sebastian@phpunit.de", 1897 | "role": "lead" 1898 | } 1899 | ], 1900 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 1901 | "homepage": "https://github.com/sebastianbergmann/version", 1902 | "support": { 1903 | "issues": "https://github.com/sebastianbergmann/version/issues", 1904 | "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" 1905 | }, 1906 | "funding": [ 1907 | { 1908 | "url": "https://github.com/sebastianbergmann", 1909 | "type": "github" 1910 | } 1911 | ], 1912 | "time": "2020-09-28T06:39:44+00:00" 1913 | }, 1914 | { 1915 | "name": "symfony/polyfill-ctype", 1916 | "version": "v1.22.0", 1917 | "source": { 1918 | "type": "git", 1919 | "url": "https://github.com/symfony/polyfill-ctype.git", 1920 | "reference": "c6c942b1ac76c82448322025e084cadc56048b4e" 1921 | }, 1922 | "dist": { 1923 | "type": "zip", 1924 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e", 1925 | "reference": "c6c942b1ac76c82448322025e084cadc56048b4e", 1926 | "shasum": "" 1927 | }, 1928 | "require": { 1929 | "php": ">=7.1" 1930 | }, 1931 | "suggest": { 1932 | "ext-ctype": "For best performance" 1933 | }, 1934 | "type": "library", 1935 | "extra": { 1936 | "branch-alias": { 1937 | "dev-main": "1.22-dev" 1938 | }, 1939 | "thanks": { 1940 | "name": "symfony/polyfill", 1941 | "url": "https://github.com/symfony/polyfill" 1942 | } 1943 | }, 1944 | "autoload": { 1945 | "psr-4": { 1946 | "Symfony\\Polyfill\\Ctype\\": "" 1947 | }, 1948 | "files": [ 1949 | "bootstrap.php" 1950 | ] 1951 | }, 1952 | "notification-url": "https://packagist.org/downloads/", 1953 | "license": [ 1954 | "MIT" 1955 | ], 1956 | "authors": [ 1957 | { 1958 | "name": "Gert de Pagter", 1959 | "email": "BackEndTea@gmail.com" 1960 | }, 1961 | { 1962 | "name": "Symfony Community", 1963 | "homepage": "https://symfony.com/contributors" 1964 | } 1965 | ], 1966 | "description": "Symfony polyfill for ctype functions", 1967 | "homepage": "https://symfony.com", 1968 | "keywords": [ 1969 | "compatibility", 1970 | "ctype", 1971 | "polyfill", 1972 | "portable" 1973 | ], 1974 | "support": { 1975 | "source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.0" 1976 | }, 1977 | "funding": [ 1978 | { 1979 | "url": "https://symfony.com/sponsor", 1980 | "type": "custom" 1981 | }, 1982 | { 1983 | "url": "https://github.com/fabpot", 1984 | "type": "github" 1985 | }, 1986 | { 1987 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1988 | "type": "tidelift" 1989 | } 1990 | ], 1991 | "time": "2021-01-07T16:49:33+00:00" 1992 | }, 1993 | { 1994 | "name": "theseer/tokenizer", 1995 | "version": "1.2.0", 1996 | "source": { 1997 | "type": "git", 1998 | "url": "https://github.com/theseer/tokenizer.git", 1999 | "reference": "75a63c33a8577608444246075ea0af0d052e452a" 2000 | }, 2001 | "dist": { 2002 | "type": "zip", 2003 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a", 2004 | "reference": "75a63c33a8577608444246075ea0af0d052e452a", 2005 | "shasum": "" 2006 | }, 2007 | "require": { 2008 | "ext-dom": "*", 2009 | "ext-tokenizer": "*", 2010 | "ext-xmlwriter": "*", 2011 | "php": "^7.2 || ^8.0" 2012 | }, 2013 | "type": "library", 2014 | "autoload": { 2015 | "classmap": [ 2016 | "src/" 2017 | ] 2018 | }, 2019 | "notification-url": "https://packagist.org/downloads/", 2020 | "license": [ 2021 | "BSD-3-Clause" 2022 | ], 2023 | "authors": [ 2024 | { 2025 | "name": "Arne Blankerts", 2026 | "email": "arne@blankerts.de", 2027 | "role": "Developer" 2028 | } 2029 | ], 2030 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", 2031 | "support": { 2032 | "issues": "https://github.com/theseer/tokenizer/issues", 2033 | "source": "https://github.com/theseer/tokenizer/tree/master" 2034 | }, 2035 | "funding": [ 2036 | { 2037 | "url": "https://github.com/theseer", 2038 | "type": "github" 2039 | } 2040 | ], 2041 | "time": "2020-07-12T23:59:07+00:00" 2042 | }, 2043 | { 2044 | "name": "webmozart/assert", 2045 | "version": "1.9.1", 2046 | "source": { 2047 | "type": "git", 2048 | "url": "https://github.com/webmozarts/assert.git", 2049 | "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" 2050 | }, 2051 | "dist": { 2052 | "type": "zip", 2053 | "url": "https://api.github.com/repos/webmozarts/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", 2054 | "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", 2055 | "shasum": "" 2056 | }, 2057 | "require": { 2058 | "php": "^5.3.3 || ^7.0 || ^8.0", 2059 | "symfony/polyfill-ctype": "^1.8" 2060 | }, 2061 | "conflict": { 2062 | "phpstan/phpstan": "<0.12.20", 2063 | "vimeo/psalm": "<3.9.1" 2064 | }, 2065 | "require-dev": { 2066 | "phpunit/phpunit": "^4.8.36 || ^7.5.13" 2067 | }, 2068 | "type": "library", 2069 | "autoload": { 2070 | "psr-4": { 2071 | "Webmozart\\Assert\\": "src/" 2072 | } 2073 | }, 2074 | "notification-url": "https://packagist.org/downloads/", 2075 | "license": [ 2076 | "MIT" 2077 | ], 2078 | "authors": [ 2079 | { 2080 | "name": "Bernhard Schussek", 2081 | "email": "bschussek@gmail.com" 2082 | } 2083 | ], 2084 | "description": "Assertions to validate method input/output with nice error messages.", 2085 | "keywords": [ 2086 | "assert", 2087 | "check", 2088 | "validate" 2089 | ], 2090 | "support": { 2091 | "issues": "https://github.com/webmozarts/assert/issues", 2092 | "source": "https://github.com/webmozarts/assert/tree/1.9.1" 2093 | }, 2094 | "time": "2020-07-08T17:02:28+00:00" 2095 | } 2096 | ], 2097 | "aliases": [], 2098 | "minimum-stability": "stable", 2099 | "stability-flags": [], 2100 | "prefer-stable": false, 2101 | "prefer-lowest": false, 2102 | "platform": { 2103 | "php": ">=5.3" 2104 | }, 2105 | "platform-dev": [], 2106 | "plugin-api-version": "2.0.0" 2107 | } 2108 | -------------------------------------------------------------------------------- /edit_link.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | - 4 | 5 |
6 |
7 | 8 |
Available placeholders: ${url}, ${permalink}, ${title}, ${tags}, ${description}, ${cw}
9 | 10 |
11 |
12 |
Preview
13 |
This is a just a simple preview, the result might be slightly different. No max length truncation will be done in this preview.
14 |
15 |
16 |
17 |
18 |
19 |
20 | ##max-length## 21 | ##tags-separator## 22 | ##is-note## 23 |
24 | -------------------------------------------------------------------------------- /shaarli2mastodon.css: -------------------------------------------------------------------------------- 1 | .toot-configure { 2 | padding: 15px; 3 | background: #f2f2f2; 4 | border: 1px solid #ddd; 5 | } 6 | .page-form .toot-configure { 7 | width: 90%; 8 | margin: auto; 9 | } 10 | 11 | .toot-hidden { 12 | display: none; 13 | } 14 | 15 | .toot-button { 16 | background: none; 17 | border: none; 18 | color: #4ea2df; 19 | } 20 | .toot-button:hover, 21 | .toot-button:focus { 22 | text-decoration: underline; 23 | } 24 | 25 | .toot-url { 26 | color: #4ea2df; 27 | } 28 | .toot-tag { 29 | color: #d9e1e8; 30 | } 31 | 32 | .toot-preview-wrapper { 33 | margin: 10px 0; 34 | } 35 | .toot-preview { 36 | max-width: 580px; 37 | margin: 5px 0; 38 | padding: 15px; 39 | border-radius: 5px; 40 | background-color: #282C37; 41 | color: #fff; 42 | text-align: left; 43 | } 44 | .toot-preview a { 45 | color: #4ea2df; 46 | } 47 | .toot-preview-cw-button { 48 | display: inline-block; 49 | margin-left: 5px; 50 | padding: 3px 5px; 51 | background: #606984; 52 | border-radius: 2px; 53 | border: 0; 54 | color: #282c37; 55 | font-weight: 700; 56 | font-size: 11px; 57 | padding: 0 6px; 58 | text-transform: uppercase; 59 | line-height: 20px; 60 | cursor: pointer; 61 | vertical-align: middle; 62 | } 63 | .toot-preview-cw { 64 | margin-top: 15px; 65 | } 66 | .toot-preview-has-cw-hidden .toot-preview-cw{ 67 | display: none; 68 | } 69 | -------------------------------------------------------------------------------- /shaarli2mastodon.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | var linkForms = document.querySelectorAll('[name="linkform"]'); 4 | 5 | linkForms.forEach(function (linkForm) { 6 | var privateInput = linkForm.querySelector('[name="lf_private"]'); 7 | 8 | var tootInput = linkForm.querySelector('[name="toot"]'); 9 | var tootButton = linkForm.querySelector('.toot-button'); 10 | var tootConfigure = linkForm.querySelector('.toot-configure'); 11 | var tootPreview = linkForm.querySelector('.toot-preview'); 12 | var reactiveFields = linkForm.querySelectorAll('input, textarea'); 13 | 14 | // Disables mastodon publication if private flag is selected. 15 | privateInput.addEventListener('click', function (event) { 16 | if (!tootInput) { 17 | return; 18 | } 19 | 20 | tootInput.disabled = privateInput.checked; 21 | }); 22 | 23 | // Toggles toot configuration panel. 24 | tootButton.addEventListener('click', function (event) { 25 | renderPreview(linkForm); 26 | tootConfigure.classList.toggle('toot-hidden'); 27 | }); 28 | 29 | tootPreview.addEventListener('click', function (event) { 30 | // Click on the "show more" button when using cw placeholder. 31 | if (event.target.className.indexOf('toot-preview-cw-button') >= 0) { 32 | var previewCw = linkForm.querySelector('.toot-preview-cw'); 33 | tootPreview.classList.toggle('toot-preview-has-cw-hidden'); 34 | 35 | if (tootPreview.className.indexOf('toot-preview-has-cw-hidden') >= 0) { 36 | event.target.innerText = 'show more'; 37 | } else { 38 | event.target.innerText = 'show less'; 39 | } 40 | } 41 | }); 42 | 43 | // Updates preview when something changes. 44 | var timeout; 45 | ['change', 'keyup'].forEach(function (event) { 46 | reactiveFields.forEach(function (field) { 47 | field.addEventListener(event, function () { 48 | clearTimeout(timeout); 49 | timeout = setTimeout(function () { 50 | renderPreview(linkForm); 51 | }, 500); 52 | }); 53 | }); 54 | }); 55 | }); 56 | 57 | var placeholders = [ 58 | 'url', 59 | 'permalink', 60 | 'title', 61 | 'tags', 62 | 'description', 63 | 'cw' 64 | ]; 65 | 66 | function renderPreview (linkForm) { 67 | var link = { 68 | 'url': linkForm.querySelector('[name="lf_url"]').value, 69 | 'permalink': '', 70 | 'title': linkForm.querySelector('[name="lf_title"]').value, 71 | 'description': linkForm.querySelector('[name="lf_description"]').value, 72 | 'tags': linkForm.querySelector('[name="lf_tags"]').value, 73 | 'cw': ' ' 74 | }; 75 | 76 | var format = linkForm.querySelector('[name="toot-format"]').value; 77 | var maxLength = linkForm.querySelector('.toot-parameter-max-length').innerText || 500; 78 | var tagsSeparator = linkForm.querySelector('.toot-parameter-tags-separator').innerText; 79 | var isNote = linkForm.querySelector('.toot-parameter-is-note').innerText === 'true'; 80 | 81 | if (isNote) { 82 | link.url = link.permalink; 83 | } 84 | 85 | var hasCw = format.indexOf('${cw}') >= 0; 86 | var isCwHidden = linkForm.querySelector('.toot-preview').className.indexOf('toot-preview-has-cw-hidden') >= 0; 87 | 88 | link['tags'] = tagify(link['tags'], tagsSeparator); 89 | link['description'] = link['description'].replace(/\n/g, '\\n'); 90 | 91 | var output = format; 92 | for (i in placeholders) { 93 | var placeholder = placeholders[i]; 94 | 95 | if (hasCw && placeholder === 'cw') { 96 | output = output.replace(new RegExp('\\$\\{' + placeholder + '\\}', 'g'), '
'); 97 | } 98 | 99 | output = output.replace(new RegExp('\\$\\{' + placeholder + '\\}', 'g'), escapeHtml(link[placeholder])); 100 | } 101 | 102 | if (hasCw) { 103 | output += '
'; 104 | } 105 | 106 | // output = output.replace(/((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+@)?[A-Za-z0-9\.\-]+|(?:www\.|[\-;:&=\+\$,\w]+@)[A-Za-z0-9\.\-]+)((?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?)/g, function (match, $1) { 107 | // var url = $1.replace(/^http[s]?:\/\//, ''); 108 | // if (url.length > 30) { 109 | // url = url.substring(0, 30) + '…'; 110 | // } 111 | // return '' + url + ''; 112 | // }); 113 | // output = output.replace(/(#[\d\w_]+)/g, '$1'); 114 | output = output.replace(/\\n/g, '
'); 115 | 116 | linkForm.querySelector('.toot-preview').innerHTML = output; 117 | } 118 | 119 | function tagify (tags, tagsSeparator) { 120 | var parts = tags.trim().split(tagsSeparator); 121 | var output = []; 122 | 123 | for (i in parts) { 124 | if (parts[i].length > 0) { 125 | output.push('#' + parts[i].replace(/[^0-9_\p{L}]/gu, '')); 126 | } 127 | } 128 | 129 | return output.join(' '); 130 | } 131 | 132 | function escapeHtml (text) { 133 | return text.replace(//g, '>').replace(/"/g, '"'); 134 | } 135 | 136 | })(); 137 | -------------------------------------------------------------------------------- /shaarli2mastodon.meta: -------------------------------------------------------------------------------- 1 | description="Automatically publish your Shaares to your Mastodon account." 2 | parameters="MASTODON_INSTANCE;MASTODON_APPTOKEN;MASTODON_TOOT_FORMAT;MASTODON_TOOT_MAX_LENGTH;MASTODON_TOOT_FORMAT" 3 | parameter.MASTODON_INSTANCE="Your Mastodon instance" 4 | parameter.MASTODON_APPTOKEN="Mastodon application token" 5 | parameter.MASTODON_TOOT_MAX_LENGTH="Maximum number of characters of a toot. Default is 500." 6 | parameter.MASTODON_TOOT_FORMAT="Placeholders: \${url} \${permalink} \${title} \${tags} \${description} \${cw}" 7 | -------------------------------------------------------------------------------- /shaarli2mastodon.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | 17 | use Shaarli\Config\ConfigManager; 18 | use Shaarli\Plugin\PluginManager; 19 | use Shaarli\Render\TemplatePage; 20 | 21 | require_once 'src/Toot.php'; 22 | require_once 'src/MastodonClient.php'; 23 | require_once 'src/Utils.php'; 24 | 25 | /** 26 | * The default toot format if none is specified. 27 | */ 28 | const TOOT_DEFAULT_FORMAT = '#Shaarli: ${title} ${url} ${tags}'; 29 | 30 | const DIRECTORY_PATH = __DIR__; 31 | 32 | /** 33 | * Init function: check settings, and set default format. 34 | * 35 | * @param ConfigManager $conf instance. 36 | * 37 | * @return array|void Error if config is not valid. 38 | */ 39 | function shaarli2mastodon_init ($conf) { 40 | $format = $conf->get('plugins.MASTODON_TOOT_FORMAT'); 41 | if (empty($format)) { 42 | $conf->set('plugins.MASTODON_TOOT_FORMAT', TOOT_DEFAULT_FORMAT); 43 | } 44 | 45 | if (!Utils::isConfigValid($conf)) { 46 | return array('Please set up your Mastodon parameters in plugin administration page.'); 47 | } 48 | } 49 | 50 | function hook_shaarli2mastodon_render_includes ($data) { 51 | if (in_array($data['_PAGE_'], [TemplatePage::EDIT_LINK, TemplatePage::EDIT_LINK_BATCH])) { 52 | $data['css_files'][] = PluginManager::$PLUGINS_PATH . '/shaarli2mastodon/shaarli2mastodon.css'; 53 | } 54 | 55 | return $data; 56 | } 57 | 58 | /** 59 | * Add the JS file: disable the toot button if the link is set to private. 60 | * 61 | * @param array $data New link values. 62 | * @param ConfigManager $conf instance. 63 | * 64 | * @return array $data with the JS file. 65 | */ 66 | function hook_shaarli2mastodon_render_footer ($data, $conf) { 67 | if (in_array($data['_PAGE_'], [TemplatePage::EDIT_LINK, TemplatePage::EDIT_LINK_BATCH])) { 68 | $data['js_files'][] = PluginManager::$PLUGINS_PATH . '/shaarli2mastodon/shaarli2mastodon.js'; 69 | } 70 | 71 | return $data; 72 | } 73 | 74 | /** 75 | * Hook save link: will automatically publish a tweet when a new public link is shaared. 76 | * 77 | * @param array $data New link values. 78 | * @param ConfigManager $conf instance. 79 | * 80 | * @return array $data not altered. 81 | */ 82 | function hook_shaarli2mastodon_save_link ($data, $conf) { 83 | // No toot without config, for private links, or on edit. 84 | if (!Utils::isConfigValid($conf) 85 | || $data['private'] 86 | || !isset($_POST['toot']) 87 | ) { 88 | return $data; 89 | } 90 | 91 | // We make sure not to alter data 92 | $link = array_merge(array(), $data); 93 | $tagsSeparator = $conf->get('general.tags_separator', ' '); 94 | $maxLength = intval($conf->get('plugins.MASTODON_TOOT_MAX_LENGTH')); 95 | 96 | $data['permalink'] = index_url($_SERVER) . 'shaare/' . $data['shorturl']; 97 | 98 | // If the link is a note, we use the permalink as the url. 99 | if(Utils::isLinkNote($data)){ 100 | $data['url'] = $data['permalink']; 101 | } 102 | 103 | $format = isset($_POST['toot-format']) ? $_POST['toot-format'] : $conf->get('plugins.MASTODON_TOOT_FORMAT', TOOT_DEFAULT_FORMAT); 104 | $toot = new Toot($data, $format, $tagsSeparator, $maxLength); 105 | $mastodonInstance = $conf->get('plugins.MASTODON_INSTANCE', false); 106 | $appToken = $conf->get('plugins.MASTODON_APPTOKEN', false); 107 | 108 | $mastodonClient = new MastodonClient($mastodonInstance, $appToken); 109 | $response = $mastodonClient->postStatus($toot); 110 | 111 | // If an error has occurred, not blocking: just log it. 112 | if (isset($response['error'])) { 113 | error_log('Mastodon API error: '. $response['error']); 114 | 115 | if (session_status() == PHP_SESSION_ACTIVE) { 116 | $_SESSION['errors'][] = 'Something went wrong when publishing the link on Mastodon. ' . $response['error']; 117 | } 118 | } 119 | 120 | return $link; 121 | } 122 | 123 | /** 124 | * Hook render_editlink: add a checkbox to toot the new link or not. 125 | * 126 | * @param array $data New link values. 127 | * @param ConfigManager $conf instance. 128 | * 129 | * @return array $data with `edit_link_plugin` placeholder filled. 130 | */ 131 | function hook_shaarli2mastodon_render_editlink ($data, $conf) { 132 | if (!Utils::isConfigValid($conf)) { 133 | return $data; 134 | } 135 | 136 | $private = $conf->get('privacy.default_private_links', false); 137 | $checked = $data['link_is_new'] && !$private; 138 | 139 | $html = file_get_contents(DIRECTORY_PATH . '/edit_link.html'); 140 | 141 | $html = str_replace([ 142 | '##checked##', 143 | '##toot-format##', 144 | '##id##', 145 | '##max-length##', 146 | '##tags-separator##', 147 | '##is-note##', 148 | ], [ 149 | $checked ? 'checked="checked"' : '', 150 | $conf->get('plugins.MASTODON_TOOT_FORMAT', TOOT_DEFAULT_FORMAT), 151 | uniqid(), 152 | $conf->get('plugins.MASTODON_TOOT_MAX_LENGTH'), 153 | $conf->get('general.tags_separator', ' '), 154 | Utils::isLinkNote($data['link']) ? 'true' : 'false', 155 | ], $html); 156 | 157 | $data['edit_link_plugin'][] = $html; 158 | 159 | return $data; 160 | } 161 | -------------------------------------------------------------------------------- /src/HttpRequest.php: -------------------------------------------------------------------------------- 1 | domainURL = 'https://' . $domain . '/'; 39 | $this->apiURL = 'api/v1/'; 40 | } 41 | 42 | /** 43 | * Do a POST http request and return response. 44 | * 45 | * For URL, domain will be automatically added. 46 | * Array headers and params will be automatically encoded for request. 47 | * 48 | * If response is JSON format, it will be decoded before return 49 | * 50 | * @param string $url 51 | * @param array $headers 52 | * @param array $params 53 | * @return mixed 54 | */ 55 | public function post ($url, $headers = [], $params = []) { 56 | return $this->request( 57 | 'POST', 58 | $url, 59 | $headers, 60 | $params 61 | ); 62 | } 63 | 64 | /** 65 | * Do a GET http request and return response. 66 | * 67 | * For URL, domain will be automatically added. 68 | * Array headers and params will be automatically encoded for request. 69 | * 70 | * If response is JSON format, it will be decoded before return 71 | * 72 | * @param string $url 73 | * @param array $headers 74 | * @param array $params 75 | * @return mixed 76 | */ 77 | public function get ($url, $headers = [],$params = []) { 78 | return $this->request( 79 | 'GET', 80 | $url, 81 | $headers, 82 | $params 83 | ); 84 | } 85 | 86 | /** 87 | * Do a http request and return response. 88 | * 89 | * For URL, domain will be automatically added. 90 | * Array headers and params will be automatically encoded for request. 91 | * 92 | * If response is JSON format, it will be decoded before return 93 | * 94 | * @param string $method POST or GET 95 | * @param string $url 96 | * @param array $headers 97 | * @param array $params 98 | * @return mixed 99 | */ 100 | protected function request ($method, $url, $headers = [], $params = []) { 101 | $curl = curl_init(); 102 | 103 | curl_setopt_array($curl, $this->getOpts($method, $url, $headers, $params)); 104 | 105 | $response = curl_exec($curl); 106 | 107 | if (curl_error($curl) !== '') { 108 | die( 'Error: "' . curl_error($curl) . '" - Code: ' . curl_errno($curl)); 109 | } 110 | curl_close($curl); 111 | 112 | if ($response !== false) { 113 | $json = json_decode($response, true); 114 | } 115 | else { 116 | return false; 117 | } 118 | 119 | return ($json !== null) ? $json : $response ; 120 | } 121 | 122 | /** 123 | * Encode Parameters before HTTP request 124 | * 125 | * @param array $params 126 | * @return string 127 | */ 128 | protected function encodeParameters ($params) { 129 | if (is_array($params) && count($params) > 0) { 130 | // Many parameters, encode them 131 | $paramsString = ''; 132 | foreach ($params as $key => $value) { 133 | $paramsString .= '&' . urlencode($key) . '=' . urlencode($value); 134 | } 135 | // Remove first '&' 136 | return substr($paramsString, 1); 137 | } elseif ($params) { 138 | // return original 139 | return $params; 140 | } 141 | } 142 | 143 | /** 144 | * Encode Headers before HTTP request 145 | * 146 | * @param array $headers 147 | * @return string 148 | */ 149 | protected function encodeHeaders ($headers) { 150 | if (is_array($headers) && count($headers) > 0) { 151 | // Many headers, encode them 152 | $headersString = ''; 153 | foreach ($headers as $key => $value) { 154 | $headersString .= "{$key}: {$value}\r\n"; 155 | } 156 | // Return trimmed string 157 | return trim($headersString); 158 | } 159 | return null; 160 | } 161 | 162 | /** 163 | * Get Options to create the cURL Opts 164 | * 165 | * @param string $method POST or GET 166 | * @param string $url URL 167 | * @param array $headers 168 | * @param array $params 169 | * @return array 170 | */ 171 | protected function getOpts ($method, $url, $headers, $params) { 172 | if (isset($headers['Authorization'])) { 173 | $params['access_token'] = str_replace('Bearer ', '', $headers['Authorization']); 174 | } 175 | if ($method === 'GET' && !empty($params)) { 176 | $url .= '?' . $this->encodeParameters($params); 177 | $opts = []; 178 | } 179 | else { 180 | $fields = is_array($params) ? http_build_query($params) : $params; 181 | $opts = [ 182 | CURLOPT_POST => true, 183 | CURLOPT_POSTFIELDS => $fields 184 | ]; 185 | } 186 | $defaultsOpts = [ 187 | CURLOPT_SSL_VERIFYPEER => false, 188 | CURLOPT_HTTPHEADER => $headers, 189 | CURLOPT_URL => $this->domainURL . $url, 190 | CURLOPT_RETURNTRANSFER => true, 191 | CURLOPT_TIMEOUT => 30, 192 | CURLOPT_CONNECTTIMEOUT => 30 193 | ]; 194 | 195 | return $defaultsOpts + $opts; 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/MastodonClient.php: -------------------------------------------------------------------------------- 1 | 'application/json; charset=utf-8', 25 | 'Accept' => '*/*' 26 | ]; 27 | 28 | /** 29 | * Credentials to use Mastodon API 30 | * @var array 31 | */ 32 | protected $appCredentials = []; 33 | 34 | /** 35 | * Setting Domain, like 'mastodon.social' 36 | * @param string $domain 37 | */ 38 | public function __construct($domain, $token) { 39 | $this->domain = $domain; 40 | 41 | $this->http = new HttpRequest($this->domain); 42 | 43 | $this->appCredentials['bearer'] = $token; 44 | $this->headers['Authorization'] = $token; 45 | } 46 | 47 | /** 48 | * Post a new status 49 | * 50 | * Post a new status in Mastodon instance 51 | * 52 | * Return entire status as an array 53 | * 54 | * @param string $content Toot content 55 | * @param string $visibility Toot visibility (optionnal) 56 | * Values are : 57 | * - public 58 | * - unlisted 59 | * - private 60 | * - direct 61 | * @param array $medias Medias IDs 62 | * @return array 63 | */ 64 | public function postStatus (Toot $toot) { 65 | $body = [ 66 | 'visibility' => 'public' 67 | ]; 68 | 69 | if ($toot->hasContentWarning()) { 70 | $body = array_merge($body, [ 71 | 'status' => $toot->getContentWarningText(), 72 | 'spoiler_text' => $toot->getMainText() 73 | ]); 74 | } else { 75 | $body['status'] = $toot->getText(); 76 | } 77 | 78 | return $this->http->post( 79 | $this->http->apiURL . 'statuses', 80 | $this->headers, 81 | $body 82 | ); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Toot.php: -------------------------------------------------------------------------------- 1 | link = $link; 26 | $this->format = $format; 27 | $this->maxLength = empty($maxLength) ? self::MASTODON_DEFAULT_MAX_LENGTH : $maxLength; 28 | $this->tagDelimiter = $tagDelimiter; 29 | } 30 | 31 | public function __clone () { 32 | $linkClone = array_merge(array(), $this->link); 33 | return new self($linkClone, $this->format, $this->tagDelimiter, $this->maxLength); 34 | } 35 | 36 | public function getFullText () { 37 | $output = $this->format; 38 | foreach (self::TOOT_ALLOWED_PLACEHOLDERS as $placeholder) { 39 | $output = str_replace('${' . $placeholder . '}', $this->link[$placeholder], $output); 40 | } 41 | 42 | return htmlspecialchars_decode(str_replace('\n', "\n", $output)); 43 | } 44 | 45 | public function getText () { 46 | $shrinkedLink = $this->getShrinkedLink(); 47 | $output = $this->format; 48 | 49 | foreach (self::TOOT_ALLOWED_PLACEHOLDERS as $placeholder) { 50 | $output = str_replace('${' . $placeholder . '}', $shrinkedLink[$placeholder], $output); 51 | } 52 | 53 | return htmlspecialchars_decode(str_replace('\n', "\n", $output)); 54 | } 55 | 56 | public function getMainText () { 57 | $shrinkedLink = $this->getShrinkedLink(); 58 | $output = ''; 59 | $parts = explode('${cw}', $this->format); 60 | 61 | if (count($parts) >= 1) { 62 | $output = $parts[0]; 63 | } 64 | 65 | foreach (self::TOOT_ALLOWED_PLACEHOLDERS as $placeholder) { 66 | $output = str_replace('${' . $placeholder . '}', $shrinkedLink[$placeholder], $output); 67 | } 68 | 69 | return htmlspecialchars_decode(str_replace('\n', "\n", $output)); 70 | } 71 | 72 | public function getContentWarningText () { 73 | $shrinkedLink = $this->getShrinkedLink(); 74 | $output = ''; 75 | $parts = explode('${cw}', $this->format); 76 | 77 | if (count($parts) >= 2) { 78 | $output = $parts[1]; 79 | } else { 80 | return ''; 81 | } 82 | 83 | foreach (self::TOOT_ALLOWED_PLACEHOLDERS as $placeholder) { 84 | $output = str_replace('${' . $placeholder . '}', $shrinkedLink[$placeholder], $output); 85 | } 86 | 87 | return str_replace('\n', "\n", $output); 88 | } 89 | 90 | /** 91 | * Return shrinked link. 92 | * @return array Shrinked link object. 93 | */ 94 | public function getShrinkedLink (): array { 95 | // Clone the link. 96 | $link = array_merge(array(), $this->link); 97 | 98 | $length = $this->getFullLength(); 99 | 100 | if ($length < $this->maxLength) { 101 | return $link; 102 | } 103 | 104 | $descriptionLength = self::getMastodonLength($link['description']); 105 | $lengthExcess = $descriptionLength > self::EXCESS_MARGIN ? 106 | $length - $this->maxLength + self::EXCESS_MARGIN : 107 | $length - $this->maxLength; 108 | 109 | if ($descriptionLength > $lengthExcess) { 110 | // Truncate description to reach the right size. 111 | $link['description'] = mb_substr($link['description'], 0, $descriptionLength - $lengthExcess) . '…'; 112 | return $link; 113 | } else { 114 | // Truncating description is not enough, we need to truncate title. 115 | $link['description'] = ''; 116 | 117 | $length = $this->getFullLength(); 118 | 119 | $titleLength = self::getMastodonLength($link['title']); 120 | $lengthExcess = $titleLength > self::EXCESS_MARGIN ? 121 | $length - $this->maxLength + self::EXCESS_MARGIN : 122 | $length - $this->maxLength; 123 | 124 | if ($titleLength > $lengthExcess) { 125 | $link['title'] = mb_substr($link['title'], 0, $titleLength - $lengthExcess) . '…'; 126 | } else { 127 | $link['title'] = ''; 128 | } 129 | } 130 | 131 | return $link; 132 | } 133 | 134 | public function getFullLength (): int { 135 | $rawOutput = $this->getFullText(); 136 | 137 | // Replaces URLs by the right number of characters. 138 | return self::getMastodonLength($rawOutput); 139 | } 140 | 141 | public function getLength (): int { 142 | return self::getMastodonLength($this->getText()); 143 | } 144 | 145 | public function hasContentWarning (): bool { 146 | return strstr($this->format, '${cw}') !== false; 147 | } 148 | 149 | // --- 150 | 151 | public function getLink (): array { 152 | return $this->link; 153 | } 154 | 155 | public function setLink ($link): void { 156 | $this->link = $link; 157 | } 158 | 159 | public function setFormat ($format): void { 160 | $this->format = $format; 161 | } 162 | 163 | public function setMaxLength ($maxLength): void { 164 | $this->maxLength = $maxLength; 165 | } 166 | 167 | public function withLink ($key, $value): Toot { 168 | $clone = clone $this; 169 | 170 | $link = $clone->getLink(); 171 | $link[$key] = $value; 172 | $clone->setLink($link); 173 | 174 | return $clone; 175 | } 176 | 177 | public function withFormat ($format): Toot { 178 | $clone = clone $this; 179 | $clone->setFormat($format); 180 | return $clone; 181 | } 182 | 183 | public function withMaxLength (int $maxLength): Toot { 184 | $clone = clone $this; 185 | $clone->setMaxLength($maxLength); 186 | return $clone; 187 | } 188 | 189 | // --- 190 | 191 | public static function getMastodonLength ($string) { 192 | $result = preg_replace('/http[s]?:\/\/[^ \\n\\t\\r]*/', str_repeat('X', self::MASTODON_URL_LENGTH), $string); 193 | return mb_strlen($result); 194 | } 195 | 196 | public static function tagify ($tagsStr, $tagDelimiter){ 197 | // Regex inspired by https://gist.github.com/janogarcia/3946583 198 | // TODO validate real hashtag rules 199 | // - only UTF-8 characters plus underscore 200 | // - must not contain only numbers. At least one alpha character or underscore 201 | if (empty($tagsStr)) { 202 | return ''; 203 | } 204 | 205 | $tags = explode($tagDelimiter, $tagsStr); 206 | 207 | $result = []; 208 | 209 | foreach ($tags as $tag) { 210 | array_push($result, '#' . preg_replace('/[^0-9_\p{L}]/u', '', $tag)); 211 | } 212 | 213 | return implode(' ', $result); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/Utils.php: -------------------------------------------------------------------------------- 1 | get('plugins.'. $value); 18 | if (empty($setting)) { 19 | return false; 20 | } 21 | } 22 | return true; 23 | } 24 | 25 | /** 26 | * Determines if the link is a note. 27 | * @param array $link The link to check. 28 | * @return boolean Whether the link is a note or not. 29 | */ 30 | public static function isLinkNote ($link) { 31 | return !preg_match('/^http[s]?:/', $link['url']); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/TootTest.php: -------------------------------------------------------------------------------- 1 | 'https://github.com/kalvn/shaarli2mastodon', 16 | 'permalink' => 'https://links.kalvn.net/shaare/MmawMw', 17 | 'title' => 'A nice text', 18 | 'description' => "The quick, brown fox jumps over a lazy dog. DJs flock by when MTV ax quiz prog. Junk MTV quiz graced by fox whelps. Bawds jog, flick quartz, vex nymphs. Waltz, bad nymph, for quick jigs vex! Fox nymphs grab quick-jived waltz. Brick quiz whangs jumpy veldt fox. Bright vixens jump; dozy fowl quack. Quick wafting zephyrs vex bold Jim. Quick zephyrs blow, vexing daft Jim. Sex-charged fop blew my junk TV quiz. How quickly daft jumping zebras vex. Two driven jocks help fax my big quiz. Quick, Baz, get", 19 | 'tags' => 'blind text' 20 | ], 21 | "start \${title}\n\${url}\n\n\${description}\n\${tags} — \${permalink} end", 22 | ' ' 23 | ); 24 | 25 | $this->defaultToot = $defaultToot; 26 | 27 | $this->toots['regular'] = $defaultToot; 28 | $this->toots['short'] = $defaultToot->withFormat('${title}'); 29 | $this->toots['cw'] = $defaultToot->withFormat("\${title}\n\${url}\${cw}\${description}\n\${tags} — \${permalink}"); 30 | } 31 | 32 | // ----------- 33 | 34 | public function testRegularToot (): void { 35 | $toot = $this->defaultToot; 36 | 37 | $this->assertEquals('start A nice text 38 | https://github.com/kalvn/shaarli2mastodon 39 | 40 | The quick, brown fox jumps over a lazy dog. DJs flock by when MTV ax quiz prog. Junk MTV quiz graced by fox whelps. Bawds jog, flick quartz, vex nymphs. Waltz, bad nymph, for quick jigs vex! Fox nymphs grab quick-jived waltz. Brick quiz whangs jumpy veldt fox. Bright vixens jump; dozy fowl quack. Quick wafting zephyrs vex bold Jim. Quick zephyrs blow, vexing daft Jim. Sex-charged fop bl… 41 | #blind #text — https://links.kalvn.net/shaare/MmawMw end', $toot->getText()); 42 | $this->assertEquals('', $toot->getContentWarningText()); 43 | $this->assertEquals(476, $toot->getLength()); 44 | $this->assertEquals(586, $toot->getFullLength()); 45 | } 46 | 47 | public function testVeryLongTitle (): void { 48 | $toot = $this->defaultToot 49 | ->withLink('title', 'The quick, brown fox jumps over a lazy dog. DJs flock by when MTV ax quiz prog. Junk MTV quiz graced by fox whelps. Bawds jog, flick quartz, vex nymphs. Waltz, bad nymph, for quick jigs vex! Fox nymphs grab quick-jived waltz. Brick quiz whangs jumpy veldt fox. Bright vixens jump; dozy fowl quack. Quick wafting zephyrs vex bold Jim'); 50 | 51 | $this->assertEquals('start The quick, brown fox jumps over a lazy dog. DJs flock by when MTV ax quiz prog. Junk MTV quiz graced by fox whelps. Bawds jog, flick quartz, vex nymphs. Waltz, bad nymph, for quick jigs vex! Fox nymphs grab quick-jived waltz. Brick quiz whangs jumpy veldt fox. Bright vixens jump; dozy fowl quack. Quick wafting zephyrs vex bold Jim 52 | https://github.com/kalvn/shaarli2mastodon 53 | 54 | The quick, brown fox jumps over a lazy dog. DJs flock by when MTV ax… 55 | #blind #text — https://links.kalvn.net/shaare/MmawMw end', $toot->getText()); 56 | $this->assertEquals('', $toot->getContentWarningText()); 57 | $this->assertEquals(476, $toot->getLength()); 58 | $this->assertEquals(907, $toot->getFullLength()); 59 | } 60 | 61 | public function testShortMaxLengthWithContentWarning (): void { 62 | $toot = $this->defaultToot 63 | ->withFormat("\${title}\n\${url}\${cw}\${description}\n\${tags} — \${permalink}") 64 | ->withMaxLength(100); 65 | 66 | $this->assertEquals(' 67 | https://github.com/kalvn/shaarli2mastodon', $toot->getMainText()); 68 | $this->assertEquals(' 69 | #blind #text — https://links.kalvn.net/shaare/MmawMw', $toot->getContentWarningText()); 70 | $this->assertEquals(64, $toot->getLength()); 71 | $this->assertTrue($toot->hasContentWarning()); 72 | } 73 | 74 | public function testGetMastodonLength (): void { 75 | $this->assertEquals(60, Toot::getMastodonLength('This is a nice toot! URL: https://kalvn.net - goodbye.')); 76 | $this->assertEquals(41, Toot::getMastodonLength('This is a nice toot! #foo #bar - goodbye.')); 77 | } 78 | 79 | public function testTagify (): void { 80 | $this->assertEquals('#test #dev', Toot::tagify('test dev', ' ')); 81 | $this->assertEquals('#testdev', Toot::tagify('test dev', ',')); 82 | $this->assertEquals('#thisisaforbiddentag', Toot::tagify('this-is-a-forbidden-tag', ' ')); 83 | $this->assertEquals('#This #is #EV3N_worse', Toot::tagify('This is $EV3N_worse!', ' ')); 84 | $this->assertEquals('#ThisisEV3N #worse', Toot::tagify('This is $EV3N_worse!', '_')); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /tests/UtilsTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(Utils::isLinkNote([ 13 | 'url' => '/shaare/i6lwMw' 14 | ])); 15 | 16 | $this->assertFalse(Utils::isLinkNote([ 17 | 'url' => 'https://github.com/kalvn/shaarli2mastodon' 18 | ])); 19 | } 20 | } 21 | --------------------------------------------------------------------------------