├── composer.json ├── LICENSE ├── README.md └── lib └── URL.php /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | { 4 | "name": "TwicPics", 5 | "email": "hello@twic.pics", 6 | "homepage": "https://www.twicpics.com" 7 | } 8 | ], 9 | "autoload": { 10 | "psr-4": { 11 | "TwicPics\\": "lib/" 12 | } 13 | }, 14 | "description": "a library to build TwicPics URLs", 15 | "homepage": "https://github.com/TwicPics/php_url", 16 | "license": "MIT", 17 | "name": "twicpics/url", 18 | "readme": "README.md", 19 | "require": { 20 | "php": ">=5.6.0" 21 | }, 22 | "require-dev": { 23 | "php": ">=7.1.0", 24 | "php-coveralls/php-coveralls": "2.1.0", 25 | "phpunit/phpunit": "7.3.0", 26 | "squizlabs/php_codesniffer": "3.3.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 TwicPics 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 | # twicpics/url 2 | 3 | [![Packagist][packagist-image]][packagist-url] 4 | [![License][license-image]][license-url] 5 | 6 | [![Build Status][travis-image]][travis-url] 7 | [![Coverage Status][coveralls-image]][coveralls-url] 8 | [![Code Style][codestyle-image]][codestyle-url] 9 | 10 | `twicpics/url` provides a simple yet expressive fluent API to generate [TwicPics](https://www.twicpics.com) URLs. 11 | 12 | Here are some examples of what it can do: 13 | 14 | ```php 15 | $builder = new TwicPics\URL(); 16 | 17 | // Create a url in one pass 18 | $onePassUrl = $builder->cover("1:1")->resize(700)->src(SRC_URL)->url(); 19 | 20 | // Pre-crop an image then apply different transformations to it 21 | $precrop = $builder->src(SRC_URL)->focus("25p", "71p")->crop(560, 280); 22 | $squareUrl = $precrop->cover("1:1")->url(); 23 | $landscapeUrl = $precrop->cover("16:9")->url(); 24 | 25 | // Prepare manipulations to be applied to different sources 26 | $square = $builder->cover("1:1")->resize(300); 27 | $landscape = $builder->cover("1:1")->resize(300); 28 | 29 | $squaredUrl = $square->src(SRC_URL)->url(); 30 | $squaredPrecrop = $square->src($precrop)->url(); 31 | 32 | $landscapedUrl = $landscape->src(SRC_URL)->url(); 33 | $landscapedPrecrop = $landscape->src($precrop)->url(); 34 | ``` 35 | 36 | ## Installation 37 | 38 | Use composer: 39 | 40 | ``` 41 | php composer.phar require twicpics/url 42 | ``` 43 | 44 | ## Usage 45 | 46 | `twicpics/url` exports a single class (`TwicPics\URL`) that will be autoloaded. Just create an instance of this class and you're good to go: 47 | 48 | ```php 49 | // Get the builder 50 | $builder = new TwicPics\URL(); 51 | 52 | // Use the builder 53 | $myFirstUrl = $builder->src(MY_IMAGE_URL)->resize( 300 )->url(); 54 | ``` 55 | 56 | The builder's API is fluent and each method call returns a new immutable object. As such you can re-use an existing object and create a totally new and independent URL: 57 | 58 | ```php 59 | $authorizedAndSquared = $builder->auth(MY_TOKEN)->cover("1:1"); 60 | 61 | $url1 = $authorizedAndSquared->src(MY_IMAGE_URL_1)->url(); 62 | $url2 = $authorizedAndSquared->src(MY_IMAGE_URL_2)->url(); 63 | ``` 64 | 65 | Last, but not least, any builder object can be used as a source image by another builder object. So you can create generic manipulations to be applied on different, eventually pre-transformed, images: 66 | 67 | ```php 68 | $square500 = $builder->cover(500, 500); 69 | 70 | // Use authentication for an image I don't own 71 | $external = $builder->auth(MY_TOKEN)->src(URL_TO_IMAGE_I_DONT_OWN); 72 | 73 | // Precrop an image I own 74 | $precrop = $builder->src(URL_TO_IMAGE_I_OWN)->crop( 75 | [ 76 | "x" => 150, 77 | "y" => 256, 78 | "width" => 700, 79 | "height" => 889 80 | ] 81 | ); 82 | 83 | // square the image I don't own 84 | $square500->src(external)->url(); 85 | 86 | // square the image I own 87 | $square500->src(precop)->url(); 88 | ``` 89 | 90 | ## API 91 | 92 | ### auth 93 | 94 | _auth( AUTHENTICATION_TOKEN )_ 95 | 96 | Adds an authentication token. 97 | 98 | ```php 99 | $builder->auth("aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa"); 100 | ``` 101 | 102 | ### contain 103 | 104 | _contain( <expr> )_ 105 | 106 | _contain( <width> [, <height> ] )_ 107 | 108 | _contain( { width, height } )_ 109 | 110 | Adds a `contain` transformation. 111 | 112 | ```php 113 | // These four lines are strictly equivalent 114 | $builder->contain("500x400"); 115 | $builder->contain(500, 400); 116 | $builder->contain( 117 | [ 118 | "width" => 500, 119 | "height" => 400 120 | ] 121 | ); 122 | $builder->contain( 123 | json_decode( 124 | '{ 125 | "width": 500, 126 | "height": 400 127 | }' 128 | ) 129 | ); 130 | ``` 131 | 132 | ### containMax 133 | 134 | _containMax( <expr> )_ 135 | 136 | _containMax( <width> [, <height> ] )_ 137 | 138 | _containMax( { width, height } )_ 139 | 140 | Adds a `contain-max` transformation. 141 | 142 | ```php 143 | // These four lines are strictly equivalent 144 | $builder->containMax("500x400"); 145 | $builder->containMax(500, 400); 146 | $builder->containMax( 147 | [ 148 | "width" => 500, 149 | "height" => 400 150 | ] 151 | ); 152 | $builder->containMax( 153 | json_decode( 154 | '{ 155 | "width": 500, 156 | "height": 400 157 | }' 158 | ) 159 | ); 160 | ``` 161 | 162 | ### containMin 163 | 164 | _containMin( <expr> )_ 165 | 166 | _containMin( <width> [, <height> ] )_ 167 | 168 | _containMin( { width, height } )_ 169 | 170 | Adds a `contain-min` transformation. 171 | 172 | ```php 173 | // These four lines are strictly equivalent 174 | $builder->containMin("500x400"); 175 | $builder->containMin(500, 400); 176 | $builder->containMin( 177 | [ 178 | "width" => 500, 179 | "height" => 400 180 | ] 181 | ); 182 | $builder->containMin( 183 | json_decode( 184 | '{ 185 | "width": 500, 186 | "height": 400 187 | }' 188 | ) 189 | ); 190 | ``` 191 | 192 | ### cover 193 | 194 | _cover( <expr> )_ 195 | 196 | _cover( <width> [, <height> ] )_ 197 | 198 | _cover( { width, height } )_ 199 | 200 | Adds a `cover` transformation. 201 | 202 | ```php 203 | // These four lines are strictly equivalent 204 | $builder->cover("500x400"); 205 | $builder->cover(500, 400); 206 | $builder->cover( 207 | [ 208 | "width" => 500, 209 | "height" => 400 210 | ] 211 | ); 212 | $builder->cover( 213 | json_decode( 214 | '{ 215 | "width": 500, 216 | "height": 400 217 | }' 218 | ) 219 | ); 220 | ``` 221 | 222 | ### coverMax 223 | 224 | _coverMax( <expr> )_ 225 | 226 | _coverMax( <width> [, <height> ] )_ 227 | 228 | _coverMax( { width, height } )_ 229 | 230 | Adds a `cover-max` transformation. 231 | 232 | ```php 233 | // These four lines are strictly equivalent 234 | $builder->coverMax("500x400"); 235 | $builder->coverMax(500, 400); 236 | $builder->coverMax( 237 | [ 238 | "width" => 500, 239 | "height" => 400 240 | ] 241 | ); 242 | $builder->coverMax( 243 | json_decode( 244 | '{ 245 | "width": 500, 246 | "height": 400 247 | }' 248 | ) 249 | ); 250 | ``` 251 | 252 | ### coverMin 253 | 254 | _coverMin( <expr> )_ 255 | 256 | _coverMin( <width> [, <height> ] )_ 257 | 258 | _coverMin( { width, height } )_ 259 | 260 | Adds a `cover-min` transformation. 261 | 262 | ```php 263 | // These four lines are strictly equivalent 264 | $builder->coverMin("500x400"); 265 | $builder->coverMin(500, 400); 266 | $builder->coverMin( 267 | [ 268 | "width" => 500, 269 | "height" => 400 270 | ] 271 | ); 272 | $builder->coverMin( 273 | json_decode( 274 | '{ 275 | "width": 500, 276 | "height": 400 277 | }' 278 | ) 279 | ); 280 | ``` 281 | 282 | ### crop 283 | 284 | _crop( <expr> )_ 285 | 286 | _crop( <width>[, <height> [, <x> [, <y> ] ] ] )_ 287 | 288 | _crop( { x, y, width, height } )_ 289 | 290 | Adds a crop transformation. 291 | 292 | ```php 293 | // The following four lines create the same crop without origin 294 | $builder->crop("500x400"); 295 | $builder->crop(500, 400); 296 | $builder->crop( 297 | [ 298 | "width" => 500, 299 | "height" => 400 300 | ] 301 | ); 302 | $builder->crop( 303 | json_decode( 304 | '{ 305 | "width": 500, 306 | "height": 400 307 | }' 308 | ) 309 | ); 310 | 311 | // The following four lines create the same crop with origin 312 | $builder->crop("500x400@15x20"); 313 | $builder->crop(500, 400, 15, 20); 314 | $builder->crop( 315 | [ 316 | "x" => 15, 317 | "y" => 20, 318 | "width" => 500, 319 | "height" => 400 320 | ] 321 | ); 322 | $builder->crop( 323 | json_decode( 324 | '{ 325 | "x": 15, 326 | "y": 20, 327 | "width": 500, 328 | "height": 400 329 | }' 330 | ) 331 | ); 332 | ``` 333 | 334 | ### focus 335 | 336 | _focus( <expr> )_ 337 | 338 | _focus( <x> [, <y> ] )_ 339 | 340 | _focus( { x, y } )_ 341 | 342 | Sets the focus point. 343 | 344 | ```php 345 | // These four lines set the exact same focus point 346 | $builder->focus("67x987"); 347 | $builder->focus(67, 987); 348 | $builder->focus( 349 | [ 350 | "x" => 67, 351 | "y" => 987 352 | ] 353 | ); 354 | $builder->focus( 355 | json_decode( 356 | '{ 357 | "x": 67, 358 | "y": 987 359 | }' 360 | ) 361 | ); 362 | ``` 363 | 364 | ### format 365 | 366 | _format( <type> [, <quality> ] )_ 367 | 368 | _format( { type, quality } )_ 369 | 370 | Sets the image format. 371 | 372 | Accepted types are `"jpeg"`, `"png"` and `"webp"`. Only `jpeg` and `webp` accept a quality value. 373 | 374 | ```php 375 | $builder->format( "jpeg", 45 ); 376 | $builder->format( 377 | [ 378 | "type" => "jpeg", 379 | "quality" => 62 380 | ] 381 | ); 382 | $builder->format( "png" ); 383 | $builder->format( 384 | json_decode( 385 | '{ 386 | "type": "webp", 387 | "quality": 80, 388 | }' 389 | ) 390 | ); 391 | ``` 392 | 393 | ### jpeg 394 | 395 | _jpeg( [ <quality> ] )_ 396 | 397 | Shortcut for `format("jpeg", $quality)`. 398 | 399 | ### max 400 | 401 | _max( <expr> )_ 402 | 403 | _max( <width> [, <height> ] )_ 404 | 405 | _max( { width, height } )_ 406 | 407 | Adds a `max` transformation. 408 | 409 | ```php 410 | // These four lines are strictly equivalent 411 | $builder->max("500x400"); 412 | $builder->max(500, 400); 413 | $builder->max( 414 | [ 415 | "width" => 500, 416 | "height" => 400 417 | ] 418 | ); 419 | $builder->max( 420 | json_decode( 421 | '{ 422 | "width": 500, 423 | "height": 400 424 | }' 425 | ) 426 | ); 427 | ``` 428 | 429 | ### min 430 | 431 | _min( <expr> )_ 432 | 433 | _min( <width> [, <height> ] )_ 434 | 435 | _min( { width, height } )_ 436 | 437 | Adds a `min` transformation. 438 | 439 | ```php 440 | // These four lines are strictly equivalent 441 | $builder->min("500x400"); 442 | $builder->min(500, 400); 443 | $builder->min( 444 | [ 445 | "width" => 500, 446 | "height" => 400 447 | ] 448 | ); 449 | $builder->min( 450 | json_decode( 451 | '{ 452 | "width": 500, 453 | "height": 400 454 | }' 455 | ) 456 | ); 457 | ``` 458 | 459 | ### png 460 | 461 | _png()_ 462 | 463 | Shortcut for `format("png")`. 464 | 465 | ### resize 466 | 467 | _resize( <expr> )_ 468 | 469 | _resize( <width> [, <height> ] )_ 470 | 471 | _resize( { width, height } )_ 472 | 473 | Adds a `resize` transformation. 474 | 475 | ```php 476 | // These four lines are strictly equivalent 477 | $builder->resize("500x400"); 478 | $builder->resize(500, 400); 479 | $builder->resize( 480 | [ 481 | "width" => 500, 482 | "height" => 400 483 | ] 484 | ); 485 | $builder->resize( 486 | json_decode( 487 | '{ 488 | "width": 500, 489 | "height": 400 490 | }' 491 | ) 492 | ); 493 | ``` 494 | 495 | ### resizeMax 496 | 497 | _resizeMax( <expr> )_ 498 | 499 | _resizeMax( <width> [, <height> ] )_ 500 | 501 | _resizeMax( { width, height } )_ 502 | 503 | Adds a `resize-max` transformation. 504 | 505 | ```php 506 | // These four lines are strictly equivalent 507 | $builder->resizeMax("500x400"); 508 | $builder->resizeMax(500, 400); 509 | $builder->resizeMax( 510 | [ 511 | "width" => 500, 512 | "height" => 400 513 | ] 514 | ); 515 | $builder->resizeMax( 516 | json_decode( 517 | '{ 518 | "width": 500, 519 | "height": 400 520 | }' 521 | ) 522 | ); 523 | ``` 524 | 525 | ### resizeMin 526 | 527 | _resizeMin( <expr> )_ 528 | 529 | _resizeMin( <width> [, <height> ] )_ 530 | 531 | _resizeMin( { width, height } )_ 532 | 533 | Adds a `resize-min` transformation. 534 | 535 | ```php 536 | // These four lines are strictly equivalent 537 | $builder->resizeMin("500x400"); 538 | $builder->resizeMin(500, 400); 539 | $builder->resizeMin( 540 | [ 541 | "width" => 500, 542 | "height" => 400 543 | ] 544 | ); 545 | $builder->resizeMin( 546 | json_decode( 547 | '{ 548 | "width": 500, 549 | "height": 400 550 | }' 551 | ) 552 | ); 553 | ``` 554 | 555 | ### src 556 | 557 | _src( <url> )_ 558 | 559 | _src( <builder object> )_ 560 | 561 | Sets the source image on which the current manipulation has to be performed. 562 | 563 | If a URL is provided than it will be used as the master image to transform. 564 | 565 | ```php 566 | $builder->resize(300)->src(MY_IMAGE); // generated a 300 pixels-wide version of MY_IMAGE 567 | ``` 568 | 569 | If a builder object is provided than its source will be used as the new manipulation's source while its transformations will be prepended to the current ones. 570 | 571 | ```php 572 | $precrop = $builder->src(MY_IMAGE)->crop( 573 | [ 574 | "x": 150, 575 | "y": 256, 576 | "width": 700, 577 | "height": 889 578 | ] 579 | ); 580 | 581 | // This will first crop MY_IMAGE then apply a cover=500x500 582 | $builder->cover(500, 500)->src($precop); 583 | ``` 584 | 585 | ### step 586 | 587 | _step( <expr> )_ 588 | 589 | _step( <width> [, <height> ] )_ 590 | 591 | _step( { width, height } )_ 592 | 593 | Adds a `step` transformation. 594 | 595 | ```php 596 | // These four lines are strictly equivalent 597 | $builder->step("10x10"); 598 | $builder->step(10, 10); 599 | $builder->step( 600 | [ 601 | "width" => 10, 602 | "height" => 10 603 | ] 604 | ); 605 | $builder->step( 606 | json_decode( 607 | '{ 608 | "width": 10, 609 | "height": 10 610 | }' 611 | ) 612 | ); 613 | ``` 614 | 615 | ### \_\_toString 616 | 617 | _\_\_toString()_ 618 | 619 | Generates the URL as a string. Note that you must have provided an image URL using `.src()` prior to this call or an exception will be thrown. 620 | 621 | ```php 622 | $builder->__toString(); // throws an exception 623 | $builder->src(MY_IMAGE_URL)->__toString(); // works 624 | ``` 625 | 626 | ### url 627 | 628 | _url()_ 629 | 630 | Alias of `__toString`. 631 | 632 | ### webp 633 | 634 | _webp( [ <quality> ] )_ 635 | 636 | Shortcut for `format("webp", $quality)`. 637 | 638 | ## License 639 | 640 | Copyright (c) 2018 [TwicPics](mailto:hello@twic.pics) 641 | Licensed under the [MIT license][license-url]. 642 | 643 | 644 | 645 | [codestyle-image]: https://img.shields.io/badge/code%20style-PHPCS-brightgreen.svg?style=flat-square 646 | [codestyle-url]: https://github.com/squizlabs/PHP_CodeSniffer 647 | [coveralls-image]: https://img.shields.io/coveralls/TwicPics/php_url.svg?style=flat-square 648 | [coveralls-url]: https://coveralls.io/github/TwicPics/php_url 649 | [license-image]: https://poser.pugx.org/twicpics/url/license?format=flat-square 650 | [license-url]: https://raw.githubusercontent.com/TwicPics/php_url/master/LICENSE 651 | [packagist-image]: https://poser.pugx.org/twicpics/url/version?format=flat-square 652 | [packagist-url]: https://packagist.org/packages/twicpics/url 653 | [travis-image]: https://img.shields.io/travis/TwicPics/php_url.svg?style=flat-square 654 | [travis-url]: https://travis-ci.org/TwicPics/php_url 655 | -------------------------------------------------------------------------------- /lib/URL.php: -------------------------------------------------------------------------------- 1 | 10 | * @license https://raw.githubusercontent.com/TwicPics/php_url/master/LICENSE MIT 11 | * @link https://www.github.com/TwicPics/php_url 12 | */ 13 | 14 | namespace TwicPics; 15 | 16 | const MAIN_URL = "https://i.twic.pics/v1/"; 17 | const R_AUTHENT 18 | = "/^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}$/"; 19 | 20 | /** 21 | * URL class 22 | * 23 | * @category Main 24 | * @package TwicPics/URL 25 | * @author TwicPics 26 | * @license https://raw.githubusercontent.com/TwicPics/php_url/master/LICENSE MIT 27 | * @link https://www.github.com/TwicPics/php_url 28 | */ 29 | class URL 30 | { 31 | /** 32 | * Tests if a value is "undefined" (false or null) 33 | * 34 | * @param mixed $value the value to test 35 | * 36 | * @return boolean 37 | */ 38 | private static function _undefined( $value ) 39 | { 40 | return $value === false || $value === null; 41 | } 42 | 43 | /** 44 | * Creates a couple string (VxV) given two values 45 | * 46 | * @param mixed $v1 first value 47 | * @param mixed $v2 second value 48 | * 49 | * @return string 50 | */ 51 | private static function _couple( $v1, $v2 = null ) 52 | { 53 | $noV1 = URL::_undefined($v1); 54 | $noV2 = URL::_undefined($v2); 55 | if ($noV1 && $noV2) { 56 | return null; 57 | } 58 | return $noV1 ? 59 | "-x" . ( ( string ) $v2 ) : 60 | ( 61 | $noV2 ? 62 | ( ( string ) $v1 ) : 63 | ( ( string ) $v1 ) . "x" . ( ( string ) $v2 ) 64 | ); 65 | } 66 | 67 | /** 68 | * Handles varargs generically 69 | * 70 | * @param string $name name of the method (for error messages) 71 | * @param mixed[] $args array of arguments to handle 72 | * @param string[] $props name of properties for map argument 73 | * 74 | * @return mixed[] the normalized arguments 75 | */ 76 | private static function _getArgs( $name, $args, $props ) 77 | { 78 | $argsLength = count($args); 79 | $propsLength = count($props); 80 | if ($argsLength < 1 || $argsLength > $propsLength) { 81 | throw new \DomainException( 82 | "method " . $name . " requires 1 to " . 83 | ( ( string ) $propsLength ). " arguments" 84 | ); 85 | } 86 | if ($argsLength === 1) { 87 | $first = $args[ 0 ]; 88 | if (is_array($first)) { 89 | return array_map( 90 | function ( $prop ) use ( $first ) { 91 | return array_key_exists($prop, $first) ? 92 | $first[ $prop ] : 93 | null; 94 | }, 95 | $props 96 | ); 97 | } 98 | if (is_object($first)) { 99 | return array_map( 100 | function ( $prop ) use ( $first ) { 101 | return isset($first->$prop) ? 102 | $first->$prop : 103 | null; 104 | }, 105 | $props 106 | ); 107 | } 108 | } 109 | while ( count($args) < $propsLength ) { 110 | $args[] = null; 111 | } 112 | return $args; 113 | } 114 | 115 | private $_auth = null; 116 | private $_format = null; 117 | private $_manip = []; 118 | private $_src = null; 119 | 120 | /** 121 | * Converts to string URL 122 | * 123 | * @return string 124 | */ 125 | public function __toString() 126 | { 127 | if (URL::_undefined($this->_src)) { 128 | throw new \DomainException("cannot create url without a source"); 129 | } 130 | $auth = $this->_auth; 131 | $format = $this->_format; 132 | $manip = $this->_manip; 133 | return MAIN_URL . 134 | ( count($manip) ? join("/", $manip) . "/" : "") . 135 | ( $format !== null ? "format=" . $format . "/" : "" ) . 136 | ( $auth !== null ? "auth:" . $auth . "/" : "" ) . 137 | $this->_src; 138 | } 139 | 140 | /** 141 | * Creates a duplicate of the object 142 | * 143 | * @return URL 144 | */ 145 | private function _clone() 146 | { 147 | $clone = new URL(); 148 | $clone->_auth = $this->_auth; 149 | $clone->_format = $this->_format; 150 | $clone->_manip = $this->_manip; 151 | $clone->_src = $this->_src; 152 | return $clone; 153 | } 154 | 155 | /** 156 | * Creates a new URL object with an additional transformation 157 | * 158 | * @param mixed $name the name of the transformation 159 | * @param mixed $value the value of the transformation 160 | * 161 | * @return URL 162 | */ 163 | private function _transformation( $name, $value ) 164 | { 165 | $clone = $this->_clone(); 166 | $clone->_manip = array_merge( 167 | $clone->_manip, 168 | [ ( (string) $name ) . "=" . ( ( string ) $value ) ] 169 | ); 170 | return $clone; 171 | } 172 | 173 | /** 174 | * Creates a new URL object with an additional resize-like transformation 175 | * 176 | * @param string $methodName the name of the method (for error messages) 177 | * @param mixed $name the name of the transformation 178 | * @param mixed[] $args the args of the method 179 | * 180 | * @return URL 181 | */ 182 | private function _resizeTransformation( $methodName, $name, $args ) 183 | { 184 | $sizeString = URL::_couple( 185 | ...URL::_getArgs( 186 | $methodName, 187 | $args, 188 | [ "width", "height" ] 189 | ) 190 | ); 191 | if ($sizeString === null) { 192 | throw new \DomainException( 193 | $methodName . ": at least a width or a height is needed" 194 | ); 195 | } 196 | return $this->_transformation($name, $sizeString); 197 | } 198 | 199 | /** 200 | * Creates a new URL object with an authentication token 201 | * 202 | * @param string $token the authentication token 203 | * 204 | * @return URL 205 | */ 206 | public function auth( $token = null ) 207 | { 208 | if (!preg_match(R_AUTHENT, $token)) { 209 | throw new \DomainException("token " . $token . " is illformed"); 210 | } 211 | $clone = $this->_clone(); 212 | $clone->_auth = $token; 213 | return $clone; 214 | } 215 | 216 | /** 217 | * Creates a new URL object with an additional contain transformation 218 | * 219 | * @param mixed[] ...$args the arguments 220 | * 221 | * @return URL 222 | */ 223 | public function contain( ...$args ) 224 | { 225 | return $this->_resizeTransformation("contain", "contain", $args); 226 | } 227 | 228 | /** 229 | * Creates a new URL object with an additional contain-max transformation 230 | * 231 | * @param mixed[] ...$args the arguments 232 | * 233 | * @return URL 234 | */ 235 | public function containMax( ...$args ) 236 | { 237 | return $this->_resizeTransformation("containMax", "contain-max", $args); 238 | } 239 | 240 | /** 241 | * Creates a new URL object with an additional contain-min transformation 242 | * 243 | * @param mixed[] ...$args the arguments 244 | * 245 | * @return URL 246 | */ 247 | public function containMin( ...$args ) 248 | { 249 | return $this->_resizeTransformation("containMin", "contain-min", $args); 250 | } 251 | 252 | /** 253 | * Creates a new URL object with an additional cover transformation 254 | * 255 | * @param mixed[] ...$args the arguments 256 | * 257 | * @return URL 258 | */ 259 | public function cover( ...$args ) 260 | { 261 | return $this->_resizeTransformation("cover", "cover", $args); 262 | } 263 | 264 | /** 265 | * Creates a new URL object with an additional cover-max transformation 266 | * 267 | * @param mixed[] ...$args the arguments 268 | * 269 | * @return URL 270 | */ 271 | public function coverMax( ...$args ) 272 | { 273 | return $this->_resizeTransformation("coverMax", "cover-max", $args); 274 | } 275 | 276 | /** 277 | * Creates a new URL object with an additional cover-min transformation 278 | * 279 | * @param mixed[] ...$args the arguments 280 | * 281 | * @return URL 282 | */ 283 | public function coverMin( ...$args ) 284 | { 285 | return $this->_resizeTransformation("coverMin", "cover-min", $args); 286 | } 287 | 288 | /** 289 | * Creates a new URL object with an additional crop transformation 290 | * 291 | * @param mixed[] ...$args the arguments 292 | * 293 | * @return URL 294 | */ 295 | public function crop( ...$args ) 296 | { 297 | $args = URL::_getArgs( 298 | "crop", 299 | $args, 300 | [ "width", "height", "x", "y" ] 301 | ); 302 | $sizeString = URL::_couple($args[ 0 ], $args[ 1 ]); 303 | if ($sizeString === null) { 304 | throw new \DomainException( 305 | "crop: at least a width or a height is needed" 306 | ); 307 | } 308 | $coordString = URL::_couple($args[ 2 ], $args[ 3 ]); 309 | return $this->_transformation( 310 | "crop", 311 | $coordString === null ? 312 | $sizeString : 313 | $sizeString . "@" . $coordString 314 | ); 315 | } 316 | 317 | /** 318 | * Creates a new URL object with the given focus point 319 | * 320 | * @param mixed[] ...$args the arguments 321 | * 322 | * @return URL 323 | */ 324 | public function focus( ...$args ) 325 | { 326 | $coordString = URL::_couple( 327 | ...URL::_getArgs( 328 | "focus", 329 | $args, 330 | [ "x", "y" ] 331 | ) 332 | ); 333 | if ($coordString === null) { 334 | throw new \DomainException( 335 | "focus: at least a x-coord or a y-coord is needed" 336 | ); 337 | } 338 | return $this->_transformation("focus", $coordString); 339 | } 340 | 341 | private static $_formatQuality = [ 342 | "jpeg" => true, 343 | "png" => false, 344 | "webp" => true 345 | ]; 346 | 347 | /** 348 | * Creates a new URL object with a given format 349 | * 350 | * @param mixed[] ...$args the arguments 351 | * 352 | * @return URL 353 | */ 354 | public function format( ...$args ) 355 | { 356 | $args = URL::_getArgs( 357 | "format", 358 | $args, 359 | [ "type", "quality" ] 360 | ); 361 | $format = ( ( string ) $args[ 0 ] ); 362 | $quality = $args[ 1 ]; 363 | if (URL::_undefined($format)) { 364 | throw new \DomainException("format expected"); 365 | } 366 | if (!array_key_exists($format, URL::$_formatQuality)) { 367 | throw new \DomainException("unknow format '" . $format . "'"); 368 | } 369 | $noQuality = URL::_undefined($quality); 370 | if (!$noQuality && !URL::$_formatQuality[ $format ]) { 371 | throw new \DomainException( 372 | "format '". $format . 373 | "' does not support quality specifier" 374 | ); 375 | } 376 | $clone = $this->_clone(); 377 | $clone->_format 378 | = $noQuality ? 379 | $format : 380 | $format . "-" . ( ( string ) $quality ); 381 | return $clone; 382 | } 383 | 384 | /** 385 | * Creates a new URL object with the jpeg format 386 | * 387 | * @param mixed? $quality the quality 388 | * 389 | * @return URL 390 | */ 391 | public function jpeg( $quality = null ) 392 | { 393 | return $this->format("jpeg", $quality); 394 | } 395 | 396 | /** 397 | * Creates a new URL object with an additional max transformation 398 | * 399 | * @param mixed[] ...$args the arguments 400 | * 401 | * @return URL 402 | */ 403 | public function max( ...$args ) 404 | { 405 | return $this->_resizeTransformation("max", "max", $args); 406 | } 407 | 408 | /** 409 | * Creates a new URL object with an additional min transformation 410 | * 411 | * @param mixed[] ...$args the arguments 412 | * 413 | * @return URL 414 | */ 415 | public function min( ...$args ) 416 | { 417 | return $this->_resizeTransformation("min", "min", $args); 418 | } 419 | 420 | /** 421 | * Creates a new URL object with the png format 422 | * 423 | * @return URL 424 | */ 425 | public function png() 426 | { 427 | return $this->format("png"); 428 | } 429 | 430 | /** 431 | * Creates a new URL object with an additional resize transformation 432 | * 433 | * @param mixed[] ...$args the arguments 434 | * 435 | * @return URL 436 | */ 437 | public function resize( ...$args ) 438 | { 439 | return $this->_resizeTransformation("resize", "resize", $args); 440 | } 441 | 442 | /** 443 | * Creates a new URL object with an additional resize-max transformation 444 | * 445 | * @param mixed[] ...$args the arguments 446 | * 447 | * @return URL 448 | */ 449 | public function resizeMax( ...$args ) 450 | { 451 | return $this->_resizeTransformation("resizeMax", "resize-max", $args); 452 | } 453 | 454 | /** 455 | * Creates a new URL object with an additional resize-min transformation 456 | * 457 | * @param mixed[] ...$args the arguments 458 | * 459 | * @return URL 460 | */ 461 | public function resizeMin( ...$args ) 462 | { 463 | return $this->_resizeTransformation("resizeMin", "resize-min", $args); 464 | } 465 | 466 | /** 467 | * Creates a new URL with a new source 468 | * 469 | * @param mixed $src the source 470 | * 471 | * @return URL 472 | */ 473 | public function src( $src ) 474 | { 475 | $clone = $this->_clone(); 476 | if ($src instanceof URL) { 477 | $clone->_auth = $src->_auth !== null ? $src->_auth : $clone->_auth; 478 | if ($clone->_format === null) { 479 | $clone->_format = $src->_format; 480 | } 481 | $clone->_manip = array_merge($src->_manip, $clone->_manip); 482 | $clone->_src = $src->_src !== null ? $src->_src : $clone->_src; 483 | } else { 484 | $clone->_src = $src; 485 | } 486 | return $clone; 487 | } 488 | 489 | /** 490 | * Creates a new URL object with an additional step transformation 491 | * 492 | * @param mixed[] ...$args the arguments 493 | * 494 | * @return URL 495 | */ 496 | public function step( ...$args ) 497 | { 498 | return $this->_resizeTransformation("step", "step", $args); 499 | } 500 | 501 | /** 502 | * Converts to string URL(alias of __toString) 503 | * 504 | * @return string 505 | */ 506 | public function url() 507 | { 508 | return $this->__toString(); 509 | } 510 | 511 | /** 512 | * Creates a new URL object with the webp format 513 | * 514 | * @param mixed? $quality the quality 515 | * 516 | * @return URL 517 | */ 518 | public function webp( $quality = null ) 519 | { 520 | return $this->format("webp", $quality); 521 | } 522 | } 523 | --------------------------------------------------------------------------------