PageRenderTime 66ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 1ms

/src/Client.php

https://github.com/schibsted/sdk-php
PHP | 1492 lines | 816 code | 118 blank | 558 comment | 193 complexity | b1c9e4ca6cd639dbe6d6169ee3a77ea6 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /** @see VGS_Client_Exception */
  3. require_once dirname(__FILE__).'/Client/Exception.php';
  4. /**
  5. * SPiD PHP SDK
  6. */
  7. if (!function_exists('curl_init')) {
  8. throw new Exception('SPiD needs the CURL PHP extension.');
  9. }
  10. if (!function_exists('json_decode')) {
  11. throw new Exception('SPiD needs the JSON PHP extension.');
  12. }
  13. /**
  14. * Provides access to the SPiD Platform.
  15. */
  16. class VGS_Client {
  17. /**
  18. * Constructor key constants
  19. */
  20. const CLIENT_ID = 'client_id';
  21. const CLIENT_SECRET = 'client_secret';
  22. const CLIENT_SIGN_SECRET= 'sig_secret';
  23. const REDIRECT_URI = 'redirect_uri';
  24. const DOMAIN = 'domain';
  25. const COOKIE = 'cookie';
  26. const PRODUCTION = 'production';
  27. const DEBUG = 'debug';
  28. const HTTPS = 'https';
  29. const XITI = 'xiti';
  30. const PRODUCTION_DOMAIN = 'production_domain';
  31. const STAGING_DOMAIN = 'staging_domain';
  32. const API_VERSION = 'api_version';
  33. const CONTEXT_CLIENT = 'context_client_id';
  34. /**
  35. * SDK Version.
  36. */
  37. const VERSION = '2.4';
  38. /**
  39. * Oauth Token URL
  40. * @var string
  41. */
  42. const _VGS_OAUTH_TOKEN_URL = '/oauth/token';
  43. /**
  44. * Default staging server domain
  45. * @var string
  46. */
  47. const _VGS_DEFAULT_STAGING_DOMAIN = 'stage.payment.schibsted.no';
  48. /**
  49. * Default production server domain
  50. * @var string
  51. */
  52. const _VGS_DEFAULT_PRODUCTION_DOMAIN = 'payment.schibsted.no';
  53. /**
  54. * Default options for curl.
  55. */
  56. public static $CURL_OPTS = array(
  57. CURLOPT_CONNECTTIMEOUT => 5,
  58. CURLOPT_RETURNTRANSFER => true,
  59. CURLOPT_SSL_VERIFYPEER => false,
  60. CURLOPT_SSL_VERIFYHOST => false,
  61. CURLOPT_TIMEOUT => 30,
  62. CURLOPT_USERAGENT => 'spid-php-2.3'
  63. );
  64. /**
  65. * List of query parameters that get automatically dropped when rebuilding
  66. * the current URI.
  67. */
  68. protected static $DROP_QUERY_PARAMS = array(
  69. 'code',
  70. 'error',
  71. 'session',
  72. 'signed_request');
  73. /**
  74. * Contains the raw string result
  75. * @var string
  76. */
  77. public $raw;
  78. /**
  79. * Contains the full json decoded container result
  80. * @var array
  81. */
  82. public $container;
  83. /**
  84. * Debugging
  85. */
  86. protected $debug = false;
  87. protected $timer = array();
  88. protected $last_result_code = null;
  89. /**
  90. * HTTPS comunication ON/OFF
  91. */
  92. protected $https = true;
  93. /**
  94. * Production Server
  95. */
  96. protected $production = false;
  97. /**
  98. * Production Server domain
  99. */
  100. protected $production_domain = self::_VGS_DEFAULT_PRODUCTION_DOMAIN;
  101. /**
  102. * Staging Server domain
  103. */
  104. protected $staging_domain = self::_VGS_DEFAULT_STAGING_DOMAIN;
  105. /**
  106. * Use API Version
  107. */
  108. protected $api_version = null;
  109. /**
  110. * The Client ID.
  111. */
  112. protected $client_id;
  113. /**
  114. * The Client Secret.
  115. */
  116. protected $client_secret;
  117. /**
  118. * The Client Secret.
  119. */
  120. protected $client_sign_secret;
  121. /**
  122. * The Context Client ID.
  123. */
  124. protected $context_client_id;
  125. /**
  126. * The Redirect URI.
  127. */
  128. protected $redirect_uri;
  129. /**
  130. * The active user session, if one is available.
  131. */
  132. protected $session;
  133. /**
  134. * The data from the signed_request token.
  135. */
  136. protected $signedRequest;
  137. /**
  138. * Indicates that we already loaded the session as best as we could.
  139. */
  140. protected $sessionLoaded = false;
  141. /**
  142. * Indicates if Cookie support should be enabled.
  143. */
  144. protected $cookieSupport = false;
  145. /**
  146. * Base domain for the Cookie.
  147. */
  148. protected $baseDomain = '';
  149. /**
  150. * Access token (used in server 2 server calls)
  151. */
  152. protected $accessToken = false;
  153. /**
  154. * Refresh token (used in server 2 server calls)
  155. */
  156. protected $refreshToken = false;
  157. /**
  158. * Xiti configuration array
  159. * Used for tracking analytics between client and service
  160. */
  161. protected $xiti = array();
  162. /**
  163. * Indicates if the CURL based @ syntax for file uploads is enabled.
  164. */
  165. protected $fileUploadSupport = false;
  166. /**
  167. * URL Argument Separator used by http_build_query
  168. *
  169. * & or &amp;
  170. *
  171. * @var string;
  172. */
  173. public $argSeparator = '&amp;';
  174. public static $errors = array();
  175. /**
  176. * Time between last curl request and curl response
  177. *
  178. * @var int
  179. */
  180. protected $latency;
  181. /**
  182. * Initialize a SPiD Application.
  183. *
  184. * The configuration:
  185. * - client_id: the application ID
  186. * - client_secret: the application secret
  187. * - redirect_uri: the application redirect URI
  188. * - cookie: (optional) boolean true to enable cookie support
  189. * - domain: (optional) domain for the cookie
  190. * - api_version: (optional) which version of the API to make requests to
  191. * - debug: (optional) debugs all calls
  192. * - https: (optional) force https on/off (default on)
  193. * - file_upload: (optional) boolean indicating if file uploads are enabled
  194. *
  195. * @param Array $config the application configuration
  196. */
  197. public function __construct($config) {
  198. $this->debug = (isset($config['debug']) && $config['debug'] == true)?true:false;
  199. $this->setProduction((boolean) $config['production']);
  200. $this->https = (isset($config['https']) && $config['https'] == false)?false:true;
  201. if ($this->production) {
  202. $this->https = true; // always defaults to https on production
  203. }
  204. if (!empty($config['production_domain'])) {
  205. $this->production_domain = $config['production_domain'];
  206. }
  207. if (!empty($config['staging_domain'])) {
  208. $this->staging_domain = $config['staging_domain'];
  209. }
  210. if (!empty($config['api_version'])) {
  211. $this->api_version = $config['api_version'];
  212. }
  213. $this->setClientID($config['client_id']);
  214. $this->setClientSecret($config['client_secret']);
  215. if (!empty($config[static::CLIENT_SIGN_SECRET])) {
  216. $this->setClientSignSecret($config[static::CLIENT_SIGN_SECRET]);
  217. }
  218. $this->setRedirectUri($config['redirect_uri']);
  219. if (isset($config['cookie'])) {
  220. $this->setCookieSupport($config['cookie']);
  221. }
  222. if (isset($config['domain'])) {
  223. $this->setBaseDomain($config['domain']);
  224. }
  225. if (isset($config['file_upload'])) {
  226. $this->setFileUploadSupport($config['file_upload']);
  227. }
  228. if (isset($config['xiti'])) {
  229. $this->xiti = $config['xiti'];
  230. }
  231. if (isset($config[static::CONTEXT_CLIENT])) {
  232. $this->context_client_id = $config[static::CONTEXT_CLIENT];
  233. }
  234. }
  235. public function getDebugInfo() {
  236. $elapsed = array();
  237. if ($this->debug) {
  238. if (is_array($this->timer) && count($this->timer) > 0) {
  239. foreach ($this->timer as $function => $entries) {
  240. if (is_array($entries['elapsed']) && count($entries['elapsed']) > 0) {
  241. $return_vals = (array_sum($entries['elapsed']) / count($entries['elapsed']));
  242. } elseif (is_array($entries['elapsed']) && count($entries['elapsed']) == 1) {
  243. $return_vals = current($entries['elapsed']);
  244. } else {
  245. $return_vals = FALSE;
  246. }
  247. $elapsed[$function] = sprintf("%.4f", $return_vals)." seconds";
  248. }
  249. }
  250. }
  251. return array('Average' => $elapsed,'Times' => $this->timer);
  252. }
  253. public function encodeSerializedUrlVariable($var) {
  254. return strtr(base64_encode(addslashes(gzcompress(serialize($var),9))), '+/=', '-_,');
  255. }
  256. public function getServerURL() {
  257. if ($this->isLive()) {
  258. return (($this->https)?'https://':'http://').$this->production_domain;
  259. } else {
  260. return (($this->https)?'https://':'http://').$this->staging_domain;
  261. }
  262. }
  263. private function getBaseURL($name = 'www') {
  264. switch ($name) {
  265. case 'flow':
  266. return self::getServerURL() . '/flow/';
  267. break;
  268. case 'api':
  269. case 'api_read':
  270. return self::getServerURL() . '/api/' . ($this->api_version == null ? '' : $this->api_version . '/');
  271. break;
  272. case 'www':
  273. default:
  274. return self::getServerURL() . '/';
  275. break;
  276. }
  277. }
  278. private function getTokenURL() {
  279. return self::getServerURL().self::_VGS_OAUTH_TOKEN_URL;
  280. }
  281. /**
  282. * Set the Client ID.
  283. *
  284. * @param string $client_id the Client ID
  285. */
  286. public function setClientID($client_id) {
  287. $this->client_id = $client_id;
  288. return $this;
  289. }
  290. /**
  291. * Get the Client ID.
  292. *
  293. * @return string the Client ID
  294. */
  295. public function getClientID() {
  296. return $this->client_id;
  297. }
  298. /**
  299. * Set the Context Client ID.
  300. *
  301. * @param string $context_client_id the Client ID
  302. */
  303. public function setContextClientID($context_client_id) {
  304. $this->context_client_id = $context_client_id;
  305. return $this;
  306. }
  307. /**
  308. * Get the Client ID.
  309. *
  310. * @return string the Client ID
  311. */
  312. public function getContextClientID() {
  313. return $this->context_client_id;
  314. }
  315. /**
  316. * Set the Client Secret.
  317. *
  318. * @param string $client_secret the Client Secret
  319. */
  320. public function setClientSecret($client_secret) {
  321. $this->client_secret = $client_secret;
  322. return $this;
  323. }
  324. /**
  325. * Get the Client Secret.
  326. *
  327. * @return string the Client Secret
  328. */
  329. public function getClientSecret() {
  330. return $this->client_secret;
  331. }
  332. /**
  333. * Set the Client Signature Secret.
  334. *
  335. * @param string $client_sign_secret the Client Secret
  336. */
  337. public function setClientSignSecret($client_sign_secret) {
  338. $this->client_sign_secret = $client_sign_secret;
  339. return $this;
  340. }
  341. /**
  342. * Get the Client Secret.
  343. *
  344. * @return string the Client Secret
  345. */
  346. public function getClientSignSecret() {
  347. return $this->client_sign_secret;
  348. }
  349. /**
  350. * Make requests to the production servers
  351. *
  352. * @param boolean $production
  353. */
  354. public function setProduction($production = false) {
  355. $this->production = $production;
  356. }
  357. /**
  358. * Set the Redirect URI.
  359. *
  360. * @param string $redirect_uri
  361. */
  362. public function setRedirectUri($redirect_uri) {
  363. $this->redirect_uri = $redirect_uri;
  364. return $this;
  365. }
  366. public function isLive() {
  367. return $this->production;
  368. }
  369. /**
  370. * Get the Redirect URI.
  371. *
  372. * @return string Redirect URI
  373. */
  374. public function getRedirectUri() {
  375. return $this->redirect_uri;
  376. }
  377. /**
  378. * Set the Cookie Support status.
  379. *
  380. * @param Boolean $cookieSupport the Cookie Support status
  381. */
  382. public function setCookieSupport($cookieSupport) {
  383. $this->cookieSupport = $cookieSupport;
  384. return $this;
  385. }
  386. /**
  387. * Get the Cookie Support status.
  388. *
  389. * @return Boolean the Cookie Support status
  390. */
  391. public function useCookieSupport() {
  392. return $this->cookieSupport;
  393. }
  394. /**
  395. * Set the base domain for the Cookie.
  396. *
  397. * @param String $domain the base domain
  398. */
  399. public function setBaseDomain($domain) {
  400. $this->baseDomain = $domain;
  401. return $this;
  402. }
  403. /**
  404. * Get the base domain for the Cookie.
  405. *
  406. * @return String the base domain
  407. */
  408. public function getBaseDomain() {
  409. return $this->baseDomain;
  410. }
  411. /**
  412. * Set the file upload support status.
  413. *
  414. * @param String $domain the base domain
  415. */
  416. public function setFileUploadSupport($fileUploadSupport) {
  417. $this->fileUploadSupport = $fileUploadSupport;
  418. return $this;
  419. }
  420. /**
  421. * Get the file upload support status.
  422. *
  423. * @return String the base domain
  424. */
  425. public function useFileUploadSupport() {
  426. return $this->fileUploadSupport;
  427. }
  428. /**
  429. * Sets xiti analytics array.
  430. *
  431. * @return void
  432. */
  433. public function setXitiConfiguration($config) {
  434. $this->xiti = array_merge((array) $this->xiti, (array) $config);
  435. return $this;
  436. }
  437. /**
  438. * Returns xiti analytics array.
  439. *
  440. * @return String Encoded Xiti configuration
  441. */
  442. public function getXitiConfiguration() {
  443. return $this->encodeSerializedUrlVariable($this->xiti);
  444. }
  445. /**
  446. * Get a paramter from $_REQUEST - overwriteable for testing
  447. *
  448. * @param string $param name of key in $_REQUEST array
  449. * @return mixed/null
  450. */
  451. protected function _getRequestParam($param) {
  452. return array_key_exists($param, $_REQUEST) ? $_REQUEST[$param] : null;
  453. }
  454. /**
  455. * Get a paramter from $_SERVER - overwriteable for testing
  456. *
  457. * @param string $param name of key in $_SERVER array
  458. * @return mixed/null
  459. */
  460. protected function _getServerParam($param) {
  461. return array_key_exists($param, $_SERVER) ? $_SERVER[$param] : null;
  462. }
  463. /**
  464. * Get the data from a signed_request token
  465. *
  466. * @return String the base domain
  467. */
  468. public function getSignedRequest() {
  469. if (!$this->signedRequest) {
  470. $signed_request_paramter = $this->_getRequestParam('signed_request');
  471. if ($signed_request_paramter) {
  472. $this->signedRequest = $this->parseSignedRequest($signed_request_paramter);
  473. }
  474. }
  475. return $this->signedRequest;
  476. }
  477. /**
  478. * Set the Session.
  479. *
  480. * @param Array $session the session
  481. * @param Boolean $write_cookie indicate if a cookie should be written. this
  482. * value is ignored if cookie support has been disabled.
  483. */
  484. public function setSession($session = null, $write_cookie = true) {
  485. if ($this->debug) { $start = microtime(true); }
  486. $session = $this->validateSessionObject($session);
  487. $this->sessionLoaded = true;
  488. $this->session = $session;
  489. if ($write_cookie) {
  490. $this->setCookieFromSession($session);
  491. }
  492. if ($this->debug) { $this->timer[__FUNCTION__]['elapsed'][] = microtime(true)-$start; }
  493. return $this;
  494. }
  495. /**
  496. * Get the session object. This will automatically look for a signed session
  497. * sent via the signed_request, Cookie or Query Parameters if needed.
  498. *
  499. * @return Array the session
  500. */
  501. public function getSession() {
  502. if ($this->debug) { $start = microtime(true); }
  503. if (!$this->sessionLoaded) {
  504. $session = null;
  505. $write_cookie = true;
  506. $parsed_url = parse_url($this->redirect_uri);
  507. $parsed_path = '/';
  508. if (!empty($parsed_url['path'])) {
  509. $parsed_path = ($parsed_url['path'] != '/')?rtrim($parsed_url['path'], '/'):'/';
  510. }
  511. $request_uri = $this->_getServerParam('REQUEST_URI');
  512. $code = $this->_getRequestParam('code');
  513. if (!$session && stristr($request_uri,$parsed_path) && $code) {
  514. $ret = $this->getAccessToken($code);
  515. if (is_array($ret) && isset($ret['access_token'])) {
  516. $session = $this->signSession($ret); // Signs session for security
  517. }
  518. }
  519. // try loading session from signed_request in $_REQUEST
  520. $signedRequest = $this->getSignedRequest();
  521. if ($signedRequest) {
  522. // sig is good, use the signedRequest
  523. $session = $this->createSessionFromSignedRequest($signedRequest);
  524. }
  525. // try loading session from $_REQUEST
  526. $session_param = $this->_getRequestParam('session');
  527. if (!$session && $session_param) {
  528. $session = json_decode(get_magic_quotes_gpc() ? stripslashes($session_param) : $session_param, true);
  529. $session = $this->validateSessionObject($session);
  530. }
  531. // try loading session from cookie if necessary
  532. if (!$session && $this->useCookieSupport()) {
  533. $cookieName = $this->getSessionCookieName();
  534. if (isset($_COOKIE[$cookieName])) {
  535. $session = array();
  536. parse_str(trim(get_magic_quotes_gpc() ? stripslashes($_COOKIE[$cookieName]) : $_COOKIE[$cookieName], '"'), $session);
  537. $session = $this->validateSessionObject($session);
  538. // write only if we need to delete a invalid session cookie
  539. $write_cookie = empty($session);
  540. }
  541. }
  542. $this->setSession($session, $write_cookie);
  543. }
  544. if ($this->debug) { $this->timer[__FUNCTION__]['elapsed'][] = microtime(true)-$start; }
  545. return $this->session;
  546. }
  547. /**
  548. * Get the User ID from the session.
  549. * @return String the UID if available
  550. */
  551. public function getUserId() {
  552. $session = $this->getSession();
  553. return $session ? $session['user_id'] : 0;
  554. }
  555. /**
  556. * Get all verified emails for the logged in user.
  557. * @return array
  558. */
  559. public function getVerifiedEmails() {
  560. $emails = array();
  561. try {
  562. $user = $this->api('/me');
  563. if (isset($user['emails']) && is_array($user['emails']) && count($user['emails']) > 0) {
  564. foreach ($user['emails'] as $key => $value) {
  565. if (isset($value['verified']) && $value['verified'] == true) {
  566. $emails[] = $value['value'];
  567. }
  568. }
  569. }
  570. } catch (VGS_Client_Exception $e) {
  571. self::errorLog('Exception thrown when getting logged in user:'. $e->getMessage());
  572. }
  573. return $emails;
  574. }
  575. public function isEmailVerified($email) {
  576. try {
  577. $user = $this->api('/user/'.$email);
  578. if (isset($user['emails']) && is_array($user['emails']) && count($user['emails']) > 0) {
  579. foreach ($user['emails'] as $key => $value) {
  580. if (isset($value['verified']) && $value['verified'] == true && $value['value'] == $email) {
  581. return true;
  582. }
  583. }
  584. }
  585. } catch (VGS_Client_Exception $e) {
  586. self::errorLog('Exception thrown when getting logged in user:'. $e->getMessage());
  587. }
  588. return false;
  589. }
  590. /**
  591. * Gets a OAuth access token.
  592. *
  593. * @return String the access token
  594. */
  595. public function getAccessToken($code = null) {
  596. if ($this->debug) { $start = microtime(true); }
  597. if ($code) {
  598. // todo get access_token via authorization_code request
  599. $params['client_id'] = $this->getClientID();
  600. $params['client_secret'] = $this->getClientSecret();
  601. $params['redirect_uri'] = $this->getRedirectUri();
  602. $params['grant_type'] = 'authorization_code';
  603. $params['scope'] = '';
  604. $params['state'] = '';
  605. $params['code'] = $code;
  606. $access_token = (array) json_decode($this->makeRequest($this->getTokenURL(), $params));
  607. } else {
  608. if ($this->accessToken) {
  609. return $this->accessToken;
  610. } else {
  611. $session = $this->getSession();
  612. // either user session signed, or app signed
  613. if (isset($session['access_token'])) {
  614. $access_token = $session['access_token'];
  615. } else {
  616. // No success! Defaults to
  617. $access_token = $this->getClientID();
  618. }
  619. }
  620. }
  621. if ($this->debug) { $this->timer[__FUNCTION__]['elapsed'][] = microtime(true)-$start; }
  622. return $access_token;
  623. }
  624. /**
  625. * Gets a Fresh OAuth access token based on a refresh token
  626. *
  627. * @return String the refresh token
  628. */
  629. public function refreshAccessToken($refresh_token = null) {
  630. $return = array();
  631. if ($refresh_token) {
  632. // todo get access_token via refresh_token request
  633. $params['client_id'] = $this->getClientID();
  634. $params['client_secret'] = $this->getClientSecret();
  635. $params['redirect_uri'] = $this->getRedirectUri();
  636. $params['grant_type'] = 'refresh_token';
  637. $params['scope'] = '';
  638. $params['state'] = '';
  639. $params['refresh_token'] = $refresh_token;
  640. $return = (array) json_decode($this->makeRequest($this->getTokenURL(), $params));
  641. }
  642. if (is_array($return) && isset($return['access_token'])) {
  643. $this->session = $return;
  644. if (isset($return['access_token'])) {
  645. $this->setAccessToken($return['access_token']);
  646. }
  647. if (isset($return['refresh_token'])) {
  648. $this->setRefreshToken($return['refresh_token']);
  649. }
  650. } else {
  651. // No success! Defaults to
  652. $this->setAccessToken($this->getClientID());
  653. }
  654. return $this->getAccessToken();
  655. }
  656. /**
  657. * Auth function for starting server to server communication
  658. * Get an OAuth access token associated with your application via the OAuth Client Credentials Flow.
  659. * OAuth access tokens have no active user session, but allow you to make administrative calls that
  660. * do not require an active user. You can obtain an access token for your application using the
  661. * auth() function. After receiving an access token you can use the api() function for all calls
  662. * that do not require an active user session.
  663. * @return String Oauth Token on success
  664. */
  665. public function auth($token = false) {
  666. if ($token) {
  667. $this->setAccessToken($token);
  668. } else {
  669. $params['client_id'] = $this->getClientID();
  670. $params['client_secret'] = $this->getClientSecret();
  671. $params['redirect_uri'] = $this->getRedirectUri();
  672. $params['grant_type'] = 'client_credentials';
  673. $params['scope'] = '';
  674. $params['state'] = '';
  675. $return = (array) json_decode($this->makeRequest($this->getTokenURL(), $params));
  676. if (is_array($return) && isset($return['access_token'])) {
  677. if (isset($return['access_token'])) {
  678. $this->setAccessToken($return['access_token']);
  679. }
  680. if (isset($return['refresh_token'])) {
  681. $this->setRefreshToken($return['refresh_token']);
  682. }
  683. } else {
  684. // No success! Defaults to
  685. $this->setAccessToken($this->getClientID());
  686. }
  687. }
  688. return $this->getAccessToken();
  689. }
  690. /**
  691. * Sets a server to server access code
  692. * @param String Oauth Token on success
  693. */
  694. public function setAccessToken($token = false) {
  695. $this->accessToken = $token;
  696. }
  697. /**
  698. * Sets a server to server refresh token
  699. * @param String Oauth Refresh Token on success
  700. */
  701. public function setRefreshToken($token = false) {
  702. $this->refreshToken = $token;
  703. }
  704. /**
  705. * Gets the server to server refresh token that was received with the latest access token
  706. * @return String Oauth Refresh Token on success
  707. */
  708. public function getRefreshToken() {
  709. return $this->refreshToken;
  710. }
  711. /**
  712. * Get an URI to any flow url in SPiD
  713. *
  714. * @param string $flow_name name of flow, ie `auth`, `login`, `checkout` etc
  715. * @param array $params get parameters to include in the url, like `cancel_redirect_uri`, `tag` or `redirect_uri`
  716. * @return string url
  717. */
  718. public function getFlowURI($flow_name, array $params = array()) {
  719. if (empty($flow_name)) {
  720. throw new VGS_Client_Exception("Unspecified flow name");
  721. }
  722. $default_params = array(
  723. 'client_id' => $this->getClientID(),
  724. 'response_type' => 'code',
  725. 'redirect_uri' => $this->getCurrentURI(),
  726. );
  727. if ($this->xiti) {
  728. $default_params['xiti'] = $this->getXitiConfiguration();
  729. }
  730. $default_params['v'] = self::VERSION;
  731. $parameters = array_merge($default_params, $params);
  732. return $this->getUrl('flow', $flow_name, $parameters);
  733. }
  734. /**
  735. * Get a Login URI for use with redirects. By default, full page redirect is
  736. * assumed. If you are using the generated URI with a window.open() call in
  737. * JavaScript, you can pass in display=popup as part of the $params.
  738. *
  739. * The parameters:
  740. * - redirect_uri: the URI to go to after a successful login
  741. * - cancel_url: the URI to go to after the user cancels
  742. * - display: can be "page" (default, full page) or "popup"
  743. *
  744. * @param Array $params provide custom parameters
  745. * @return String the URI for the login flow
  746. */
  747. public function getLoginURI($params = array()) {
  748. return $this->getFlowURI('login', $params);
  749. }
  750. /**
  751. * Get a Signup URI for use with redirects. By default, full page redirect is
  752. * assumed. If you are using the generated URI with a window.open() call in
  753. * JavaScript, you can pass in display=popup as part of the $params.
  754. *
  755. * The parameters:
  756. * - redirect_uri: the URI to go to after a successful login
  757. * - cancel_url: the URI to go to after the user cancels
  758. * - display: can be "page" (default, full page) or "popup"
  759. *
  760. * @param Array $params provide custom parameters
  761. * @return String the URI for the login flow
  762. */
  763. public function getSignupURI($params = array()) {
  764. return $this->getFlowURI('signup', $params);
  765. }
  766. /**
  767. * Get the URI for redirecting the user to his VG Konto account page
  768. * @return String the URI for the account page
  769. */
  770. public function getAccountURI($params = array()) {
  771. $default_params = array(
  772. 'client_id' => $this->getClientID(),
  773. 'response_type' => 'code',
  774. 'redirect_uri' => $this->getCurrentURI(),
  775. );
  776. if ($this->xiti) {
  777. $default_params['xiti'] = $this->getXitiConfiguration();
  778. }
  779. $default_params['v'] = self::VERSION;
  780. return $this->getUrl('www', 'account', array_merge($default_params, $params));
  781. }
  782. /**
  783. * Get the URI for redirecting the user to his VG Konto purchase history page
  784. * @return String the URI for the purchase history page
  785. */
  786. public function getPurchaseHistoryURI($params = array()) {
  787. $default_params = array(
  788. 'client_id' => $this->getClientID(),
  789. 'response_type' => 'code',
  790. 'redirect_uri' => $this->getCurrentURI(),
  791. );
  792. if ($this->xiti) {
  793. $default_params['xiti'] = $this->getXitiConfiguration();
  794. }
  795. $default_params['v'] = self::VERSION;
  796. return $this->getUrl('www', 'account/purchasehistory', array_merge($default_params, $params));
  797. }
  798. /**
  799. * Get the API URI
  800. * @return String the API URI
  801. */
  802. public function getApiURI($path, $params = array()) {
  803. return $this->getUrl('api', $path, array_merge(array('oauth_token' => $this->getAccessToken()),$params));
  804. }
  805. /**
  806. * Get a Logout URI suitable for use with redirects.
  807. *
  808. * The parameters:
  809. * - redirect_uri: the URI to go to after a successful logout
  810. *
  811. * @param Array $params provide custom parameters
  812. * @return String the URI for the logout flow
  813. */
  814. public function getLogoutURI($params = array()) {
  815. $default_params = array(
  816. 'redirect_uri'=> $this->getCurrentURI(),
  817. 'oauth_token' => $this->getAccessToken()
  818. );
  819. if ($this->xiti) {
  820. $default_params['xiti'] = $this->getXitiConfiguration();
  821. }
  822. $default_params['v'] = self::VERSION;
  823. return $this->getUrl('www', 'logout', array_merge($default_params, $params));
  824. }
  825. /**
  826. * Get a Purchase URI suitable for use with redirects.
  827. *
  828. * The parameters:
  829. * - product_id: preselect a specific product (skip choose product step)
  830. *
  831. * @param Array $params provide custom parameters
  832. * @return string URI to product purchase
  833. */
  834. public function getPurchaseURI($params = array()) {
  835. return $this->getFlowURI('checkout', $params);
  836. }
  837. /**
  838. * Get a login status URI to fetch the status from SPiD.
  839. *
  840. * The parameters:
  841. * - ok_session: the URI to go to if a session is found
  842. * - no_session: the URI to go to if the user is not connected
  843. * - no_user: the URI to go to if the user is not signed into SPiD
  844. *
  845. * @param Array $params provide custom parameters
  846. * @return String the URI for the logout flow
  847. */
  848. public function getLoginStatusUrl($params = array()) {
  849. return $this->getUrl('www', 'login_status', array_merge(array(
  850. 'client_id' => $this->getClientID(),
  851. 'no_session' => $this->getCurrentURI(),
  852. 'no_user' => $this->getCurrentURI(),
  853. 'ok_session' => $this->getCurrentURI(),
  854. 'session_version' => 1), $params));
  855. }
  856. /**
  857. * Make an API call.
  858. *
  859. * @param Array $params the API call parameters
  860. * @return mixed the decoded response
  861. */
  862. public function api(/* polymorphic */) {
  863. $args = func_get_args();
  864. return call_user_func_array(array(
  865. $this,
  866. '_restserver'), $args);
  867. }
  868. /**
  869. * Invoke the REST API.
  870. *
  871. * @param String $path the path (required)
  872. * @param String $method the http method (default 'GET')
  873. * @param Array $params the query/post data
  874. * @return the decoded response object
  875. * @throws VGS_Client_Exception
  876. */
  877. protected function _restserver($path, $method = 'GET', $params = array(), $getParams = array()) {
  878. $this->container = null;
  879. if ($this->debug) { $start = microtime(true); }
  880. if (is_array($method) && empty($params)) {
  881. $params = $method;
  882. $method = 'GET';
  883. }
  884. if ($method == 'PUT') {
  885. $method = 'POST';
  886. }
  887. $getParams['method'] = $method; // method override as we always do a POST
  888. $uri = $this->getUrl('api', $path);
  889. $result = $this->_oauthRequest($uri, $params, $getParams);
  890. if (floatval($this->api_version) >= 2) {
  891. $container = json_decode($result, true);
  892. if ($container && array_key_exists('name', $container) && $container['name'] == 'SPP Container') {
  893. $this->container = $container;
  894. }
  895. }
  896. preg_match("/\.(json|jsonp|html|xml|serialize|php|csv|tgz)$/", $path, $matches);
  897. if ($matches) {
  898. switch ($matches[1]) {
  899. case 'json':
  900. $result = json_decode($result, true);
  901. break;
  902. default:
  903. if ($this->debug) { $this->timer[__FUNCTION__]['elapsed'][] = microtime(true)-$start; }
  904. return $result;
  905. break;
  906. }
  907. } else
  908. $result = json_decode($result, true);
  909. // results are returned, errors are thrown
  910. if (is_array($result) && isset($result['error']) && $result['error']) {
  911. $e = new VGS_Client_Exception($result, $this->raw);
  912. switch ($e->getType()) {
  913. case 'ApiException':
  914. break;
  915. // OAuth 2.0 Draft 00 style
  916. case 'OAuthException':
  917. // OAuth 2.0 Draft 10 style
  918. case 'invalid_token':
  919. $this->setSession(null);
  920. }
  921. throw $e;
  922. }
  923. if (floatval($this->api_version) >= 2 && $result && is_array($result) && array_key_exists('name', $result) && $result['name'] == 'SPP Container') {
  924. if (isset($result['sig']) && isset($result['algorithm'])) {
  925. $result = $this->validateAndDecodeSignedRequest($result['sig'], $result['data'], $result['algorithm']);
  926. } else {
  927. $result = $result['data'];
  928. }
  929. }
  930. if ($this->debug) { $this->timer[__FUNCTION__]['elapsed'][] = microtime(true)-$start; }
  931. return $result;
  932. }
  933. public function getLastMeta() {
  934. if ($this->container && is_array($this->container) && isset($this->container['meta'])) {
  935. return $this->container['meta'];
  936. }
  937. }
  938. public function getLastError() {
  939. if ($this->container && is_array($this->container) && isset($this->container['error'])) {
  940. return $this->container['error'];
  941. }
  942. }
  943. public function getLastDebug() {
  944. if ($this->container && is_array($this->container) && isset($this->container['debug'])) {
  945. return $this->container['debug'];
  946. }
  947. }
  948. public function getLastContainerType() {
  949. return ($this->container && is_array($this->container) && isset($this->container['type'])) ? $this->container['type'] : null;
  950. }
  951. public function getLastContainerObject() {
  952. return ($this->container && is_array($this->container) && isset($this->container['object'])) ? $this->container['object'] : null;
  953. }
  954. public function getLastHttpCode() {
  955. return ($this->container && is_array($this->container) && isset($this->container['code'])) ? $this->container['code'] : null;
  956. }
  957. public function getLastRequestMeta() {
  958. return ($this->container && is_array($this->container) && isset($this->container['request'])) ? $this->container['request'] : null;
  959. }
  960. public function getLastLatency() {
  961. return $this->latency;
  962. }
  963. /**
  964. * Make a OAuth Request
  965. *
  966. * @param String $path the path (required)
  967. * @param Array $params the query/post data
  968. * @return the decoded response object
  969. * @throws VGS_Client_Exception
  970. */
  971. protected function _oauthRequest($uri, $params, $getParams = array()) {
  972. if ($this->debug) { $start = microtime(true); }
  973. if (!isset($getParams['oauth_token']) && !isset($params['oauth_token'])) {
  974. $params['oauth_token'] = $this->getAccessToken();
  975. }
  976. // json_encode all params values that are not strings
  977. foreach ((array)$params as $key => $value) {
  978. if (!is_string($value)) {
  979. $params[$key] = json_encode($value);
  980. }
  981. }
  982. if ($this->debug) { $this->timer[__FUNCTION__]['elapsed'][] = microtime(true)-$start; }
  983. return $this->makeRequest($uri, $params, null, $getParams);
  984. }
  985. /**
  986. * Makes an HTTP request. This method can be overriden by subclasses if
  987. * developers want to do fancier things or use something other than curl to
  988. * make the request.
  989. *
  990. * @param String $uri the URI to make the request to
  991. * @param Array $params the parameters to use for the POST body
  992. * @param CurlHandler $ch optional initialized curl handle
  993. * @return String the response text
  994. */
  995. protected function makeRequest($uri, $params, $ch = null, $getParams = array()) {
  996. $start = microtime(true);
  997. if (!$ch) {
  998. $ch = curl_init();
  999. }
  1000. $opts = self::$CURL_OPTS;
  1001. if ($this->useFileUploadSupport()) {
  1002. $opts[CURLOPT_POSTFIELDS] = $params;
  1003. } else {
  1004. if (!isset($getParams['contextClientId']) && $this->getContextClientID()) {
  1005. $getParams['contextClientId'] = $this->getContextClientID();
  1006. }
  1007. if (isset($getParams['method']) && strtoupper($getParams['method']) == 'GET') {
  1008. if ($params && is_array($params)) foreach ($params as $k => $v) $getParams[$k] = $v;
  1009. $uri = $uri . (strpos($uri, '?') === FALSE ? '?' : '') . http_build_query($getParams, null, '&');
  1010. } else {
  1011. $uri = $uri . (strpos($uri, '?') === FALSE ? '?' : '') . http_build_query($getParams, null, '&');
  1012. $opts[CURLOPT_POSTFIELDS] = http_build_query($params, null, '&');
  1013. }
  1014. }
  1015. $opts[CURLOPT_URL] = $uri;
  1016. // disable the 'Expect: 100-continue' behaviour. This causes CURL to wait
  1017. // for 2 seconds if the server does not support this header.
  1018. if (isset($opts[CURLOPT_HTTPHEADER])) {
  1019. $existing_headers = $opts[CURLOPT_HTTPHEADER];
  1020. $existing_headers[] = 'Expect:';
  1021. $opts[CURLOPT_HTTPHEADER] = $existing_headers;
  1022. } else {
  1023. $opts[CURLOPT_HTTPHEADER] = array(
  1024. 'Expect:');
  1025. }
  1026. curl_setopt_array($ch, $opts);
  1027. $result = $this->raw = curl_exec($ch);
  1028. $info = curl_getinfo($ch);
  1029. $this->last_result_code = $info['http_code'];
  1030. /*
  1031. if (curl_errno($ch) == 60) { // CURLE_SSL_CACERT
  1032. self::errorLog('Invalid or no certificate authority found, using bundled information');
  1033. curl_setopt($ch, CURLOPT_CAINFO,
  1034. dirname(__FILE__) . '/vgs_ca_chain_bundle.crt');
  1035. $result = curl_exec($ch);
  1036. }
  1037. */
  1038. $this->latency = microtime(true)-$start;
  1039. if ($this->debug) { $this->timer[__FUNCTION__]['elapsed'][] = $this->latency; }
  1040. if ($result === false) {
  1041. $e = new VGS_Client_Exception(array(
  1042. 'error_code' => curl_errno($ch),
  1043. 'error' => array(
  1044. 'message' => curl_error($ch),
  1045. 'type' => 'CurlException'
  1046. )
  1047. ));
  1048. curl_close($ch);
  1049. throw $e;
  1050. } elseif (isset($info['http_code']) && $info['http_code'] >= 400) {
  1051. $result = json_decode($result, true) ?: $result;
  1052. if (is_array($result) && isset($result['name']) && $result['name'] == 'SPP Container') {
  1053. $this->container = $result;
  1054. }
  1055. $e = new VGS_Client_Exception($result, $this->raw);
  1056. curl_close($ch);
  1057. throw $e;
  1058. }
  1059. curl_close($ch);
  1060. return $result;
  1061. }
  1062. /**
  1063. * The name of the Cookie that contains the session.
  1064. *
  1065. * @return String the cookie name
  1066. */
  1067. protected function getSessionCookieName() {
  1068. return 'vgs_' . $this->getClientID();
  1069. }
  1070. /**
  1071. * Delete Session
  1072. */
  1073. public function deleteSession() {
  1074. $this->setCookieFromSession();
  1075. unset($_COOKIE[$this->getSessionCookieName()]);
  1076. }
  1077. /**
  1078. * Set a JS Cookie based on the _passed in_ session. It does not use the
  1079. * currently stored session -- you need to explicitly pass it in.
  1080. *
  1081. * @param Array $session the session to use for setting the cookie
  1082. */
  1083. protected function setCookieFromSession($session = null) {
  1084. if ($this->debug) { $start = microtime(true); }
  1085. if (!$this->useCookieSupport()) {
  1086. return;
  1087. }
  1088. $now = time();
  1089. $cookieName = $this->getSessionCookieName();
  1090. $value = 'deleted';
  1091. $expires_in = $now - 3600*25; // Expires in all 25 timezones
  1092. $domain = $this->getBaseDomain();
  1093. if ($session) {
  1094. $value = '"' . http_build_query($session, null, '&') . '"';
  1095. if (isset($session['base_domain'])) {
  1096. $domain = $session['base_domain'];
  1097. }
  1098. $expires_in = $now + $session['expires_in']; // Defaults to client server time
  1099. }
  1100. // prepend dot if a domain is found
  1101. if ($domain) {
  1102. $domain = '.' . $domain;
  1103. }
  1104. // if an existing cookie is not set, we dont need to delete it
  1105. if ($value == 'deleted' && empty($_COOKIE[$cookieName])) {
  1106. return;
  1107. }
  1108. if (headers_sent()) {
  1109. self::errorLog('Could not set cookie. Headers already sent.');
  1110. // ignore for code coverage as we will never be able to setcookie in a CLI
  1111. // environment
  1112. // @codeCoverageIgnoreStart
  1113. } else {
  1114. setcookie($cookieName, $value, $expires_in, '/', $domain);
  1115. }
  1116. // @codeCoverageIgnoreEnd
  1117. if ($this->debug) { $this->timer[__FUNCTION__]['elapsed'][] = microtime(true)-$start; }
  1118. }
  1119. /**
  1120. * Validates a session_version=1 style session object.
  1121. *
  1122. * @param Array $session the session object
  1123. * @return Array the session object if it validates, null otherwise
  1124. */
  1125. protected function validateSessionObject($session) {
  1126. // make sure some essential fields exist
  1127. if (is_array($session) && isset($session['access_token']) && isset($session['sig'])) {
  1128. // validate the signature
  1129. $session_without_sig = $session;
  1130. unset($session_without_sig['sig']);
  1131. $expected_sig = self::generateSignature($session_without_sig, $this->getClientSecret());
  1132. if ($session['sig'] != $expected_sig) {
  1133. self::errorLog('Got invalid session signature in cookie.');
  1134. $session = null;
  1135. }
  1136. // check expiration time
  1137. } else {
  1138. $session = null;
  1139. }
  1140. return $session;
  1141. }
  1142. /**
  1143. * Returns something that looks like our JS session object from the
  1144. * signed token's data
  1145. *
  1146. * TODO: Nuke this once the login flow uses OAuth2
  1147. *
  1148. * @param Array the output of getSignedRequest
  1149. * @return Array Something that will work as a session
  1150. */
  1151. protected function createSessionFromSignedRequest($data) {
  1152. if (!isset($data['oauth_token'])) {
  1153. return null;
  1154. }
  1155. $session = array(
  1156. 'user_id' => isset($data['user_id'])?$data['user_id']:0,
  1157. 'access_token' => $data['oauth_token'],
  1158. 'expires_in' => $data['expires_in'],
  1159. 'server_time' => $data['server_time'],
  1160. );
  1161. // put a real sig, so that validateSignature works
  1162. $session['sig'] = self::generateSignature($session, $this->getClientSecret());
  1163. return $session;
  1164. }
  1165. /**
  1166. * Signs the session data
  1167. *
  1168. * @param Array Unsigned session
  1169. * @return Array Signed session
  1170. */
  1171. protected function signSession($data) {
  1172. if (!isset($data['access_token'])) {
  1173. return null;
  1174. }
  1175. $session = array(
  1176. 'user_id' => isset($data['user_id'])?$data['user_id']:0,
  1177. 'access_token' => $data['access_token'],
  1178. 'expires_in' => $data['expires_in'],
  1179. 'server_time' => $data['server_time'],
  1180. 'refresh_token' => $data['refresh_token'],
  1181. );
  1182. // put a real sig, so that validateSignature works
  1183. $session['sig'] = self::generateSignature($session, $this->getClientSecret());
  1184. return $session;
  1185. }
  1186. /**
  1187. * Used to create outgoing POST data hashes, not for API response signature
  1188. *
  1189. * @param array $data
  1190. * @return string
  1191. */
  1192. private function recursiveHash($data) {
  1193. if (!is_array($data)) {
  1194. return $data;
  1195. }
  1196. $ret = "";
  1197. uksort($data, 'strnatcmp');
  1198. foreach ($data as $v) {
  1199. $ret .= $this->recursiveHash($v);
  1200. }
  1201. return $ret;
  1202. }
  1203. /**
  1204. * Creates a post data array hash that must be added to outgoing API requests
  1205. * that require it.
  1206. *
  1207. * @param array $data
  1208. * @return string
  1209. */
  1210. public function createHash($data) {
  1211. $string = $this->recursiveHash($data);
  1212. $secret = $this->getClientSignSecret();
  1213. return self::base64UrlEncode(hash_hmac("sha256", $string, $secret, true));
  1214. }
  1215. /**
  1216. * Parses a signed_request and validates the signature.
  1217. *
  1218. * @param String A signed token
  1219. * @return Array the payload inside it or null if the sig is wrong
  1220. */
  1221. public function parseSignedRequest($signed_request) {
  1222. list($encoded_sig, $payload) = explode('.', $signed_request, 2);
  1223. $data = json_decode(self::base64UrlDecode($payload), true);
  1224. $algorithm = strtoupper($data['algorithm']);
  1225. return $this->validateAndDecodeSignedRequest($encoded_sig, $payload, $algorithm);
  1226. }
  1227. /**
  1228. * Validate and decode Signed API Request responses
  1229. *
  1230. * @param string $encoded_signature
  1231. * @param string $payload
  1232. * @param string $algorithm
  1233. * @return array array of decoded payload or null if invalid signature
  1234. */
  1235. public function validateAndDecodeSignedRequest($encoded_signature, $payload, $algorithm = 'HMAC-SHA256') {
  1236. $sig = self::base64UrlDecode($encoded_signature);
  1237. switch ($algorithm) {
  1238. case 'HMAC-SHA256' :
  1239. $expected_sig = hash_hmac('sha256', $payload, $this->getClientSignSecret(), true);
  1240. // check sig
  1241. if ($sig !== $expected_sig) {
  1242. self::errorLog('Bad Signed JSON signature!');
  1243. return null;
  1244. }
  1245. return json_decode(self::base64UrlDecode($payload), true);
  1246. break;
  1247. default:
  1248. self::errorLog('Unknown algorithm. Expected HMAC-SHA256');
  1249. break;
  1250. }
  1251. return null;
  1252. }
  1253. /**
  1254. * Build the URI for given domain alias, path and parameters.
  1255. *
  1256. * @param $name String the name of the domain
  1257. * @param $path String optional path (without a leading slash)
  1258. * @param $params Array optional query parameters
  1259. * @return String the URI for the given parameters
  1260. */
  1261. protected function getUrl($name, $path = '', $params = array()) {
  1262. $uri = self::getBaseURL($name);
  1263. if ($path) {
  1264. if ($path[0] === '/') {
  1265. $path = substr($path, 1);
  1266. }
  1267. $uri .= $path;
  1268. }
  1269. if ($params) {
  1270. $uri .= '?' . http_build_query($params, null, $this->argSeparator);
  1271. }
  1272. return $uri;
  1273. }
  1274. /**
  1275. * Returns the Current URI, stripping it of known parameters that should
  1276. * not persist.
  1277. *
  1278. * @return String the current URI
  1279. */
  1280. public function getCurrentURI($extra_params = array(), $drop_params = array()) {
  1281. $drop_params = array_merge(self::$DROP_QUERY_PARAMS, $drop_params);
  1282. $server_https = $this->_getServerParam('HTTPS');
  1283. $server_http_host = $this->_getServerParam('HTTP_HOST') ?: '';
  1284. $server_request_uri = $this->_getServerParam('REQUEST_URI') ?: '';
  1285. $protocol = isset($server_https) && $server_https == 'on' ? 'https://' : 'http://';
  1286. $currentUrl = $protocol . $server_http_host . $server_request_uri;
  1287. $parts = parse_url($currentUrl);
  1288. // drop known params
  1289. $query = '';
  1290. if (!empty($parts['query'])) {
  1291. $params = array();
  1292. parse_str($parts['query'], $params);
  1293. $params = array_merge($params, $extra_params);
  1294. //print_r($params);
  1295. foreach ($drop_params as $key) {
  1296. unset($params[$key]);
  1297. }
  1298. if (!empty($params)) {
  1299. $query = '?' . http_build_query($params, null, $this->argSeparator);
  1300. }
  1301. } elseif (!empty($extra_params)) {
  1302. $query = '?' . http_build_query($extra_params, null, $this->argSeparator);
  1303. }
  1304. // use port if non default
  1305. $port = isset($parts['port']) && (($protocol === 'http://' && $parts['port'] !== 80) || ($protocol === 'https://' && $parts['port'] !== 443)) ? ':' . $parts['port'] : '';
  1306. // rebuild
  1307. return $protocol . (isset($parts['host'])?$parts['host']:'') . $port . (isset($parts['path'])?$parts['path']:'') . $query;
  1308. }
  1309. /**
  1310. * Returns the result code of the last request.
  1311. *
  1312. * @return int
  1313. */
  1314. public function getLastResultCode() {
  1315. return $this->last_result_code;
  1316. }
  1317. /**
  1318. * Generate a signature for the given params and secret.
  1319. *
  1320. * @param Array $params the parameters to sign
  1321. * @param String $secret the secret to sign with
  1322. * @return String the generated signature
  1323. */
  1324. protected static function generateSignature($params, $secret) {
  1325. // work with sorted data
  1326. ksort($params);
  1327. // generate the base string
  1328. $base_string = '';
  1329. foreach ($params as $key => $value) {
  1330. $base_string .= $key . '=' . $value;
  1331. }
  1332. $base_string .= $secret;
  1333. return md5($base_string);
  1334. }
  1335. /**
  1336. * Prints to the error log if you aren't in command line mode.
  1337. *
  1338. * @param String log message
  1339. */
  1340. protected static function errorLog($msg) {
  1341. // disable error log if we are running in a CLI environment
  1342. // @codeCoverageIgnoreStart
  1343. if (php_sapi_name() != 'cli') {
  1344. error_log($msg);
  1345. }
  1346. // uncomment this if you want to see the errors on the page
  1347. self::$errors[] = $msg;
  1348. // print 'error_log: '.$msg."\n";
  1349. // @codeCoverageIgnoreEnd
  1350. }
  1351. /**
  1352. * Base64 encoding that doesn't need to be urlencode()ed.
  1353. * Exactly the same as base64_encode except it uses
  1354. * - instead of +
  1355. * _ instead of /
  1356. *
  1357. * @param String base64UrlEncodeded string
  1358. */
  1359. protected static function base64UrlDecode($input) {

Large files files are truncated, but you can click here to view the full file