1<?php 2/** 3 * Zend Framework (http://framework.zend.com/) 4 * 5 * @link http://github.com/zendframework/zf2 for the canonical source repository 6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 7 * @license http://framework.zend.com/license/new-bsd New BSD License 8 */ 9 10namespace Zend\Session\Config; 11 12use Traversable; 13use Zend\Session\Exception; 14use Zend\Validator\Hostname as HostnameValidator; 15 16/** 17 * Standard session configuration 18 */ 19class StandardConfig implements ConfigInterface 20{ 21 /** 22 * session.name 23 * 24 * @var string 25 */ 26 protected $name; 27 28 /** 29 * session.save_path 30 * 31 * @var string 32 */ 33 protected $savePath; 34 35 /** 36 * session.cookie_lifetime 37 * 38 * @var int 39 */ 40 protected $cookieLifetime; 41 42 /** 43 * session.cookie_path 44 * 45 * @var string 46 */ 47 protected $cookiePath; 48 49 /** 50 * session.cookie_domain 51 * 52 * @var string 53 */ 54 protected $cookieDomain; 55 56 /** 57 * session.cookie_secure 58 * 59 * @var bool 60 */ 61 protected $cookieSecure; 62 63 /** 64 * session.cookie_httponly 65 * 66 * @var bool 67 */ 68 protected $cookieHttpOnly; 69 70 /** 71 * remember_me_seconds 72 * 73 * @var int 74 */ 75 protected $rememberMeSeconds; 76 77 /** 78 * session.use_cookies 79 * 80 * @var bool 81 */ 82 protected $useCookies; 83 84 /** 85 * All options 86 * 87 * @var array 88 */ 89 protected $options = array(); 90 91 /** 92 * Set many options at once 93 * 94 * If a setter method exists for the key, that method will be called; 95 * otherwise, a standard option will be set with the value provided via 96 * {@link setOption()}. 97 * 98 * @param array|Traversable $options 99 * @return StandardConfig 100 * @throws Exception\InvalidArgumentException 101 */ 102 public function setOptions($options) 103 { 104 if (!is_array($options) && !$options instanceof Traversable) { 105 throw new Exception\InvalidArgumentException(sprintf( 106 'Parameter provided to %s must be an array or Traversable', 107 __METHOD__ 108 )); 109 } 110 111 foreach ($options as $key => $value) { 112 $setter = 'set' . str_replace(' ', '', ucwords(str_replace('_', ' ', $key))); 113 if (method_exists($this, $setter)) { 114 $this->{$setter}($value); 115 } else { 116 $this->setOption($key, $value); 117 } 118 } 119 return $this; 120 } 121 122 /** 123 * Get all options set 124 * 125 * @return array 126 */ 127 public function getOptions() 128 { 129 return $this->options; 130 } 131 132 /** 133 * Set an individual option 134 * 135 * Keys are normalized to lowercase. After setting internally, calls 136 * {@link setStorageOption()} to allow further processing. 137 * 138 * 139 * @param string $option 140 * @param mixed $value 141 * @return StandardConfig 142 */ 143 public function setOption($option, $value) 144 { 145 $option = strtolower($option); 146 $this->options[$option] = $value; 147 $this->setStorageOption($option, $value); 148 return $this; 149 } 150 151 /** 152 * Get an individual option 153 * 154 * Keys are normalized to lowercase. If the option is not found, attempts 155 * to retrieve it via {@link getStorageOption()}; if a value is returned 156 * from that method, it will be set as the internal value and returned. 157 * 158 * Returns null for unfound options 159 * 160 * @param string $option 161 * @return mixed 162 */ 163 public function getOption($option) 164 { 165 $option = strtolower($option); 166 if (array_key_exists($option, $this->options)) { 167 return $this->options[$option]; 168 } 169 170 $value = $this->getStorageOption($option); 171 if (null !== $value) { 172 $this->setOption($option, $value); 173 return $value; 174 } 175 176 return; 177 } 178 179 /** 180 * Check to see if an internal option has been set for the key provided. 181 * 182 * @param string $option 183 * @return bool 184 */ 185 public function hasOption($option) 186 { 187 $option = strtolower($option); 188 return array_key_exists($option, $this->options); 189 } 190 191 /** 192 * Set storage option in backend configuration store 193 * 194 * Does nothing in this implementation; others might use it to set things 195 * such as INI settings. 196 * 197 * @param string $storageName 198 * @param mixed $storageValue 199 * @return StandardConfig 200 */ 201 public function setStorageOption($storageName, $storageValue) 202 { 203 return $this; 204 } 205 206 /** 207 * Retrieve a storage option from a backend configuration store 208 * 209 * Used to retrieve default values from a backend configuration store. 210 * 211 * @param string $storageOption 212 * @return mixed 213 */ 214 public function getStorageOption($storageOption) 215 { 216 return; 217 } 218 219 /** 220 * Set session.save_path 221 * 222 * @param string $savePath 223 * @return StandardConfig 224 * @throws Exception\InvalidArgumentException on invalid path 225 */ 226 public function setSavePath($savePath) 227 { 228 if (!is_dir($savePath)) { 229 throw new Exception\InvalidArgumentException('Invalid save_path provided; not a directory'); 230 } 231 if (!is_writable($savePath)) { 232 throw new Exception\InvalidArgumentException('Invalid save_path provided; not writable'); 233 } 234 235 $this->savePath = $savePath; 236 $this->setStorageOption('save_path', $savePath); 237 return $this; 238 } 239 240 /** 241 * Set session.save_path 242 * 243 * @return string|null 244 */ 245 public function getSavePath() 246 { 247 if (null === $this->savePath) { 248 $this->savePath = $this->getStorageOption('save_path'); 249 } 250 return $this->savePath; 251 } 252 253 /** 254 * Set session.name 255 * 256 * @param string $name 257 * @return StandardConfig 258 * @throws Exception\InvalidArgumentException 259 */ 260 public function setName($name) 261 { 262 $this->name = (string) $name; 263 if (empty($this->name)) { 264 throw new Exception\InvalidArgumentException('Invalid session name; cannot be empty'); 265 } 266 $this->setStorageOption('name', $this->name); 267 return $this; 268 } 269 270 /** 271 * Get session.name 272 * 273 * @return null|string 274 */ 275 public function getName() 276 { 277 if (null === $this->name) { 278 $this->name = $this->getStorageOption('name'); 279 } 280 return $this->name; 281 } 282 283 /** 284 * Set session.gc_probability 285 * 286 * @param int $gcProbability 287 * @return StandardConfig 288 * @throws Exception\InvalidArgumentException 289 */ 290 public function setGcProbability($gcProbability) 291 { 292 if (!is_numeric($gcProbability)) { 293 throw new Exception\InvalidArgumentException('Invalid gc_probability; must be numeric'); 294 } 295 $gcProbability = (int) $gcProbability; 296 if (0 > $gcProbability || 100 < $gcProbability) { 297 throw new Exception\InvalidArgumentException('Invalid gc_probability; must be a percentage'); 298 } 299 $this->setOption('gc_probability', $gcProbability); 300 $this->setStorageOption('gc_probability', $gcProbability); 301 return $this; 302 } 303 304 /** 305 * Get session.gc_probability 306 * 307 * @return int 308 */ 309 public function getGcProbability() 310 { 311 if (!isset($this->options['gc_probability'])) { 312 $this->options['gc_probability'] = $this->getStorageOption('gc_probability'); 313 } 314 315 return $this->options['gc_probability']; 316 } 317 318 /** 319 * Set session.gc_divisor 320 * 321 * @param int $gcDivisor 322 * @return StandardConfig 323 * @throws Exception\InvalidArgumentException 324 */ 325 public function setGcDivisor($gcDivisor) 326 { 327 if (!is_numeric($gcDivisor)) { 328 throw new Exception\InvalidArgumentException('Invalid gc_divisor; must be numeric'); 329 } 330 $gcDivisor = (int) $gcDivisor; 331 if (1 > $gcDivisor) { 332 throw new Exception\InvalidArgumentException('Invalid gc_divisor; must be a positive integer'); 333 } 334 $this->setOption('gc_divisor', $gcDivisor); 335 $this->setStorageOption('gc_divisor', $gcDivisor); 336 return $this; 337 } 338 339 /** 340 * Get session.gc_divisor 341 * 342 * @return int 343 */ 344 public function getGcDivisor() 345 { 346 if (!isset($this->options['gc_divisor'])) { 347 $this->options['gc_divisor'] = $this->getStorageOption('gc_divisor'); 348 } 349 350 return $this->options['gc_divisor']; 351 } 352 353 /** 354 * Set gc_maxlifetime 355 * 356 * @param int $gcMaxlifetime 357 * @return StandardConfig 358 * @throws Exception\InvalidArgumentException 359 */ 360 public function setGcMaxlifetime($gcMaxlifetime) 361 { 362 if (!is_numeric($gcMaxlifetime)) { 363 throw new Exception\InvalidArgumentException('Invalid gc_maxlifetime; must be numeric'); 364 } 365 366 $gcMaxlifetime = (int) $gcMaxlifetime; 367 if (1 > $gcMaxlifetime) { 368 throw new Exception\InvalidArgumentException('Invalid gc_maxlifetime; must be a positive integer'); 369 } 370 371 $this->setOption('gc_maxlifetime', $gcMaxlifetime); 372 $this->setStorageOption('gc_maxlifetime', $gcMaxlifetime); 373 return $this; 374 } 375 376 /** 377 * Get session.gc_maxlifetime 378 * 379 * @return int 380 */ 381 public function getGcMaxlifetime() 382 { 383 if (!isset($this->options['gc_maxlifetime'])) { 384 $this->options['gc_maxlifetime'] = $this->getStorageOption('gc_maxlifetime'); 385 } 386 387 return $this->options['gc_maxlifetime']; 388 } 389 390 /** 391 * Set session.cookie_lifetime 392 * 393 * @param int $cookieLifetime 394 * @return StandardConfig 395 * @throws Exception\InvalidArgumentException 396 */ 397 public function setCookieLifetime($cookieLifetime) 398 { 399 if (!is_numeric($cookieLifetime)) { 400 throw new Exception\InvalidArgumentException('Invalid cookie_lifetime; must be numeric'); 401 } 402 if (0 > $cookieLifetime) { 403 throw new Exception\InvalidArgumentException( 404 'Invalid cookie_lifetime; must be a positive integer or zero' 405 ); 406 } 407 408 $this->cookieLifetime = (int) $cookieLifetime; 409 $this->setStorageOption('cookie_lifetime', $this->cookieLifetime); 410 return $this; 411 } 412 413 /** 414 * Get session.cookie_lifetime 415 * 416 * @return int 417 */ 418 public function getCookieLifetime() 419 { 420 if (null === $this->cookieLifetime) { 421 $this->cookieLifetime = $this->getStorageOption('cookie_lifetime'); 422 } 423 return $this->cookieLifetime; 424 } 425 426 /** 427 * Set session.cookie_path 428 * 429 * @param string $cookiePath 430 * @return StandardConfig 431 * @throws Exception\InvalidArgumentException 432 */ 433 public function setCookiePath($cookiePath) 434 { 435 $cookiePath = (string) $cookiePath; 436 437 $test = parse_url($cookiePath, PHP_URL_PATH); 438 if ($test != $cookiePath || '/' != $test[0]) { 439 throw new Exception\InvalidArgumentException('Invalid cookie path'); 440 } 441 442 $this->cookiePath = $cookiePath; 443 $this->setStorageOption('cookie_path', $cookiePath); 444 return $this; 445 } 446 447 /** 448 * Get session.cookie_path 449 * 450 * @return string 451 */ 452 public function getCookiePath() 453 { 454 if (null === $this->cookiePath) { 455 $this->cookiePath = $this->getStorageOption('cookie_path'); 456 } 457 return $this->cookiePath; 458 } 459 460 /** 461 * Set session.cookie_domain 462 * 463 * @param string $cookieDomain 464 * @return StandardConfig 465 * @throws Exception\InvalidArgumentException 466 */ 467 public function setCookieDomain($cookieDomain) 468 { 469 if (!is_string($cookieDomain)) { 470 throw new Exception\InvalidArgumentException('Invalid cookie domain: must be a string'); 471 } 472 473 $validator = new HostnameValidator(HostnameValidator::ALLOW_ALL); 474 475 if (!empty($cookieDomain) && !$validator->isValid($cookieDomain)) { 476 throw new Exception\InvalidArgumentException( 477 'Invalid cookie domain: ' . implode('; ', $validator->getMessages()) 478 ); 479 } 480 481 $this->cookieDomain = $cookieDomain; 482 $this->setStorageOption('cookie_domain', $cookieDomain); 483 return $this; 484 } 485 486 /** 487 * Get session.cookie_domain 488 * 489 * @return string 490 */ 491 public function getCookieDomain() 492 { 493 if (null === $this->cookieDomain) { 494 $this->cookieDomain = $this->getStorageOption('cookie_domain'); 495 } 496 return $this->cookieDomain; 497 } 498 499 /** 500 * Set session.cookie_secure 501 * 502 * @param bool $cookieSecure 503 * @return StandardConfig 504 */ 505 public function setCookieSecure($cookieSecure) 506 { 507 $this->cookieSecure = (bool) $cookieSecure; 508 $this->setStorageOption('cookie_secure', $this->cookieSecure); 509 return $this; 510 } 511 512 /** 513 * Get session.cookie_secure 514 * 515 * @return bool 516 */ 517 public function getCookieSecure() 518 { 519 if (null === $this->cookieSecure) { 520 $this->cookieSecure = $this->getStorageOption('cookie_secure'); 521 } 522 return $this->cookieSecure; 523 } 524 525 /** 526 * Set session.cookie_httponly 527 * 528 * case sensitive method lookups in setOptions means this method has an 529 * unusual casing 530 * 531 * @param bool $cookieHttpOnly 532 * @return StandardConfig 533 */ 534 public function setCookieHttpOnly($cookieHttpOnly) 535 { 536 $this->cookieHttpOnly = (bool) $cookieHttpOnly; 537 $this->setStorageOption('cookie_httponly', $this->cookieHttpOnly); 538 return $this; 539 } 540 541 /** 542 * Get session.cookie_httponly 543 * 544 * @return bool 545 */ 546 public function getCookieHttpOnly() 547 { 548 if (null === $this->cookieHttpOnly) { 549 $this->cookieHttpOnly = $this->getStorageOption('cookie_httponly'); 550 } 551 return $this->cookieHttpOnly; 552 } 553 554 /** 555 * Set session.use_cookies 556 * 557 * @param bool $useCookies 558 * @return StandardConfig 559 */ 560 public function setUseCookies($useCookies) 561 { 562 $this->useCookies = (bool) $useCookies; 563 $this->setStorageOption('use_cookies', $this->useCookies); 564 return $this; 565 } 566 567 /** 568 * Get session.use_cookies 569 * 570 * @return bool 571 */ 572 public function getUseCookies() 573 { 574 if (null === $this->useCookies) { 575 $this->useCookies = $this->getStorageOption('use_cookies'); 576 } 577 return $this->useCookies; 578 } 579 580 /** 581 * Set session.entropy_file 582 * 583 * @param string $entropyFile 584 * @return StandardConfig 585 * @throws Exception\InvalidArgumentException 586 */ 587 public function setEntropyFile($entropyFile) 588 { 589 if (!is_readable($entropyFile)) { 590 throw new Exception\InvalidArgumentException(sprintf( 591 "Invalid entropy_file provided: '%s'; doesn't exist or not readable", 592 $entropyFile 593 )); 594 } 595 596 $this->setOption('entropy_file', $entropyFile); 597 $this->setStorageOption('entropy_file', $entropyFile); 598 return $this; 599 } 600 601 /** 602 * Get session.entropy_file 603 * 604 * @return string 605 */ 606 public function getEntropyFile() 607 { 608 if (!isset($this->options['entropy_file'])) { 609 $this->options['entropy_file'] = $this->getStorageOption('entropy_file'); 610 } 611 612 return $this->options['entropy_file']; 613 } 614 615 /** 616 * set session.entropy_length 617 * 618 * @param int $entropyLength 619 * @return StandardConfig 620 * @throws Exception\InvalidArgumentException 621 */ 622 public function setEntropyLength($entropyLength) 623 { 624 if (!is_numeric($entropyLength)) { 625 throw new Exception\InvalidArgumentException('Invalid entropy_length; must be numeric'); 626 } 627 if (0 > $entropyLength) { 628 throw new Exception\InvalidArgumentException('Invalid entropy_length; must be a positive integer or zero'); 629 } 630 631 $this->setOption('entropy_length', $entropyLength); 632 $this->setStorageOption('entropy_length', $entropyLength); 633 return $this; 634 } 635 636 /** 637 * Get session.entropy_length 638 * 639 * @return string 640 */ 641 public function getEntropyLength() 642 { 643 if (!isset($this->options['entropy_length'])) { 644 $this->options['entropy_length'] = $this->getStorageOption('entropy_length'); 645 } 646 647 return $this->options['entropy_length']; 648 } 649 650 /** 651 * Set session.cache_expire 652 * 653 * @param int $cacheExpire 654 * @return StandardConfig 655 * @throws Exception\InvalidArgumentException 656 */ 657 public function setCacheExpire($cacheExpire) 658 { 659 if (!is_numeric($cacheExpire)) { 660 throw new Exception\InvalidArgumentException('Invalid cache_expire; must be numeric'); 661 } 662 663 $cacheExpire = (int) $cacheExpire; 664 if (1 > $cacheExpire) { 665 throw new Exception\InvalidArgumentException('Invalid cache_expire; must be a positive integer'); 666 } 667 668 $this->setOption('cache_expire', $cacheExpire); 669 $this->setStorageOption('cache_expire', $cacheExpire); 670 return $this; 671 } 672 673 /** 674 * Get session.cache_expire 675 * 676 * @return string 677 */ 678 public function getCacheExpire() 679 { 680 if (!isset($this->options['cache_expire'])) { 681 $this->options['cache_expire'] = $this->getStorageOption('cache_expire'); 682 } 683 684 return $this->options['cache_expire']; 685 } 686 687 /** 688 * Set session.hash_bits_per_character 689 * 690 * @param int $hashBitsPerCharacter 691 * @return StandardConfig 692 * @throws Exception\InvalidArgumentException 693 */ 694 public function setHashBitsPerCharacter($hashBitsPerCharacter) 695 { 696 if (!is_numeric($hashBitsPerCharacter)) { 697 throw new Exception\InvalidArgumentException('Invalid hash bits per character provided'); 698 } 699 $hashBitsPerCharacter = (int) $hashBitsPerCharacter; 700 $this->setOption('hash_bits_per_character', $hashBitsPerCharacter); 701 $this->setStorageOption('hash_bits_per_character', $hashBitsPerCharacter); 702 return $this; 703 } 704 705 /** 706 * Get session.hash_bits_per_character 707 * 708 * @return string 709 */ 710 public function getHashBitsPerCharacter() 711 { 712 if (!isset($this->options['hash_bits_per_character'])) { 713 $this->options['hash_bits_per_character'] = $this->getStorageOption('hash_bits_per_character'); 714 } 715 716 return $this->options['hash_bits_per_character']; 717 } 718 719 /** 720 * Set remember_me_seconds 721 * 722 * @param int $rememberMeSeconds 723 * @return StandardConfig 724 * @throws Exception\InvalidArgumentException 725 */ 726 public function setRememberMeSeconds($rememberMeSeconds) 727 { 728 if (!is_numeric($rememberMeSeconds)) { 729 throw new Exception\InvalidArgumentException('Invalid remember_me_seconds; must be numeric'); 730 } 731 732 $rememberMeSeconds = (int) $rememberMeSeconds; 733 if (1 > $rememberMeSeconds) { 734 throw new Exception\InvalidArgumentException('Invalid remember_me_seconds; must be a positive integer'); 735 } 736 737 $this->rememberMeSeconds = $rememberMeSeconds; 738 $this->setStorageOption('remember_me_seconds', $rememberMeSeconds); 739 return $this; 740 } 741 742 /** 743 * Get remember_me_seconds 744 * 745 * @return int 746 */ 747 public function getRememberMeSeconds() 748 { 749 if (null === $this->rememberMeSeconds) { 750 $this->rememberMeSeconds = $this->getStorageOption('remember_me_seconds'); 751 } 752 return $this->rememberMeSeconds; 753 } 754 755 /** 756 * Cast configuration to an array 757 * 758 * @return array 759 */ 760 public function toArray() 761 { 762 $extraOpts = array( 763 'cookie_domain' => $this->getCookieDomain(), 764 'cookie_httponly' => $this->getCookieHttpOnly(), 765 'cookie_lifetime' => $this->getCookieLifetime(), 766 'cookie_path' => $this->getCookiePath(), 767 'cookie_secure' => $this->getCookieSecure(), 768 'name' => $this->getName(), 769 'remember_me_seconds' => $this->getRememberMeSeconds(), 770 'save_path' => $this->getSavePath(), 771 'use_cookies' => $this->getUseCookies(), 772 ); 773 return array_merge($this->options, $extraOpts); 774 } 775 776 /** 777 * Intercept get*() and set*() methods 778 * 779 * Intercepts getters and setters and passes them to getOption() and setOption(), 780 * respectively. 781 * 782 * @param string $method 783 * @param array $args 784 * @return mixed 785 * @throws Exception\BadMethodCallException on non-getter/setter method 786 */ 787 public function __call($method, $args) 788 { 789 $prefix = substr($method, 0, 3); 790 $option = substr($method, 3); 791 $key = strtolower(preg_replace('#(?<=[a-z])([A-Z])#', '_\1', $option)); 792 793 if ($prefix === 'set') { 794 $value = array_shift($args); 795 return $this->setOption($key, $value); 796 } elseif ($prefix === 'get') { 797 return $this->getOption($key); 798 } else { 799 throw new Exception\BadMethodCallException(sprintf( 800 'Method "%s" does not exist in %s', 801 $method, 802 get_class($this) 803 )); 804 } 805 } 806} 807