/src/C4/Library/Session/Configuration/StandardConfiguration.php

https://bitbucket.org/paulscott56/c4-new · PHP · 680 lines · 335 code · 62 blank · 283 comment · 43 complexity · 5ccc343a6fb28936ebd675f1d8457076 MD5 · raw file

  1. <?php
  2. namespace C4\Library\Session\Configuration;
  3. use C4\Library\Session\Configuration\ConfigurationInterface as Configurable,
  4. C4\Library\Session\Exception,
  5. C4\Library\Validator\Hostname as HostnameValidator,
  6. C4\Library\Filter\Word\CamelCaseToUnderscore as CamelCaseToUnderscoreFilter;
  7. class StandardConfiguration implements Configurable
  8. {
  9. /**
  10. * @var C4\Library\Filter Filter to convert CamelCase to underscore_separated
  11. */
  12. protected $camelCaseToUnderscoreFilter;
  13. /**
  14. * @var string session.cookie_domain
  15. */
  16. protected $cookieDomain;
  17. /**
  18. * @var bool session.cookie_httponly
  19. */
  20. protected $cookieHTTPOnly;
  21. /**
  22. * @var int session.cookie_lifetime
  23. */
  24. protected $cookieLifetime;
  25. /**
  26. * @var string session.cookie_path
  27. */
  28. protected $cookiePath;
  29. /**
  30. * @var bool session.cookie_secure
  31. */
  32. protected $cookieSecure;
  33. /**
  34. * @var string session.name
  35. */
  36. protected $name;
  37. /**
  38. * @var array All options
  39. */
  40. protected $options = array();
  41. /**
  42. * @var int remember_me_seconds
  43. */
  44. protected $rememberMeSeconds;
  45. /**
  46. * @var string session.save_path
  47. */
  48. protected $savePath;
  49. /**
  50. * @var bool session.use_cookies
  51. */
  52. protected $useCookies;
  53. /**
  54. * Set storage option in backend configuration store
  55. *
  56. * Does nothing in this implementation; others might use it to set things
  57. * such as INI settings.
  58. *
  59. * @param string $storageName
  60. * @param mixed $storageValue
  61. * @return StandardConfiguration
  62. */
  63. public function setStorageOption($storageName, $storageValue)
  64. {
  65. }
  66. /**
  67. * Retrieve a storage option from a backend configuration store
  68. *
  69. * Used to retrieve default values from a backend configuration store.
  70. *
  71. * @param string $storageOption
  72. * @return mixed
  73. */
  74. public function getStorageOption($storageOption)
  75. {
  76. return null;
  77. }
  78. /**
  79. * Set session.save_path
  80. *
  81. * @param string $savePath
  82. * @return StandardConfiguration
  83. * @throws Exception\InvalidArgumentException on invalid path
  84. */
  85. public function setSavePath($savePath)
  86. {
  87. if (!is_dir($savePath)) {
  88. throw new Exception\InvalidArgumentException('Invalid save_path provided');
  89. }
  90. $this->savePath = $savePath;
  91. $this->setStorageOption('save_path', $savePath);
  92. return $this;
  93. }
  94. /**
  95. * Set session.save_path
  96. *
  97. * @return string|null
  98. */
  99. public function getSavePath()
  100. {
  101. if (null === $this->savePath) {
  102. $this->savePath = $this->getStorageOption('save_path');
  103. }
  104. return $this->savePath;
  105. }
  106. /**
  107. * Set session.name
  108. *
  109. * @param string $name
  110. * @return StandardConfiguration
  111. * @throws Exception\InvalidArgumentException
  112. */
  113. public function setName($name)
  114. {
  115. $this->name = (string) $name;
  116. if (empty($this->name)) {
  117. throw new Exception\InvalidArgumentException('Invalid session name; cannot be empty');
  118. }
  119. $this->setStorageOption('name', $this->name);
  120. return $this;
  121. }
  122. /**
  123. * Get session.name
  124. *
  125. * @return null|string
  126. */
  127. public function getName()
  128. {
  129. if (null === $this->name) {
  130. $this->name = $this->getStorageOption('name');
  131. }
  132. return $this->name;
  133. }
  134. /**
  135. * Set session.gc_probability
  136. *
  137. * @param int $gcProbability
  138. * @return StandardConfiguration
  139. * @throws Exception\InvalidArgumentException
  140. */
  141. public function setGcProbability($gcProbability)
  142. {
  143. if (!is_numeric($gcProbability)) {
  144. throw new Exception\InvalidArgumentException('Invalid gc_probability; must be numeric');
  145. }
  146. $gcProbability = (int) $gcProbability;
  147. if (1 > $gcProbability || 100 < $gcProbability) {
  148. throw new Exception\InvalidArgumentException('Invalid gc_probability; must be a percentage');
  149. }
  150. $this->setOption('gc_probability', $gcProbability);
  151. $this->setStorageOption('gc_probability', $gcProbability);
  152. return $this;
  153. }
  154. /**
  155. * Set session.gc_divisor
  156. *
  157. * @param int $gcDivisor
  158. * @return StandardConfiguration
  159. * @throws Exception\InvalidArgumentException
  160. */
  161. public function setGcDivisor($gcDivisor)
  162. {
  163. if (!is_numeric($gcDivisor)) {
  164. throw new Exception\InvalidArgumentException('Invalid gc_divisor; must be numeric');
  165. }
  166. $gcDivisor = (int) $gcDivisor;
  167. if (1 > $gcDivisor) {
  168. throw new Exception\InvalidArgumentException('Invalid gc_divisor; must be a positive integer');
  169. }
  170. $this->setOption('gc_divisor', $gcDivisor);
  171. $this->setStorageOption('gc_divisor', $gcDivisor);
  172. return $this;
  173. }
  174. /**
  175. * Set gc.maxlifetime
  176. *
  177. * @param int $gcMaxlifetime
  178. * @return StandardConfiguration
  179. * @throws Exception\InvalidArgumentException
  180. */
  181. public function setGcMaxlifetime($gcMaxlifetime)
  182. {
  183. if (!is_numeric($gcMaxlifetime)) {
  184. throw new Exception\InvalidArgumentException('Invalid gc_maxlifetime; must be numeric');
  185. }
  186. $gcMaxlifetime = (int) $gcMaxlifetime;
  187. if (1 > $gcMaxlifetime) {
  188. throw new Exception\InvalidArgumentException('Invalid gc_maxlifetime; must be a positive integer');
  189. }
  190. $this->setOption('gc_maxlifetime', $gcMaxlifetime);
  191. $this->setStorageOption('gc_maxlifetime', $gcMaxlifetime);
  192. return $this;
  193. }
  194. /**
  195. * Set session.cookie_lifetime
  196. *
  197. * @param int $cookieLifetime
  198. * @return StandardConfiguration
  199. * @throws Exception\InvalidArgumentException
  200. */
  201. public function setCookieLifetime($cookieLifetime)
  202. {
  203. if (!is_numeric($cookieLifetime)) {
  204. throw new Exception\InvalidArgumentException('Invalid cookie_lifetime; must be numeric');
  205. }
  206. if (0 > $cookieLifetime) {
  207. throw new Exception\InvalidArgumentException('Invalid cookie_lifetime; must be a positive integer or zero');
  208. }
  209. $this->cookieLifetime = (int) $cookieLifetime;
  210. $this->setStorageOption('cookie_lifetime', $this->cookieLifetime);
  211. return $this;
  212. }
  213. /**
  214. * Get session.cookie_lifetime
  215. *
  216. * @return int
  217. */
  218. public function getCookieLifetime()
  219. {
  220. if (null === $this->cookieLifetime) {
  221. $this->cookieLifetime = $this->getStorageOption('cookie_lifetime');
  222. }
  223. return $this->cookieLifetime;
  224. }
  225. /**
  226. * Set session.cookie_path
  227. *
  228. * @param string $cookiePath
  229. * @return StandardConfiguration
  230. * @throws Exception\InvalidArgumentException
  231. */
  232. public function setCookiePath($cookiePath)
  233. {
  234. $cookiePath = (string) $cookiePath;
  235. $test = parse_url($cookiePath, PHP_URL_PATH);
  236. if ($test != $cookiePath || '/' != $test[0]) {
  237. throw new Exception\InvalidArgumentException('Invalid cookie path');
  238. }
  239. $this->cookiePath = $cookiePath;
  240. $this->setStorageOption('cookie_path', $cookiePath);
  241. return $this;
  242. }
  243. /**
  244. * Get session.cookie_path
  245. *
  246. * @return string
  247. */
  248. public function getCookiePath()
  249. {
  250. if (null === $this->cookiePath) {
  251. $this->cookiePath = $this->getStorageOption('cookie_path');
  252. }
  253. return $this->cookiePath;
  254. }
  255. /**
  256. * Set session.cookie_domain
  257. *
  258. * @param string $cookieDomain
  259. * @return StandardConfiguration
  260. * @throws Exception\InvalidArgumentException
  261. */
  262. public function setCookieDomain($cookieDomain)
  263. {
  264. if (!is_string($cookieDomain)) {
  265. throw new Exception\InvalidArgumentException('Invalid cookie domain: must be a string');
  266. }
  267. //$validator = new HostnameValidator(HostnameValidator::ALLOW_ALL);
  268. //if (!empty($cookieDomain) && !$validator->isValid($cookieDomain)) {
  269. // throw new Exception\InvalidArgumentException('Invalid cookie domain: ' . implode('; ', $validator->getMessages()));
  270. //}
  271. $this->cookieDomain = $cookieDomain;
  272. $this->setStorageOption('cookie_domain', $cookieDomain);
  273. return $this;
  274. }
  275. /**
  276. * Get session.cookie_domain
  277. *
  278. * @return string
  279. */
  280. public function getCookieDomain()
  281. {
  282. if (null === $this->cookieDomain) {
  283. $this->cookieDomain = $this->getStorageOption('cookie_domain');
  284. }
  285. return $this->cookieDomain;
  286. }
  287. /**
  288. * Set session.cookie_secure
  289. *
  290. * @param bool $cookieSecure
  291. * @return StandardConfiguration
  292. */
  293. public function setCookieSecure($cookieSecure)
  294. {
  295. $this->cookieSecure = (bool) $cookieSecure;
  296. $this->setStorageOption('cookie_secure', $this->cookieSecure);
  297. return $this;
  298. }
  299. /**
  300. * Get session.cookie_secure
  301. *
  302. * @return bool
  303. */
  304. public function getCookieSecure()
  305. {
  306. if (null === $this->cookieSecure) {
  307. $this->cookieSecure = $this->getStorageOption('cookie_secure');
  308. }
  309. return $this->cookieSecure;
  310. }
  311. /**
  312. * Set session.cookie_httponly
  313. *
  314. * case sensitive method lookups in setOptions means this method has an
  315. * unusual casing
  316. *
  317. * @param bool $cookieHTTPOnly
  318. * @return StandardConfiguration
  319. */
  320. public function setCookieHttponly($cookieHTTPOnly)
  321. {
  322. $this->cookieHTTPOnly = (bool) $cookieHTTPOnly;
  323. $this->setStorageOption('cookie_httponly', $this->cookieHTTPOnly);
  324. return $this;
  325. }
  326. /**
  327. * Get session.cookie_httponly
  328. *
  329. * @return bool
  330. */
  331. public function getCookieHTTPOnly()
  332. {
  333. if (null === $this->cookieHTTPOnly) {
  334. $this->cookieHTTPOnly = $this->getStorageOption('cookie_httponly');
  335. }
  336. return $this->cookieHTTPOnly;
  337. }
  338. /**
  339. * Set session.use_cookies
  340. *
  341. * @param bool $useCookies
  342. * @return StandardConfiguration
  343. */
  344. public function setUseCookies($useCookies)
  345. {
  346. $this->useCookies = (bool) $useCookies;
  347. $this->setStorageOption('use_cookies', $this->useCookies);
  348. return $this;
  349. }
  350. /**
  351. * Get session.use_cookies
  352. *
  353. * @return bool
  354. */
  355. public function getUseCookies()
  356. {
  357. if (null === $this->useCookies) {
  358. $this->useCookies = $this->getStorageOption('use_cookies');
  359. }
  360. return $this->useCookies;
  361. }
  362. /**
  363. * Set session.entropy_file
  364. *
  365. * @param string $entropyFile
  366. * @return StandardConfiguration
  367. * @throws Exception\InvalidArgumentException
  368. */
  369. public function setEntropyFile($entropyFile)
  370. {
  371. if (is_dir($entropyFile) || !is_readable($entropyFile)) {
  372. throw new Exception\InvalidArgumentException('Invalid entropy_file provided');
  373. }
  374. $this->setOption('entropy_file', $entropyFile);
  375. $this->setStorageOption('entropy_file', $entropyFile);
  376. return $this;
  377. }
  378. /**
  379. * set session.entropy_length
  380. *
  381. * @param int $entropyLength
  382. * @return StandardConfiguration
  383. * @throws Exception\InvalidArgumentException
  384. */
  385. public function setEntropyLength($entropyLength)
  386. {
  387. if (!is_numeric($entropyLength)) {
  388. throw new Exception\InvalidArgumentException('Invalid entropy_length; must be numeric');
  389. }
  390. if (0 > $entropyLength) {
  391. throw new Exception\InvalidArgumentException('Invalid entropy_length; must be a positive integer or zero');
  392. }
  393. $this->setOption('entropy_length', $entropyLength);
  394. $this->setStorageOption('entropy_length', $entropyLength);
  395. return $this;
  396. }
  397. /**
  398. * Set session.cache_expire
  399. *
  400. * @param int $cacheExpire
  401. * @return StandardConfiguration
  402. * @throws Exception\InvalidArgumentException
  403. */
  404. public function setCacheExpire($cacheExpire)
  405. {
  406. if (!is_numeric($cacheExpire)) {
  407. throw new Exception\InvalidArgumentException('Invalid cache_expire; must be numeric');
  408. }
  409. $cacheExpire = (int) $cacheExpire;
  410. if (1 > $cacheExpire) {
  411. throw new Exception\InvalidArgumentException('Invalid cache_expire; must be a positive integer');
  412. }
  413. $this->setOption('cache_expire', $cacheExpire);
  414. $this->setStorageOption('cache_expire', $cacheExpire);
  415. return $this;
  416. }
  417. /**
  418. * Set session.hash_bits_per_character
  419. *
  420. * @param int $hashBitsPerCharacter
  421. * @return StandardConfiguration
  422. * @throws Exception\InvalidArgumentException
  423. */
  424. public function setHashBitsPerCharacter($hashBitsPerCharacter)
  425. {
  426. if (!is_numeric($hashBitsPerCharacter)) {
  427. throw new Exception\InvalidArgumentException('Invalid hash bits per character provided');
  428. }
  429. $hashBitsPerCharacter = (int) $hashBitsPerCharacter;
  430. $this->setOption('hash_bits_per_character', $hashBitsPerCharacter);
  431. $this->setStorageOption('hash_bits_per_character', $hashBitsPerCharacter);
  432. return $this;
  433. }
  434. /**
  435. * Set remember_me_seconds
  436. *
  437. * @param int $rememberMeSeconds
  438. * @return StandardConfiguration
  439. * @throws Exception\InvalidArgumentException
  440. */
  441. public function setRememberMeSeconds($rememberMeSeconds)
  442. {
  443. if (!is_numeric($rememberMeSeconds)) {
  444. throw new Exception\InvalidArgumentException('Invalid remember_me_seconds; must be numeric');
  445. }
  446. $rememberMeSeconds = (int) $rememberMeSeconds;
  447. if (1 > $rememberMeSeconds) {
  448. throw new Exception\InvalidArgumentException('Invalid remember_me_seconds; must be a positive integer');
  449. }
  450. $this->rememberMeSeconds = $rememberMeSeconds;
  451. $this->setStorageOption('remember_me_seconds', $rememberMeSeconds);
  452. return $this;
  453. }
  454. /**
  455. * Get remember_me_seconds
  456. *
  457. * @return int
  458. */
  459. public function getRememberMeSeconds()
  460. {
  461. if (null === $this->rememberMeSeconds) {
  462. $this->rememberMeSeconds = $this->getStorageOption('remember_me_seconds');
  463. }
  464. return $this->rememberMeSeconds;
  465. }
  466. /**
  467. * Set many options at once
  468. *
  469. * If a setter method exists for the key, that method will be called;
  470. * otherwise, a standard option will be set with the value provided via
  471. * {@link setOption()}.
  472. *
  473. * @param array $options
  474. * @return StandardConfiguration
  475. */
  476. public function setOptions(array $options)
  477. {
  478. $methods = get_class_methods($this);
  479. foreach ($options as $key => $value) {
  480. // translate key from underscore_separated to TitleCased
  481. $methodKey = str_replace(' ', '', ucwords(str_replace('_', ' ', $key)));
  482. $method = 'set' . $methodKey;
  483. if (in_array($method, $methods)) {
  484. $this->$method($value);
  485. } else {
  486. $this->setOption($key, $value);
  487. }
  488. }
  489. return $this;
  490. }
  491. /**
  492. * Set an individual option
  493. *
  494. * Keys are normalized to lowercase. After setting internally, calls
  495. * {@link setStorageOption()} to allow further processing.
  496. *
  497. *
  498. * @param string $option
  499. * @param mixed $value
  500. * @return StandardConfiguration
  501. */
  502. public function setOption($option, $value)
  503. {
  504. $option = $this->normalizeOption($option);
  505. $this->options[$option] = $value;
  506. $this->setStorageOption($option, $value);
  507. return $this;
  508. }
  509. /**
  510. * Get an individual option
  511. *
  512. * Keys are normalized to lowercase. If the option is not found, attempts
  513. * to retrieve it via {@link getStorageOption()}; if a value is returned
  514. * from that method, it will be set as the internal value and returned.
  515. *
  516. * Returns null for unfound options
  517. *
  518. * @param string $option
  519. * @return mixed
  520. */
  521. public function getOption($option)
  522. {
  523. $option = $this->normalizeOption($option);
  524. if (array_key_exists($option, $this->options)) {
  525. return $this->options[$option];
  526. }
  527. $value = $this->getStorageOption($option);
  528. if (null !== $value) {
  529. $this->setOption($option, $value);
  530. return $value;
  531. }
  532. return null;
  533. }
  534. /**
  535. * Check to see if an internal option has been set for the key provided.
  536. *
  537. * @param string $option
  538. * @return bool
  539. */
  540. public function hasOption($option)
  541. {
  542. $option = $this->normalizeOption($option);
  543. return array_key_exists($option, $this->options);
  544. }
  545. /**
  546. * Cast configuration to an array
  547. *
  548. * @return array
  549. */
  550. public function toArray()
  551. {
  552. $options = $this->options;
  553. $extraOpts = array(
  554. 'cookie_domain' => $this->getCookieDomain(),
  555. 'cookie_httponly' => $this->getCookieHTTPOnly(),
  556. 'cookie_lifetime' => $this->getCookieLifetime(),
  557. 'cookie_path' => $this->getCookiePath(),
  558. 'cookie_secure' => $this->getCookieSecure(),
  559. 'name' => $this->getName(),
  560. 'remember_me_seconds' => $this->getRememberMeSeconds(),
  561. 'save_path' => $this->getSavePath(),
  562. 'use_cookies' => $this->getUseCookies(),
  563. );
  564. return array_merge($options, $extraOpts);
  565. }
  566. /**
  567. * Intercept get*() and set*() methods
  568. *
  569. * Intercepts getters and setters and passes them to getOption() and setOption(),
  570. * respectively.
  571. *
  572. * @param string $method
  573. * @param array $args
  574. * @return mixed
  575. * @throws Exception\BadMethodCallException on non-getter/setter method
  576. */
  577. public function __call($method, $args)
  578. {
  579. if ('get' == substr($method, 0, 3)) {
  580. // Call to a getter; return matching option.
  581. // Transform method from MixedCase to underscore_separated.
  582. $option = substr($method, 3);
  583. $key = $this->getCamelCaseToUnderscoreFilter()->filter($option);
  584. return $this->getOption($key);
  585. }
  586. if ('set' == substr($method, 0, 3)) {
  587. // Call to a setter; return matching option.
  588. // Transform method from MixedCase to underscore_separated.
  589. $option = substr($method, 3);
  590. $key = $this->getCamelCaseToUnderscoreFilter()->filter($option);
  591. $value = array_shift($args);
  592. return $this->setOption($key, $value);
  593. }
  594. throw new Exception\BadMethodCallException(sprintf('Method "%s" does not exist', $method));
  595. }
  596. /**
  597. * Normalize an option name to lowercase
  598. *
  599. * @param string $option
  600. * @return string
  601. */
  602. protected function normalizeOption($option)
  603. {
  604. return strtolower((string) $option);
  605. }
  606. /**
  607. * Retrieve the CamelCaseToUnderscoreFilter
  608. *
  609. * @return CamelCaseToUnderscoreFilter
  610. */
  611. protected function getCamelCaseToUnderscoreFilter()
  612. {
  613. if (null === $this->camelCaseToUnderscoreFilter) {
  614. $this->camelCaseToUnderscoreFilter = new CamelCaseToUnderscoreFilter();
  615. }
  616. return $this->camelCaseToUnderscoreFilter;
  617. }
  618. }