File manager - Edit - /home/aresglob/public_html/wp/wp-includes/images/smilies/lib.tar
Back
hybridauth/Logger/Logger.php 0000644 00000006276 15104256626 0012100 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Logger; use Hybridauth\Exception\RuntimeException; use Hybridauth\Exception\InvalidArgumentException; /** * Debugging and Logging utility. */ class Logger implements LoggerInterface { const NONE = 'none'; // turn logging off const DEBUG = 'debug'; // debug, info and error messages const INFO = 'info'; // info and error messages const ERROR = 'error'; // only error messages /** * Debug level. * * One of Logger::NONE, Logger::DEBUG, Logger::INFO, Logger::ERROR * * @var string */ protected $level; /** * Path to file writeable by the web server. Required if $this->level !== Logger::NONE. * * @var string */ protected $file; /** * @param bool|string $level One of Logger::NONE, Logger::DEBUG, Logger::INFO, Logger::ERROR * @param string $file File where to write messages * * @throws InvalidArgumentException * @throws RuntimeException */ public function __construct($level, $file) { $this->level = self::NONE; if ($level && $level !== self::NONE) { $this->initialize($file); $this->level = $level === true ? Logger::DEBUG : $level; $this->file = $file; } } /** * @param string $file * * @throws InvalidArgumentException * @throws RuntimeException */ protected function initialize($file) { if (!$file) { throw new InvalidArgumentException('Log file is not specified.'); } if (!file_exists($file) && !touch($file)) { throw new RuntimeException(sprintf('Log file %s can not be created.', $file)); } if (!is_writable($file)) { throw new RuntimeException(sprintf('Log file %s is not writeable.', $file)); } } /** * @inheritdoc */ public function info($message, array $context = []) { if (!in_array($this->level, [self::DEBUG, self::INFO])) { return; } $this->log(self::INFO, $message, $context); } /** * @inheritdoc */ public function debug($message, array $context = []) { if (!in_array($this->level, [self::DEBUG])) { return; } $this->log(self::DEBUG, $message, $context); } /** * @inheritdoc */ public function error($message, array $context = []) { if (!in_array($this->level, [self::DEBUG, self::INFO, self::ERROR])) { return; } $this->log(self::ERROR, $message, $context); } /** * @inheritdoc */ public function log($level, $message, array $context = []) { $datetime = new \DateTime(); $datetime = $datetime->format(DATE_ATOM); $content = sprintf('%s -- %s -- %s -- %s', $level, $_SERVER['REMOTE_ADDR'], $datetime, $message); $content .= ($context ? "\n" . print_r($context, true) : ''); $content .= "\n"; file_put_contents($this->file, $content, FILE_APPEND); } } hybridauth/Logger/Psr3LoggerWrapper.php 0000644 00000001730 15104256626 0014177 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Logger; use Psr\Log\LoggerAwareTrait; /** * Wrapper for PSR3 logger. */ class Psr3LoggerWrapper implements LoggerInterface { use LoggerAwareTrait; /** * @inheritdoc */ public function info($message, array $context = []) { $this->logger->info($message, $context); } /** * @inheritdoc */ public function debug($message, array $context = []) { $this->logger->debug($message, $context); } /** * @inheritdoc */ public function error($message, array $context = []) { $this->logger->error($message, $context); } /** * @inheritdoc */ public function log($level, $message, array $context = []) { $this->logger->log($level, $message, $context); } } hybridauth/Logger/LoggerInterface.php 0000644 00000002221 15104256626 0013703 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Logger; /** * Logger interface, forward-compatible with PSR-3. */ interface LoggerInterface { /** * Interesting events. * * Example: User logs in, SQL logs. * * @param string $message * @param array $context */ public function info($message, array $context = array()); /** * Detailed debug information. * * @param string $message * @param array $context */ public function debug($message, array $context = array()); /** * Runtime errors that do not require immediate action but should typically * be logged and monitored. * * @param string $message * @param array $context */ public function error($message, array $context = array()); /** * Logs with an arbitrary level. * * @param mixed $level * @param string $message * @param array $context */ public function log($level, $message, array $context = array()); } hybridauth/index.html 0000644 00000000004 15104256626 0010705 0 ustar 00 403. hybridauth/Hybridauth.php 0000644 00000016271 15104256626 0011541 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth; use Hybridauth\Exception\InvalidArgumentException; use Hybridauth\Exception\UnexpectedValueException; use Hybridauth\Storage\StorageInterface; use Hybridauth\Logger\LoggerInterface; use Hybridauth\Logger\Logger; use Hybridauth\HttpClient\HttpClientInterface; /** * Hybridauth\Hybridauth * * For ease of use of multiple providers, Hybridauth implements the class Hybridauth\Hybridauth, * a sort of factory/façade which acts as an unified interface or entry point, and it expects a * configuration array containing the list of providers you want to use, their respective credentials * and authorized callback. */ class Hybridauth { /** * Hybridauth config. * * @var array */ protected $config; /** * Storage. * * @var StorageInterface */ protected $storage; /** * HttpClient. * * @var HttpClientInterface */ protected $httpClient; /** * Logger. * * @var LoggerInterface */ protected $logger; /** * @param array|string $config Array with configuration or Path to PHP file that will return array * @param HttpClientInterface $httpClient * @param StorageInterface $storage * @param LoggerInterface $logger * * @throws InvalidArgumentException */ public function __construct( $config, HttpClientInterface $httpClient = null, StorageInterface $storage = null, LoggerInterface $logger = null ) { if (is_string($config) && file_exists($config)) { $config = include $config; } elseif (!is_array($config)) { throw new InvalidArgumentException('Hybridauth config does not exist on the given path.'); } $this->config = $config + [ 'debug_mode' => Logger::NONE, 'debug_file' => '', 'curl_options' => null, 'providers' => [] ]; $this->storage = $storage; $this->logger = $logger; $this->httpClient = $httpClient; } /** * Instantiate the given provider and authentication or authorization protocol. * * If not authenticated yet, the user will be redirected to the provider's site for * authentication/authorisation, otherwise it will simply return an instance of * provider's adapter. * * @param string $name adapter's name (case insensitive) * * @return \Hybridauth\Adapter\AdapterInterface * @throws InvalidArgumentException * @throws UnexpectedValueException */ public function authenticate($name) { $adapter = $this->getAdapter($name); $adapter->authenticate(); return $adapter; } /** * Returns a new instance of a provider's adapter by name * * @param string $name adapter's name (case insensitive) * * @return \Hybridauth\Adapter\AdapterInterface * @throws InvalidArgumentException * @throws UnexpectedValueException */ public function getAdapter($name) { $config = $this->getProviderConfig($name); $adapter = isset($config['adapter']) ? $config['adapter'] : sprintf('Hybridauth\\Provider\\%s', $name); if (!class_exists($adapter)) { $adapter = null; $fs = new \FilesystemIterator(__DIR__ . '/Provider/'); /** @var \SplFileInfo $file */ foreach ($fs as $file) { if (!$file->isDir()) { $provider = strtok($file->getFilename(), '.'); if (strtolower($name) === mb_strtolower($provider)) { $adapter = sprintf('Hybridauth\\Provider\\%s', $provider); break; } } } if ($adapter === null) { throw new InvalidArgumentException('Unknown Provider.'); } } return new $adapter($config, $this->httpClient, $this->storage, $this->logger); } /** * Get provider config by name. * * @param string $name adapter's name (case insensitive) * * @throws UnexpectedValueException * @throws InvalidArgumentException * * @return array */ public function getProviderConfig($name) { $name = strtolower($name); $providersConfig = array_change_key_case($this->config['providers'], CASE_LOWER); if (!isset($providersConfig[$name])) { throw new InvalidArgumentException('Unknown Provider.'); } if (!$providersConfig[$name]['enabled']) { throw new UnexpectedValueException('Disabled Provider.'); } $config = $providersConfig[$name]; $config += [ 'debug_mode' => $this->config['debug_mode'], 'debug_file' => $this->config['debug_file'], ]; if (!isset($config['callback']) && isset($this->config['callback'])) { $config['callback'] = $this->config['callback']; } return $config; } /** * Returns a boolean of whether the user is connected with a provider * * @param string $name adapter's name (case insensitive) * * @return bool * @throws InvalidArgumentException * @throws UnexpectedValueException */ public function isConnectedWith($name) { return $this->getAdapter($name)->isConnected(); } /** * Returns a list of enabled adapters names * * @return array */ public function getProviders() { $providers = []; foreach ($this->config['providers'] as $name => $config) { if ($config['enabled']) { $providers[] = $name; } } return $providers; } /** * Returns a list of currently connected adapters names * * @return array * @throws InvalidArgumentException * @throws UnexpectedValueException */ public function getConnectedProviders() { $providers = []; foreach ($this->getProviders() as $name) { if ($this->isConnectedWith($name)) { $providers[] = $name; } } return $providers; } /** * Returns a list of new instances of currently connected adapters * * @return \Hybridauth\Adapter\AdapterInterface[] * @throws InvalidArgumentException * @throws UnexpectedValueException */ public function getConnectedAdapters() { $adapters = []; foreach ($this->getProviders() as $name) { $adapter = $this->getAdapter($name); if ($adapter->isConnected()) { $adapters[$name] = $adapter; } } return $adapters; } /** * Disconnect all currently connected adapters at once */ public function disconnectAllAdapters() { foreach ($this->getProviders() as $name) { $adapter = $this->getAdapter($name); if ($adapter->isConnected()) { $adapter->disconnect(); } } } } hybridauth/Exception/InvalidAccessTokenException.php 0000644 00000000501 15104256626 0016751 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Exception; /** * */ class InvalidAccessTokenException extends InvalidArgumentException implements ExceptionInterface { } hybridauth/Exception/RuntimeException.php 0000644 00000000606 15104256626 0014671 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Exception; /** * RuntimeException * * Exception thrown if an error which can only be found on runtime occurs. */ class RuntimeException extends Exception implements ExceptionInterface { } hybridauth/Exception/InvalidOpenidIdentifierException.php 0000644 00000000506 15104256626 0017775 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Exception; /** * */ class InvalidOpenidIdentifierException extends InvalidArgumentException implements ExceptionInterface { } hybridauth/Exception/AuthorizationDeniedException.php 0000644 00000000502 15104256626 0017212 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Exception; /** * */ class AuthorizationDeniedException extends UnexpectedValueException implements ExceptionInterface { } hybridauth/Exception/InvalidAuthorizationCodeException.php 0000644 00000000507 15104256626 0020210 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Exception; /** * */ class InvalidAuthorizationCodeException extends InvalidArgumentException implements ExceptionInterface { } hybridauth/Exception/InvalidApplicationCredentialsException.php 0000644 00000000514 15104256626 0021174 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Exception; /** * */ class InvalidApplicationCredentialsException extends InvalidArgumentException implements ExceptionInterface { } hybridauth/Exception/InvalidOauthTokenException.php 0000644 00000000500 15104256626 0016627 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Exception; /** * */ class InvalidOauthTokenException extends InvalidArgumentException implements ExceptionInterface { } hybridauth/Exception/HttpRequestFailedException.php 0000644 00000000500 15104256626 0016634 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Exception; /** * */ class HttpRequestFailedException extends UnexpectedValueException implements ExceptionInterface { } hybridauth/Exception/NotImplementedException.php 0000644 00000000473 15104256626 0016174 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Exception; /** * */ class NotImplementedException extends BadMethodCallException implements ExceptionInterface { } hybridauth/Exception/UnexpectedValueException.php 0000644 00000001113 15104256626 0016341 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Exception; /** * UnexpectedValueException * * Exception thrown if a value does not match with a set of values. Typically this happens when a function calls * another function and expects the return value to be of a certain type or value not including arithmetic or * buffer related errors. */ class UnexpectedValueException extends RuntimeException implements ExceptionInterface { } hybridauth/Exception/InvalidAuthorizationStateException.php 0000644 00000000510 15104256626 0020410 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Exception; /** * */ class InvalidAuthorizationStateException extends InvalidArgumentException implements ExceptionInterface { } hybridauth/Exception/Exception.php 0000644 00000004075 15104256626 0013331 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Exception; /** * Hybridauth Base Exception */ class Exception extends \Exception implements ExceptionInterface { /** * Shamelessly Borrowed from Slimframework * * @param $object */ public function debug($object) { $title = 'Hybridauth Exception'; $code = $this->getCode(); $message = $this->getMessage(); $file = $this->getFile(); $line = $this->getLine(); $trace = $this->getTraceAsString(); $html = sprintf('<h1>%s</h1>', $title); $html .= '<p>Hybridauth has encountered the following error:</p>'; $html .= '<h2>Details</h2>'; $html .= sprintf('<div><strong>Exception:</strong> %s</div>', get_class($this)); $html .= sprintf('<div><strong>Message:</strong> <font color="#cc0000">%s</font></div>', $message); $html .= sprintf('<div><strong>File:</strong> %s</div>', $file); $html .= sprintf('<div><strong>Line:</strong> %s</div>', $line); $html .= sprintf('<div><strong>Code:</strong> %s</div>', $code); $html .= '<h2>Trace</h2>'; $html .= sprintf('<pre>%s</pre>', $trace); if ($object) { $html .= '<h2>Debug</h2>'; $obj_dump = print_r($object, true); // phpcs:ignore $html .= sprintf('<b>' . get_class($object) . '</b> extends <b>' . get_parent_class($object) . '</b><pre>%s</pre>', $obj_dump); } $html .= '<h2>Session</h2>'; $session_dump = print_r($_SESSION, true); $html .= sprintf('<pre>%s</pre>', $session_dump); // phpcs:ignore echo sprintf("<html><head><title>%s</title><style>body{margin:0;padding:30px;font:12px/1.5 Helvetica,Arial,Verdana,sans-serif;}h1{margin:0;font-size:48px;font-weight:normal;line-height:48px;}strong{display:inline-block;width:75px;}</style></head><body>%s</body></html>", $title, $html); } } hybridauth/Exception/InvalidArgumentException.php 0000644 00000000622 15104256626 0016335 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Exception; /** * InvalidArgumentException * * Exception thrown if an argument is not of the expected type. */ class InvalidArgumentException extends RuntimeException implements ExceptionInterface { } hybridauth/Exception/UnexpectedApiResponseException.php 0000644 00000000504 15104256626 0017520 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Exception; /** * */ class UnexpectedApiResponseException extends UnexpectedValueException implements ExceptionInterface { } hybridauth/Exception/HttpClientFailureException.php 0000644 00000000500 15104256626 0016625 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Exception; /** * */ class HttpClientFailureException extends UnexpectedValueException implements ExceptionInterface { } hybridauth/Exception/ExceptionInterface.php 0000644 00000003351 15104256626 0015146 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Exception; /** * Hybridauth Exceptions Interface */ interface ExceptionInterface { /* ExceptionInterface Exception extends \Exception implements ExceptionInterface | RuntimeException extends Exception | | UnexpectedValueException extends RuntimeException | | | AuthorizationDeniedException extends UnexpectedValueException | | | HttpClientFailureException extends UnexpectedValueException | | | HttpRequestFailedException extends UnexpectedValueException | | | InvalidAuthorizationCodeException extends UnexpectedValueException | | | InvalidAuthorizationStateException extends UnexpectedValueException | | | InvalidOauthTokenException extends UnexpectedValueException | | | InvalidAccessTokenException extends UnexpectedValueException | | | UnexpectedApiResponseException extends UnexpectedValueException | | | | BadMethodCallException extends RuntimeException | | | NotImplementedException extends BadMethodCallException | | | | InvalidArgumentException extends RuntimeException | | | InvalidApplicationCredentialsException extends InvalidArgumentException | | | InvalidOpenidIdentifierException extends InvalidArgumentException */ } hybridauth/Exception/BadMethodCallException.php 0000644 00000000660 15104256626 0015671 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Exception; /** * BadMethodCallException * * Exception thrown if a callback refers to an undefined method or if some arguments are missing. */ class BadMethodCallException extends RuntimeException implements ExceptionInterface { } hybridauth/version.txt 0000644 00000000006 15104256626 0011140 0 ustar 00 3.11.0 hybridauth/Adapter/AbstractAdapter.php 0000644 00000021031 15104256626 0014050 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Adapter; use Hybridauth\Exception\NotImplementedException; use Hybridauth\Exception\InvalidArgumentException; use Hybridauth\Exception\HttpClientFailureException; use Hybridauth\Exception\HttpRequestFailedException; use Hybridauth\Storage\StorageInterface; use Hybridauth\Storage\Session; use Hybridauth\Logger\LoggerInterface; use Hybridauth\Logger\Logger; use Hybridauth\HttpClient\HttpClientInterface; use Hybridauth\HttpClient\Curl as HttpClient; use Hybridauth\Data; /** * Class AbstractAdapter */ abstract class AbstractAdapter implements AdapterInterface { use DataStoreTrait; /** * Provider ID (unique name). * * @var string */ protected $providerId = ''; /** * Specific Provider config. * * @var mixed */ protected $config = []; /** * Extra Provider parameters. * * @var array */ protected $params; /** * Callback url * * @var string */ protected $callback = ''; /** * Storage. * * @var StorageInterface */ public $storage; /** * HttpClient. * * @var HttpClientInterface */ public $httpClient; /** * Logger. * * @var LoggerInterface */ public $logger; /** * Whether to validate API status codes of http responses * * @var bool */ protected $validateApiResponseHttpCode = true; /** * Common adapters constructor. * * @param array $config * @param HttpClientInterface $httpClient * @param StorageInterface $storage * @param LoggerInterface $logger */ public function __construct( $config = [], HttpClientInterface $httpClient = null, StorageInterface $storage = null, LoggerInterface $logger = null ) { $this->providerId = (new \ReflectionClass($this))->getShortName(); $this->config = new Data\Collection($config); $this->setHttpClient($httpClient); $this->setStorage($storage); $this->setLogger($logger); $this->configure(); $this->logger->debug(sprintf('Initialize %s, config: ', get_class($this)), $config); $this->initialize(); } /** * Load adapter's configuration */ abstract protected function configure(); /** * Adapter initializer */ abstract protected function initialize(); /** * {@inheritdoc} */ abstract public function isConnected(); /** * {@inheritdoc} */ public function apiRequest($url, $method = 'GET', $parameters = [], $headers = [], $multipart = false) { throw new NotImplementedException('Provider does not support this feature.'); } /** * {@inheritdoc} */ public function maintainToken() { // Nothing needed for most providers } /** * {@inheritdoc} */ public function getUserProfile() { throw new NotImplementedException('Provider does not support this feature.'); } /** * {@inheritdoc} */ public function getUserContacts() { throw new NotImplementedException('Provider does not support this feature.'); } /** * {@inheritdoc} */ public function getUserPages() { throw new NotImplementedException('Provider does not support this feature.'); } /** * {@inheritdoc} */ public function getUserActivity($stream) { throw new NotImplementedException('Provider does not support this feature.'); } /** * {@inheritdoc} */ public function setUserStatus($status) { throw new NotImplementedException('Provider does not support this feature.'); } /** * {@inheritdoc} */ public function setPageStatus($status, $pageId) { throw new NotImplementedException('Provider does not support this feature.'); } /** * {@inheritdoc} */ public function disconnect() { $this->clearStoredData(); } /** * {@inheritdoc} */ public function getAccessToken() { $tokenNames = [ 'access_token', 'access_token_secret', 'token_type', 'refresh_token', 'expires_in', 'expires_at', ]; $tokens = []; foreach ($tokenNames as $name) { if ($this->getStoredData($name)) { $tokens[$name] = $this->getStoredData($name); } } return $tokens; } /** * {@inheritdoc} */ public function setAccessToken($tokens = []) { $this->clearStoredData(); foreach ($tokens as $token => $value) { $this->storeData($token, $value); } // Re-initialize token parameters. $this->initialize(); } /** * {@inheritdoc} */ public function setHttpClient(HttpClientInterface $httpClient = null) { $this->httpClient = $httpClient ?: new HttpClient(); if ($this->config->exists('curl_options') && method_exists($this->httpClient, 'setCurlOptions')) { $this->httpClient->setCurlOptions($this->config->get('curl_options')); } } /** * {@inheritdoc} */ public function getHttpClient() { return $this->httpClient; } /** * {@inheritdoc} */ public function setStorage(StorageInterface $storage = null) { $this->storage = $storage ?: new Session(); } /** * {@inheritdoc} */ public function getStorage() { return $this->storage; } /** * {@inheritdoc} */ public function setLogger(LoggerInterface $logger = null) { $this->logger = $logger ?: new Logger( $this->config->get('debug_mode'), $this->config->get('debug_file') ); if (method_exists($this->httpClient, 'setLogger')) { $this->httpClient->setLogger($this->logger); } } /** * {@inheritdoc} */ public function getLogger() { return $this->logger; } /** * Set Adapter's API callback url * * @param string $callback * * @throws InvalidArgumentException */ protected function setCallback($callback) { if (!filter_var($callback, FILTER_VALIDATE_URL)) { throw new InvalidArgumentException('A valid callback url is required.'); } $this->callback = $callback; } /** * Overwrite Adapter's API endpoints * * @param array|Data\Collection $endpoints */ protected function setApiEndpoints($endpoints = null) { if (empty($endpoints)) { return; } $collection = is_array($endpoints) ? new Data\Collection($endpoints) : $endpoints; $this->apiBaseUrl = $collection->get('api_base_url') ?: $this->apiBaseUrl; $this->authorizeUrl = $collection->get('authorize_url') ?: $this->authorizeUrl; $this->accessTokenUrl = $collection->get('access_token_url') ?: $this->accessTokenUrl; } /** * Validate signed API responses Http status code. * * Since the specifics of error responses is beyond the scope of RFC6749 and OAuth Core specifications, * Hybridauth will consider any HTTP status code that is different than '200 OK' as an ERROR. * * @param string $error String to pre append to message thrown in exception * * @throws HttpClientFailureException * @throws HttpRequestFailedException */ protected function validateApiResponse($error = '') { $error .= !empty($error) ? '. ' : ''; if ($this->httpClient->getResponseClientError()) { throw new HttpClientFailureException( $error . 'HTTP client error: ' . $this->httpClient->getResponseClientError() . '.' ); } // if validateApiResponseHttpCode is set to false, we by pass verification of http status code if (!$this->validateApiResponseHttpCode) { return; } $status = $this->httpClient->getResponseHttpCode(); if ($status < 200 || $status > 299) { throw new HttpRequestFailedException( $error . 'HTTP error ' . $this->httpClient->getResponseHttpCode() . '. Raw Provider API response: ' . $this->httpClient->getResponseBody() . '.' ); } } } hybridauth/Adapter/OAuth2.php 0000644 00000054653 15104256626 0012126 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Adapter; use Hybridauth\Exception\Exception; use Hybridauth\Exception\InvalidApplicationCredentialsException; use Hybridauth\Exception\InvalidAuthorizationStateException; use Hybridauth\Exception\InvalidAuthorizationCodeException; use Hybridauth\Exception\AuthorizationDeniedException; use Hybridauth\Exception\InvalidAccessTokenException; use Hybridauth\Data; use Hybridauth\HttpClient; /** * This class can be used to simplify the authorization flow of OAuth 2 based service providers. * * Subclasses (i.e., providers adapters) can either use the already provided methods or override * them when necessary. */ abstract class OAuth2 extends AbstractAdapter implements AdapterInterface { /** * Client Identifier * * RFC6749: client_id REQUIRED. The client identifier issued to the client during * the registration process described by Section 2.2. * * http://tools.ietf.org/html/rfc6749#section-2.2 * * @var string */ protected $clientId = ''; /** * Client Secret * * RFC6749: client_secret REQUIRED. The client secret. The client MAY omit the * parameter if the client secret is an empty string. * * http://tools.ietf.org/html/rfc6749#section-2.2 * * @var string */ protected $clientSecret = ''; /** * Access Token Scope * * RFC6749: The authorization and token endpoints allow the client to specify the * scope of the access request using the "scope" request parameter. * * http://tools.ietf.org/html/rfc6749#section-3.3 * * @var string */ protected $scope = ''; /** * Base URL to provider API * * This var will be used to build urls when sending signed requests * * @var string */ protected $apiBaseUrl = ''; /** * Authorization Endpoint * * RFC6749: The authorization endpoint is used to interact with the resource * owner and obtain an authorization grant. * * http://tools.ietf.org/html/rfc6749#section-3.1 * * @var string */ protected $authorizeUrl = ''; /** * Access Token Endpoint * * RFC6749: The token endpoint is used by the client to obtain an access token by * presenting its authorization grant or refresh token. * * http://tools.ietf.org/html/rfc6749#section-3.2 * * @var string */ protected $accessTokenUrl = ''; /** * TokenInfo endpoint * * Access token validation. OPTIONAL. * * @var string */ protected $accessTokenInfoUrl = ''; /** * IPD API Documentation * * OPTIONAL. * * @var string */ protected $apiDocumentation = ''; /** * Redirection Endpoint or Callback * * RFC6749: After completing its interaction with the resource owner, the * authorization server directs the resource owner's user-agent back to * the client. * * http://tools.ietf.org/html/rfc6749#section-3.1.2 * * @var string */ protected $callback = ''; /** * Authorization Url Parameters * * @var array */ protected $AuthorizeUrlParameters = []; /** * Authorization Url Parameter encoding type * @see https://www.php.net/manual/de/function.http-build-query.php * * @var string */ protected $AuthorizeUrlParametersEncType = PHP_QUERY_RFC1738; /** * Authorization Request State * * @var bool */ protected $supportRequestState = true; /** * Access Token name * * While most providers will use 'access_token' as name for the Access Token attribute, other do not. * On the latter case, this should be set by sub classes. * * @var string */ protected $accessTokenName = 'access_token'; /** * Authorization Request HTTP method. * * @see exchangeCodeForAccessToken() * * @var string */ protected $tokenExchangeMethod = 'POST'; /** * Authorization Request URL parameters. * * Sub classes may change add any additional parameter when necessary. * * @see exchangeCodeForAccessToken() * * @var array */ protected $tokenExchangeParameters = []; /** * Authorization Request HTTP headers. * * Sub classes may add any additional header when necessary. * * @see exchangeCodeForAccessToken() * * @var array */ protected $tokenExchangeHeaders = []; /** * Refresh Token Request HTTP method. * * @see refreshAccessToken() * * @var string */ protected $tokenRefreshMethod = 'POST'; /** * Refresh Token Request URL parameters. * * Sub classes may change add any additional parameter when necessary. * * @see refreshAccessToken() * * @var array|null */ protected $tokenRefreshParameters = null; /** * Refresh Token Request HTTP headers. * * Sub classes may add any additional header when necessary. * * @see refreshAccessToken() * * @var array */ protected $tokenRefreshHeaders = []; /** * Authorization Request URL parameters. * * Sub classes may change add any additional parameter when necessary. * * @see apiRequest() * * @var array */ protected $apiRequestParameters = []; /** * Authorization Request HTTP headers. * * Sub classes may add any additional header when necessary. * * @see apiRequest() * * @var array */ protected $apiRequestHeaders = []; /** * {@inheritdoc} */ protected function configure() { $this->clientId = $this->config->filter('keys')->get('id') ?: $this->config->filter('keys')->get('key'); $this->clientSecret = $this->config->filter('keys')->get('secret'); if (!$this->clientId || !$this->clientSecret) { throw new InvalidApplicationCredentialsException( 'Your application id is required in order to connect to ' . $this->providerId ); } $this->scope = $this->config->exists('scope') ? $this->config->get('scope') : $this->scope; if ($this->config->exists('tokens')) { $this->setAccessToken($this->config->get('tokens')); } if ($this->config->exists('supportRequestState')) { $this->supportRequestState = $this->config->get('supportRequestState'); } $this->setCallback($this->config->get('callback')); $this->setApiEndpoints($this->config->get('endpoints')); } /** * {@inheritdoc} */ protected function initialize() { $this->AuthorizeUrlParameters = [ 'response_type' => 'code', 'client_id' => $this->clientId, 'redirect_uri' => $this->callback, 'scope' => $this->scope, ]; $this->tokenExchangeParameters = [ 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, 'grant_type' => 'authorization_code', 'redirect_uri' => $this->callback ]; $refreshToken = $this->getStoredData('refresh_token'); if (!empty($refreshToken)) { $this->tokenRefreshParameters = [ 'grant_type' => 'refresh_token', 'refresh_token' => $refreshToken, ]; } $this->apiRequestHeaders = [ 'Authorization' => 'Bearer ' . $this->getStoredData('access_token') ]; } /** * {@inheritdoc} */ public function authenticate() { $this->logger->info(sprintf('%s::authenticate()', get_class($this))); if ($this->isConnected()) { return true; } try { $this->authenticateCheckError(); $code = filter_input($_SERVER['REQUEST_METHOD'] === 'POST' ? INPUT_POST : INPUT_GET, 'code'); if (empty($code)) { $this->authenticateBegin(); } else { $this->authenticateFinish(); } } catch (Exception $e) { $this->clearStoredData(); throw $e; } return null; } /** * {@inheritdoc} */ public function isConnected() { if ((bool)$this->getStoredData('access_token')) { return (!$this->hasAccessTokenExpired() || $this->isRefreshTokenAvailable()); } return false; } /** * If we can use a refresh token, then an expired token does not stop us being connected. * * @return bool */ public function isRefreshTokenAvailable() { return is_array($this->tokenRefreshParameters); } /** * Authorization Request Error Response * * RFC6749: If the request fails due to a missing, invalid, or mismatching * redirection URI, or if the client identifier is missing or invalid, * the authorization server SHOULD inform the resource owner of the error. * * http://tools.ietf.org/html/rfc6749#section-4.1.2.1 * * @throws \Hybridauth\Exception\InvalidAuthorizationCodeException * @throws \Hybridauth\Exception\AuthorizationDeniedException */ protected function authenticateCheckError() { $error = filter_input(INPUT_GET, 'error', FILTER_SANITIZE_SPECIAL_CHARS); if (!empty($error)) { $error_description = filter_input(INPUT_GET, 'error_description', FILTER_SANITIZE_SPECIAL_CHARS); $error_uri = filter_input(INPUT_GET, 'error_uri', FILTER_SANITIZE_SPECIAL_CHARS); $collated_error = sprintf('Provider returned an error: %s %s %s', $error, $error_description, $error_uri); if ($error == 'access_denied') { throw new AuthorizationDeniedException($collated_error); } throw new InvalidAuthorizationCodeException($collated_error); } } /** * Initiate the authorization protocol * * Build Authorization URL for Authorization Request and redirect the user-agent to the * Authorization Server. */ protected function authenticateBegin() { $authUrl = $this->getAuthorizeUrl(); $this->logger->debug(sprintf('%s::authenticateBegin(), redirecting user to:', get_class($this)), [$authUrl]); HttpClient\Util::redirect($authUrl); } /** * Finalize the authorization process * * @throws \Hybridauth\Exception\HttpClientFailureException * @throws \Hybridauth\Exception\HttpRequestFailedException * @throws InvalidAccessTokenException * @throws InvalidAuthorizationStateException */ protected function authenticateFinish() { $this->logger->debug( sprintf('%s::authenticateFinish(), callback url:', get_class($this)), [HttpClient\Util::getCurrentUrl(true)] ); $state = filter_input($_SERVER['REQUEST_METHOD'] === 'POST' ? INPUT_POST : INPUT_GET, 'state'); $code = filter_input($_SERVER['REQUEST_METHOD'] === 'POST' ? INPUT_POST : INPUT_GET, 'code'); /** * Authorization Request State * * RFC6749: state : RECOMMENDED. An opaque value used by the client to maintain * state between the request and callback. The authorization server includes * this value when redirecting the user-agent back to the client. * * http://tools.ietf.org/html/rfc6749#section-4.1.1 */ if ($this->supportRequestState && (!$state || $this->getStoredData('authorization_state') != $state) ) { $this->deleteStoredData('authorization_state'); throw new InvalidAuthorizationStateException( 'The authorization state [state=' . substr(htmlentities($state), 0, 100) . '] ' . 'of this page is either invalid or has already been consumed.' ); } /** * Authorization Request Code * * RFC6749: If the resource owner grants the access request, the authorization * server issues an authorization code and delivers it to the client: * * http://tools.ietf.org/html/rfc6749#section-4.1.2 */ $response = $this->exchangeCodeForAccessToken($code); $this->validateAccessTokenExchange($response); $this->initialize(); } /** * Build Authorization URL for Authorization Request * * RFC6749: The client constructs the request URI by adding the following * $parameters to the query component of the authorization endpoint URI: * * - response_type REQUIRED. Value MUST be set to "code". * - client_id REQUIRED. * - redirect_uri OPTIONAL. * - scope OPTIONAL. * - state RECOMMENDED. * * http://tools.ietf.org/html/rfc6749#section-4.1.1 * * Sub classes may redefine this method when necessary. * * @param array $parameters * * @return string Authorization URL */ protected function getAuthorizeUrl($parameters = []) { $this->AuthorizeUrlParameters = !empty($parameters) ? $parameters : array_replace( (array)$this->AuthorizeUrlParameters, (array)$this->config->get('authorize_url_parameters') ); if ($this->supportRequestState) { if (!isset($this->AuthorizeUrlParameters['state'])) { $this->AuthorizeUrlParameters['state'] = 'HA-' . str_shuffle('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'); } $this->storeData('authorization_state', $this->AuthorizeUrlParameters['state']); } $queryParams = http_build_query($this->AuthorizeUrlParameters, '', '&', $this->AuthorizeUrlParametersEncType); return $this->authorizeUrl . '?' . $queryParams; } /** * Access Token Request * * This method will exchange the received $code in loginFinish() with an Access Token. * * RFC6749: The client makes a request to the token endpoint by sending the * following parameters using the "application/x-www-form-urlencoded" * with a character encoding of UTF-8 in the HTTP request entity-body: * * - grant_type REQUIRED. Value MUST be set to "authorization_code". * - code REQUIRED. The authorization code received from the authorization server. * - redirect_uri REQUIRED. * - client_id REQUIRED. * * http://tools.ietf.org/html/rfc6749#section-4.1.3 * * @param string $code * * @return string Raw Provider API response * @throws \Hybridauth\Exception\HttpClientFailureException * @throws \Hybridauth\Exception\HttpRequestFailedException */ protected function exchangeCodeForAccessToken($code) { $this->tokenExchangeParameters['code'] = $code; $response = $this->httpClient->request( $this->accessTokenUrl, $this->tokenExchangeMethod, $this->tokenExchangeParameters, $this->tokenExchangeHeaders ); $this->validateApiResponse('Unable to exchange code for API access token'); return $response; } /** * Validate Access Token Response * * RFC6749: If the access token request is valid and authorized, the * authorization server issues an access token and optional refresh token. * If the request client authentication failed or is invalid, the authorization * server returns an error response as described in Section 5.2. * * Example of a successful response: * * HTTP/1.1 200 OK * Content-Type: application/json;charset=UTF-8 * Cache-Control: no-store * Pragma: no-cache * * { * "access_token":"2YotnFZFEjr1zCsicMWpAA", * "token_type":"example", * "expires_in":3600, * "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA", * "example_parameter":"example_value" * } * * http://tools.ietf.org/html/rfc6749#section-4.1.4 * * This method uses Data_Parser to attempt to decodes the raw $response (usually JSON) * into a data collection. * * @param string $response * * @return \Hybridauth\Data\Collection * @throws InvalidAccessTokenException */ protected function validateAccessTokenExchange($response) { $data = (new Data\Parser())->parse($response); $collection = new Data\Collection($data); if (!$collection->exists('access_token')) { throw new InvalidAccessTokenException( 'Provider returned no access_token: ' . htmlentities($response) ); } $this->storeData('access_token', $collection->get('access_token')); $this->storeData('token_type', $collection->get('token_type')); if ($collection->get('refresh_token')) { $this->storeData('refresh_token', $collection->get('refresh_token')); } // calculate when the access token expire if ($collection->exists('expires_in')) { $expires_at = time() + (int)$collection->get('expires_in'); $this->storeData('expires_in', $collection->get('expires_in')); $this->storeData('expires_at', $expires_at); } $this->deleteStoredData('authorization_state'); $this->initialize(); return $collection; } /** * Refreshing an Access Token * * RFC6749: If the authorization server issued a refresh token to the * client, the client makes a refresh request to the token endpoint by * adding the following parameters ... in the HTTP request entity-body: * * - grant_type REQUIRED. Value MUST be set to "refresh_token". * - refresh_token REQUIRED. The refresh token issued to the client. * - scope OPTIONAL. * * http://tools.ietf.org/html/rfc6749#section-6 * * This method is similar to exchangeCodeForAccessToken(). The only * difference is here we exchange refresh_token for a new access_token. * * @param array $parameters * * @return string|null Raw Provider API response, or null if we cannot refresh * @throws \Hybridauth\Exception\HttpClientFailureException * @throws \Hybridauth\Exception\HttpRequestFailedException * @throws InvalidAccessTokenException */ public function refreshAccessToken($parameters = []) { $this->tokenRefreshParameters = !empty($parameters) ? $parameters : $this->tokenRefreshParameters; if (!$this->isRefreshTokenAvailable()) { return null; } $response = $this->httpClient->request( $this->accessTokenUrl, $this->tokenRefreshMethod, $this->tokenRefreshParameters, $this->tokenRefreshHeaders ); $this->validateApiResponse('Unable to refresh the access token'); $this->validateRefreshAccessToken($response); return $response; } /** * Check whether access token has expired * * @param int|null $time * @return bool|null */ public function hasAccessTokenExpired($time = null) { if ($time === null) { $time = time(); } $expires_at = $this->getStoredData('expires_at'); if (!$expires_at) { return null; } return $expires_at <= $time; } /** * Validate Refresh Access Token Request * * RFC6749: If valid and authorized, the authorization server issues an * access token as described in Section 5.1. If the request failed * verification or is invalid, the authorization server returns an error * response as described in Section 5.2. * * http://tools.ietf.org/html/rfc6749#section-6 * http://tools.ietf.org/html/rfc6749#section-5.1 * http://tools.ietf.org/html/rfc6749#section-5.2 * * This method simply use validateAccessTokenExchange(), however sub * classes may redefine it when necessary. * * @param $response * * @return \Hybridauth\Data\Collection * @throws InvalidAccessTokenException */ protected function validateRefreshAccessToken($response) { return $this->validateAccessTokenExchange($response); } /** * Send a signed request to provider API * * RFC6749: Accessing Protected Resources: The client accesses protected * resources by presenting the access token to the resource server. The * resource server MUST validate the access token and ensure that it has * not expired and that its scope covers the requested resource. * * Note: Since the specifics of error responses is beyond the scope of * RFC6749 and OAuth specifications, Hybridauth will consider any HTTP * status code that is different than '200 OK' as an ERROR. * * http://tools.ietf.org/html/rfc6749#section-7 * * @param string $url * @param string $method * @param array $parameters * @param array $headers * @param bool $multipart * * @return mixed * @throws \Hybridauth\Exception\HttpClientFailureException * @throws \Hybridauth\Exception\HttpRequestFailedException * @throws InvalidAccessTokenException */ public function apiRequest($url, $method = 'GET', $parameters = [], $headers = [], $multipart = false) { // refresh tokens if needed $this->maintainToken(); if ($this->hasAccessTokenExpired() === true) { $this->refreshAccessToken(); } if (strrpos($url, 'http://') !== 0 && strrpos($url, 'https://') !== 0) { $url = rtrim($this->apiBaseUrl, '/') . '/' . ltrim($url, '/'); } $parameters = array_replace($this->apiRequestParameters, (array)$parameters); $headers = array_replace($this->apiRequestHeaders, (array)$headers); $response = $this->httpClient->request( $url, $method, // HTTP Request Method. Defaults to GET. $parameters, // Request Parameters $headers, // Request Headers $multipart // Is request multipart ); $this->validateApiResponse('Signed API request to ' . $url . ' has returned an error'); $response = (new Data\Parser())->parse($response); return $response; } } hybridauth/Adapter/OpenID.php 0000644 00000017362 15104256626 0012136 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Adapter; use Hybridauth\Exception\InvalidOpenidIdentifierException; use Hybridauth\Exception\AuthorizationDeniedException; use Hybridauth\Exception\UnexpectedApiResponseException; use Hybridauth\Data; use Hybridauth\HttpClient; use Hybridauth\User; use Hybridauth\Thirdparty\OpenID\LightOpenID; /** * This class can be used to simplify the authentication flow of OpenID based service providers. * * Subclasses (i.e., providers adapters) can either use the already provided methods or override * them when necessary. */ abstract class OpenID extends AbstractAdapter implements AdapterInterface { /** * LightOpenID instance * * @var object */ protected $openIdClient = null; /** * Openid provider identifier * * @var string */ protected $openidIdentifier = ''; /** * IPD API Documentation * * OPTIONAL. * * @var string */ protected $apiDocumentation = ''; /** * {@inheritdoc} */ protected function configure() { if ($this->config->exists('openid_identifier')) { $this->openidIdentifier = $this->config->get('openid_identifier'); } if (empty($this->openidIdentifier)) { throw new InvalidOpenidIdentifierException('OpenID adapter requires an openid_identifier.', 4); } $this->setCallback($this->config->get('callback')); $this->setApiEndpoints($this->config->get('endpoints')); } /** * {@inheritdoc} */ protected function initialize() { $hostPort = parse_url($this->callback, PHP_URL_PORT); $hostUrl = parse_url($this->callback, PHP_URL_HOST); if ($hostPort) { $hostUrl .= ':' . $hostPort; } // @fixme: add proxy $this->openIdClient = new LightOpenID($hostUrl, null); } /** * {@inheritdoc} */ public function authenticate() { $this->logger->info(sprintf('%s::authenticate()', get_class($this))); if ($this->isConnected()) { return true; } if (empty($_REQUEST['openid_mode'])) { $this->authenticateBegin(); } else { return $this->authenticateFinish(); } return null; } /** * {@inheritdoc} */ public function isConnected() { return (bool)$this->storage->get($this->providerId . '.user'); } /** * {@inheritdoc} */ public function disconnect() { $this->storage->delete($this->providerId . '.user'); return true; } /** * Initiate the authorization protocol * * Include and instantiate LightOpenID */ protected function authenticateBegin() { $this->openIdClient->identity = $this->openidIdentifier; $this->openIdClient->returnUrl = $this->callback; $this->openIdClient->required = [ 'namePerson/first', 'namePerson/last', 'namePerson/friendly', 'namePerson', 'contact/email', 'birthDate', 'birthDate/birthDay', 'birthDate/birthMonth', 'birthDate/birthYear', 'person/gender', 'pref/language', 'contact/postalCode/home', 'contact/city/home', 'contact/country/home', 'media/image/default', ]; $authUrl = $this->openIdClient->authUrl(); $this->logger->debug(sprintf('%s::authenticateBegin(), redirecting user to:', get_class($this)), [$authUrl]); HttpClient\Util::redirect($authUrl); } /** * Finalize the authorization process. * * @throws AuthorizationDeniedException * @throws UnexpectedApiResponseException */ protected function authenticateFinish() { $this->logger->debug( sprintf('%s::authenticateFinish(), callback url:', get_class($this)), [HttpClient\Util::getCurrentUrl(true)] ); if ($this->openIdClient->mode == 'cancel') { throw new AuthorizationDeniedException('User has cancelled the authentication.'); } if (!$this->openIdClient->validate()) { throw new UnexpectedApiResponseException('Invalid response received.'); } $openidAttributes = $this->openIdClient->getAttributes(); if (!$this->openIdClient->identity) { throw new UnexpectedApiResponseException('Provider returned an unexpected response.'); } $userProfile = $this->fetchUserProfile($openidAttributes); /* with openid providers we only get user profiles once, so we store it */ $this->storage->set($this->providerId . '.user', $userProfile); } /** * Fetch user profile from received openid attributes * * @param array $openidAttributes * * @return User\Profile */ protected function fetchUserProfile($openidAttributes) { $data = new Data\Collection($openidAttributes); $userProfile = new User\Profile(); $userProfile->identifier = $this->openIdClient->identity; $userProfile->firstName = $data->get('namePerson/first'); $userProfile->lastName = $data->get('namePerson/last'); $userProfile->email = $data->get('contact/email'); $userProfile->language = $data->get('pref/language'); $userProfile->country = $data->get('contact/country/home'); $userProfile->zip = $data->get('contact/postalCode/home'); $userProfile->gender = $data->get('person/gender'); $userProfile->photoURL = $data->get('media/image/default'); $userProfile->birthDay = $data->get('birthDate/birthDay'); $userProfile->birthMonth = $data->get('birthDate/birthMonth'); $userProfile->birthYear = $data->get('birthDate/birthDate'); $userProfile = $this->fetchUserGender($userProfile, $data->get('person/gender')); $userProfile = $this->fetchUserDisplayName($userProfile, $data); return $userProfile; } /** * Extract users display names * * @param User\Profile $userProfile * @param Data\Collection $data * * @return User\Profile */ protected function fetchUserDisplayName(User\Profile $userProfile, Data\Collection $data) { $userProfile->displayName = $data->get('namePerson'); $userProfile->displayName = $userProfile->displayName ? $userProfile->displayName : $data->get('namePerson/friendly'); $userProfile->displayName = $userProfile->displayName ? $userProfile->displayName : trim($userProfile->firstName . ' ' . $userProfile->lastName); return $userProfile; } /** * Extract users gender * * @param User\Profile $userProfile * @param string $gender * * @return User\Profile */ protected function fetchUserGender(User\Profile $userProfile, $gender) { $gender = strtolower((string)$gender); if ('f' == $gender) { $gender = 'female'; } if ('m' == $gender) { $gender = 'male'; } $userProfile->gender = $gender; return $userProfile; } /** * OpenID only provide the user profile one. This method will attempt to retrieve the profile from storage. */ public function getUserProfile() { $userProfile = $this->storage->get($this->providerId . '.user'); if (!is_object($userProfile)) { throw new UnexpectedApiResponseException('Provider returned an unexpected response.'); } return $userProfile; } } hybridauth/Adapter/DataStoreTrait.php 0000644 00000003640 15104256626 0013704 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Adapter; /** * Trait DataStoreTrait */ trait DataStoreTrait { /** * Returns storage instance * * @return \Hybridauth\Storage\StorageInterface */ abstract public function getStorage(); /** * Store a piece of data in storage. * * This method is mainly used for OAuth tokens (access, secret, refresh, and whatnot), but it * can be also used by providers to store any other useful data (i.g., user_id, auth_nonce, etc.) * * @param string $name * @param mixed $value */ protected function storeData($name, $value = null) { // if empty, we simply delete the thing as we'd want to only store necessary data if (empty($value)) { $this->deleteStoredData($name); } $this->getStorage()->set($this->providerId . '.' . $name, $value); } /** * Retrieve a piece of data from storage. * * This method is mainly used for OAuth tokens (access, secret, refresh, and whatnot), but it * can be also used by providers to retrieve from store any other useful data (i.g., user_id, * auth_nonce, etc.) * * @param string $name * * @return mixed */ protected function getStoredData($name) { return $this->getStorage()->get($this->providerId . '.' . $name); } /** * Delete a stored piece of data. * * @param string $name */ protected function deleteStoredData($name) { $this->getStorage()->delete($this->providerId . '.' . $name); } /** * Delete all stored data of the instantiated adapter */ protected function clearStoredData() { $this->getStorage()->deleteMatch($this->providerId . '.'); } } hybridauth/Adapter/AdapterInterface.php 0000644 00000006520 15104256626 0014213 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Adapter; use Hybridauth\HttpClient\HttpClientInterface; use Hybridauth\Storage\StorageInterface; use Hybridauth\Logger\LoggerInterface; /** * Interface AdapterInterface */ interface AdapterInterface { /** * Initiate the appropriate protocol and process/automate the authentication or authorization flow. * * @return bool|null */ public function authenticate(); /** * Returns TRUE if the user is connected * * @return bool */ public function isConnected(); /** * Clear all access token in storage */ public function disconnect(); /** * Retrieve the connected user profile * * @return \Hybridauth\User\Profile */ public function getUserProfile(); /** * Retrieve the connected user contacts list * * @return \Hybridauth\User\Contact[] */ public function getUserContacts(); /** * Retrieve the connected user pages|companies|groups list * * @return array */ public function getUserPages(); /** * Retrieve the user activity stream * * @param string $stream * * @return \Hybridauth\User\Activity[] */ public function getUserActivity($stream); /** * Post a status on user wall|timeline|blog|website|etc. * * @param string|array $status * * @return mixed API response */ public function setUserStatus($status); /** * Post a status on page|company|group wall. * * @param string|array $status * @param string $pageId * * @return mixed API response */ public function setPageStatus($status, $pageId); /** * Send a signed request to provider API * * @param string $url * @param string $method * @param array $parameters * @param array $headers * @param bool $multipart * * @return mixed */ public function apiRequest($url, $method = 'GET', $parameters = [], $headers = [], $multipart = false); /** * Do whatever may be necessary to make sure tokens do not expire. * Intended to be be called frequently, e.g. via Cron. */ public function maintainToken(); /** * Return oauth access tokens. * * @return array */ public function getAccessToken(); /** * Set oauth access tokens. * * @param array $tokens */ public function setAccessToken($tokens = []); /** * Set http client instance. * * @param HttpClientInterface $httpClient */ public function setHttpClient(HttpClientInterface $httpClient = null); /** * Return http client instance. */ public function getHttpClient(); /** * Set storage instance. * * @param StorageInterface $storage */ public function setStorage(StorageInterface $storage = null); /** * Return storage instance. */ public function getStorage(); /** * Set Logger instance. * * @param LoggerInterface $logger */ public function setLogger(LoggerInterface $logger = null); /** * Return logger instance. */ public function getLogger(); } hybridauth/Adapter/OAuth1.php 0000644 00000044121 15104256626 0012112 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Adapter; use Hybridauth\Exception\Exception; use Hybridauth\Exception\InvalidApplicationCredentialsException; use Hybridauth\Exception\AuthorizationDeniedException; use Hybridauth\Exception\InvalidOauthTokenException; use Hybridauth\Exception\InvalidAccessTokenException; use Hybridauth\Data; use Hybridauth\HttpClient; use Hybridauth\Thirdparty\OAuth\OAuthConsumer; use Hybridauth\Thirdparty\OAuth\OAuthRequest; use Hybridauth\Thirdparty\OAuth\OAuthSignatureMethodHMACSHA1; use Hybridauth\Thirdparty\OAuth\OAuthUtil; /** * This class can be used to simplify the authorization flow of OAuth 1 based service providers. * * Subclasses (i.e., providers adapters) can either use the already provided methods or override * them when necessary. */ abstract class OAuth1 extends AbstractAdapter implements AdapterInterface { /** * Base URL to provider API * * This var will be used to build urls when sending signed requests * * @var string */ protected $apiBaseUrl = ''; /** * @var string */ protected $authorizeUrl = ''; /** * @var string */ protected $requestTokenUrl = ''; /** * @var string */ protected $accessTokenUrl = ''; /** * IPD API Documentation * * OPTIONAL. * * @var string */ protected $apiDocumentation = ''; /** * OAuth Version * * '1.0' OAuth Core 1.0 * '1.0a' OAuth Core 1.0 Revision A * * @var string */ protected $oauth1Version = '1.0a'; /** * @var string */ protected $consumerKey = null; /** * @var string */ protected $consumerSecret = null; /** * @var object */ protected $OAuthConsumer = null; /** * @var object */ protected $sha1Method = null; /** * @var object */ protected $consumerToken = null; /** * Authorization Url Parameters * * @var bool */ protected $AuthorizeUrlParameters = []; /** * @var string */ protected $requestTokenMethod = 'POST'; /** * @var array */ protected $requestTokenParameters = []; /** * @var array */ protected $requestTokenHeaders = []; /** * @var string */ protected $tokenExchangeMethod = 'POST'; /** * @var array */ protected $tokenExchangeParameters = []; /** * @var array */ protected $tokenExchangeHeaders = []; /** * @var array */ protected $apiRequestParameters = []; /** * @var array */ protected $apiRequestHeaders = []; /** * {@inheritdoc} */ protected function configure() { $this->consumerKey = $this->config->filter('keys')->get('id') ?: $this->config->filter('keys')->get('key'); $this->consumerSecret = $this->config->filter('keys')->get('secret'); if (!$this->consumerKey || !$this->consumerSecret) { throw new InvalidApplicationCredentialsException( 'Your application id is required in order to connect to ' . $this->providerId ); } if ($this->config->exists('tokens')) { $this->setAccessToken($this->config->get('tokens')); } $this->setCallback($this->config->get('callback')); $this->setApiEndpoints($this->config->get('endpoints')); } /** * {@inheritdoc} */ protected function initialize() { /** * Set up OAuth Signature and Consumer * * OAuth Core: All Token requests and Protected Resources requests MUST be signed * by the Consumer and verified by the Service Provider. * * The protocol defines three signature methods: HMAC-SHA1, RSA-SHA1, and PLAINTEXT.. * * The Consumer declares a signature method in the oauth_signature_method parameter.. * * http://oauth.net/core/1.0a/#signing_process */ $this->sha1Method = new OAuthSignatureMethodHMACSHA1(); $this->OAuthConsumer = new OAuthConsumer( $this->consumerKey, $this->consumerSecret ); if ($this->getStoredData('request_token')) { $this->consumerToken = new OAuthConsumer( $this->getStoredData('request_token'), $this->getStoredData('request_token_secret') ); } if ($this->getStoredData('access_token')) { $this->consumerToken = new OAuthConsumer( $this->getStoredData('access_token'), $this->getStoredData('access_token_secret') ); } } /** * {@inheritdoc} */ public function authenticate() { $this->logger->info(sprintf('%s::authenticate()', get_class($this))); if ($this->isConnected()) { return true; } try { if (!$this->getStoredData('request_token')) { // Start a new flow. $this->authenticateBegin(); } elseif (empty($_GET['oauth_token']) && empty($_GET['denied'])) { // A previous authentication was not finished, and this request is not finishing it. $this->authenticateBegin(); } else { // Finish a flow. $this->authenticateFinish(); } } catch (Exception $exception) { $this->clearStoredData(); throw $exception; } return null; } /** * {@inheritdoc} */ public function isConnected() { return (bool)$this->getStoredData('access_token'); } /** * Initiate the authorization protocol * * 1. Obtaining an Unauthorized Request Token * 2. Build Authorization URL for Authorization Request and redirect the user-agent to the * Authorization Server. */ protected function authenticateBegin() { $response = $this->requestAuthToken(); $this->validateAuthTokenRequest($response); $authUrl = $this->getAuthorizeUrl(); $this->logger->debug(sprintf('%s::authenticateBegin(), redirecting user to:', get_class($this)), [$authUrl]); HttpClient\Util::redirect($authUrl); } /** * Finalize the authorization process * * @throws AuthorizationDeniedException * @throws \Hybridauth\Exception\HttpClientFailureException * @throws \Hybridauth\Exception\HttpRequestFailedException * @throws InvalidAccessTokenException * @throws InvalidOauthTokenException */ protected function authenticateFinish() { $this->logger->debug( sprintf('%s::authenticateFinish(), callback url:', get_class($this)), [HttpClient\Util::getCurrentUrl(true)] ); $denied = filter_input(INPUT_GET, 'denied'); $oauth_problem = filter_input(INPUT_GET, 'oauth_problem'); $oauth_token = filter_input(INPUT_GET, 'oauth_token'); $oauth_verifier = filter_input(INPUT_GET, 'oauth_verifier'); if ($denied) { throw new AuthorizationDeniedException( 'User denied access request. Provider returned a denied token: ' . htmlentities($denied) ); } if ($oauth_problem) { throw new InvalidOauthTokenException( 'Provider returned an error. oauth_problem: ' . htmlentities($oauth_problem) ); } if (!$oauth_token) { throw new InvalidOauthTokenException( 'Expecting a non-null oauth_token to continue the authorization flow.' ); } $response = $this->exchangeAuthTokenForAccessToken($oauth_token, $oauth_verifier); $this->validateAccessTokenExchange($response); $this->initialize(); } /** * Build Authorization URL for Authorization Request * * @param array $parameters * * @return string */ protected function getAuthorizeUrl($parameters = []) { $this->AuthorizeUrlParameters = !empty($parameters) ? $parameters : array_replace( (array)$this->AuthorizeUrlParameters, (array)$this->config->get('authorize_url_parameters') ); $this->AuthorizeUrlParameters['oauth_token'] = $this->getStoredData('request_token'); return $this->authorizeUrl . '?' . http_build_query($this->AuthorizeUrlParameters, '', '&'); } /** * Unauthorized Request Token * * OAuth Core: The Consumer obtains an unauthorized Request Token by asking the Service Provider * to issue a Token. The Request Token's sole purpose is to receive User approval and can only * be used to obtain an Access Token. * * http://oauth.net/core/1.0/#auth_step1 * 6.1.1. Consumer Obtains a Request Token * * @return string Raw Provider API response * @throws \Hybridauth\Exception\HttpClientFailureException * @throws \Hybridauth\Exception\HttpRequestFailedException */ protected function requestAuthToken() { /** * OAuth Core 1.0 Revision A: oauth_callback: An absolute URL to which the Service Provider will redirect * the User back when the Obtaining User Authorization step is completed. * * http://oauth.net/core/1.0a/#auth_step1 */ if ('1.0a' == $this->oauth1Version) { $this->requestTokenParameters['oauth_callback'] = $this->callback; } $response = $this->oauthRequest( $this->requestTokenUrl, $this->requestTokenMethod, $this->requestTokenParameters, $this->requestTokenHeaders ); return $response; } /** * Validate Unauthorized Request Token Response * * OAuth Core: The Service Provider verifies the signature and Consumer Key. If successful, * it generates a Request Token and Token Secret and returns them to the Consumer in the HTTP * response body. * * http://oauth.net/core/1.0/#auth_step1 * 6.1.2. Service Provider Issues an Unauthorized Request Token * * @param string $response * * @return \Hybridauth\Data\Collection * @throws InvalidOauthTokenException */ protected function validateAuthTokenRequest($response) { /** * The response contains the following parameters: * * - oauth_token The Request Token. * - oauth_token_secret The Token Secret. * - oauth_callback_confirmed MUST be present and set to true. * * http://oauth.net/core/1.0/#auth_step1 * 6.1.2. Service Provider Issues an Unauthorized Request Token * * Example of a successful response: * * HTTP/1.1 200 OK * Content-Type: text/html; charset=utf-8 * Cache-Control: no-store * Pragma: no-cache * * oauth_token=80359084-clg1DEtxQF3wstTcyUdHF3wsdHM&oauth_token_secret=OIF07hPmJB:P * 6qiHTi1znz6qiH3tTcyUdHnz6qiH3tTcyUdH3xW3wsDvV08e&example_parameter=example_value * * OAuthUtil::parse_parameters will attempt to decode the raw response into an array. */ $tokens = OAuthUtil::parse_parameters($response); $collection = new Data\Collection($tokens); if (!$collection->exists('oauth_token')) { throw new InvalidOauthTokenException( 'Provider returned no oauth_token: ' . htmlentities($response) ); } $this->consumerToken = new OAuthConsumer( $tokens['oauth_token'], $tokens['oauth_token_secret'] ); $this->storeData('request_token', $tokens['oauth_token']); $this->storeData('request_token_secret', $tokens['oauth_token_secret']); return $collection; } /** * Requests an Access Token * * OAuth Core: The Request Token and Token Secret MUST be exchanged for an Access Token and Token Secret. * * http://oauth.net/core/1.0a/#auth_step3 * 6.3.1. Consumer Requests an Access Token * * @param string $oauth_token * @param string $oauth_verifier * * @return string Raw Provider API response * @throws \Hybridauth\Exception\HttpClientFailureException * @throws \Hybridauth\Exception\HttpRequestFailedException */ protected function exchangeAuthTokenForAccessToken($oauth_token, $oauth_verifier = '') { $this->tokenExchangeParameters['oauth_token'] = $oauth_token; /** * OAuth Core 1.0 Revision A: oauth_verifier: The verification code received from the Service Provider * in the "Service Provider Directs the User Back to the Consumer" step. * * http://oauth.net/core/1.0a/#auth_step3 */ if ('1.0a' == $this->oauth1Version) { $this->tokenExchangeParameters['oauth_verifier'] = $oauth_verifier; } $response = $this->oauthRequest( $this->accessTokenUrl, $this->tokenExchangeMethod, $this->tokenExchangeParameters, $this->tokenExchangeHeaders ); return $response; } /** * Validate Access Token Response * * OAuth Core: If successful, the Service Provider generates an Access Token and Token Secret and returns * them in the HTTP response body. * * The Access Token and Token Secret are stored by the Consumer and used when signing Protected Resources requests. * * http://oauth.net/core/1.0a/#auth_step3 * 6.3.2. Service Provider Grants an Access Token * * @param string $response * * @return \Hybridauth\Data\Collection * @throws InvalidAccessTokenException */ protected function validateAccessTokenExchange($response) { /** * The response contains the following parameters: * * - oauth_token The Access Token. * - oauth_token_secret The Token Secret. * * http://oauth.net/core/1.0/#auth_step3 * 6.3.2. Service Provider Grants an Access Token * * Example of a successful response: * * HTTP/1.1 200 OK * Content-Type: text/html; charset=utf-8 * Cache-Control: no-store * Pragma: no-cache * * oauth_token=sHeLU7Far428zj8PzlWR75&oauth_token_secret=fXb30rzoG&oauth_callback_confirmed=true * * OAuthUtil::parse_parameters will attempt to decode the raw response into an array. */ $tokens = OAuthUtil::parse_parameters($response); $collection = new Data\Collection($tokens); if (!$collection->exists('oauth_token')) { throw new InvalidAccessTokenException( 'Provider returned no access_token: ' . htmlentities($response) ); } $this->consumerToken = new OAuthConsumer( $collection->get('oauth_token'), $collection->get('oauth_token_secret') ); $this->storeData('access_token', $collection->get('oauth_token')); $this->storeData('access_token_secret', $collection->get('oauth_token_secret')); $this->deleteStoredData('request_token'); $this->deleteStoredData('request_token_secret'); return $collection; } /** * Send a signed request to provider API * * Note: Since the specifics of error responses is beyond the scope of RFC6749 and OAuth specifications, * Hybridauth will consider any HTTP status code that is different than '200 OK' as an ERROR. * * @param string $url * @param string $method * @param array $parameters * @param array $headers * @param bool $multipart * * @return mixed * @throws \Hybridauth\Exception\HttpClientFailureException * @throws \Hybridauth\Exception\HttpRequestFailedException */ public function apiRequest($url, $method = 'GET', $parameters = [], $headers = [], $multipart = false) { // refresh tokens if needed $this->maintainToken(); if (strrpos($url, 'http://') !== 0 && strrpos($url, 'https://') !== 0) { $url = rtrim($this->apiBaseUrl, '/') . '/' . ltrim($url, '/'); } $parameters = array_replace($this->apiRequestParameters, (array)$parameters); $headers = array_replace($this->apiRequestHeaders, (array)$headers); $response = $this->oauthRequest($url, $method, $parameters, $headers, $multipart); $response = (new Data\Parser())->parse($response); return $response; } /** * Setup and Send a Signed Oauth Request * * This method uses OAuth Library. * * @param string $uri * @param string $method * @param array $parameters * @param array $headers * @param bool $multipart * * @return string Raw Provider API response * @throws \Hybridauth\Exception\HttpClientFailureException * @throws \Hybridauth\Exception\HttpRequestFailedException */ protected function oauthRequest($uri, $method = 'GET', $parameters = [], $headers = [], $multipart = false) { $signing_parameters = $parameters; if ($multipart) { $signing_parameters = []; } $request = OAuthRequest::from_consumer_and_token( $this->OAuthConsumer, $this->consumerToken, $method, $uri, $signing_parameters ); $request->sign_request( $this->sha1Method, $this->OAuthConsumer, $this->consumerToken ); $uri = $request->get_normalized_http_url(); $headers = array_replace($request->to_header(), (array)$headers); $response = $this->httpClient->request( $uri, $method, $parameters, $headers, $multipart ); $this->validateApiResponse('Signed API request to ' . $uri . ' has returned an error'); return $response; } } hybridauth/User/Activity.php 0000644 00000002765 15104256626 0012153 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\User; use Hybridauth\Exception\UnexpectedValueException; /** * Hybridauth\User\Activity */ final class Activity { /** * activity id on the provider side, usually given as integer * * @var string */ public $id = null; /** * activity date of creation * * @var string */ public $date = null; /** * activity content as a string * * @var string */ public $text = null; /** * user who created the activity * * @var object */ public $user = null; /** * */ public function __construct() { $this->user = new \stdClass(); // typically, we should have a few information about the user who created the event from social apis $this->user->identifier = null; $this->user->displayName = null; $this->user->profileURL = null; $this->user->photoURL = null; } /** * Prevent the providers adapters from adding new fields. * * @throws UnexpectedValueException * @var string $name * * @var mixed $value * */ public function __set($name, $value) { // phpcs:ignore throw new UnexpectedValueException(sprintf('Adding new property "%s\' to %s is not allowed.', $name, __CLASS__)); } } hybridauth/User/Contact.php 0000644 00000002765 15104256626 0011752 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\User; use Hybridauth\Exception\UnexpectedValueException; /** * Hybridauth\User\Contact */ final class Contact { /** * The Unique contact user ID * * @var string */ public $identifier = null; /** * User website, blog, web page * * @var string */ public $webSiteURL = null; /** * URL link to profile page on the IDp web site * * @var string */ public $profileURL = null; /** * URL link to user photo or avatar * * @var string */ public $photoURL = null; /** * User displayName provided by the IDp or a concatenation of first and last name * * @var string */ public $displayName = null; /** * A short about_me * * @var string */ public $description = null; /** * User email. Not all of IDp grant access to the user email * * @var string */ public $email = null; /** * Prevent the providers adapters from adding new fields. * * @param string $name * @param mixed $value * * @throws UnexpectedValueException */ public function __set($name, $value) { throw new UnexpectedValueException(sprintf('Adding new property "%s" to %s is not allowed.', $name, __CLASS__)); } } hybridauth/User/Profile.php 0000644 00000006352 15104256626 0011753 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\User; use Hybridauth\Exception\UnexpectedValueException; /** * Hybridauth\Userobject represents the current logged in user profile. */ final class Profile { /** * The Unique user's ID on the connected provider * * @var int|null */ public $identifier = null; /** * User website, blog, web page * * @var string|null */ public $webSiteURL = null; /** * URL link to profile page on the IDp web site * * @var string|null */ public $profileURL = null; /** * URL link to user photo or avatar * * @var string|null */ public $photoURL = null; /** * User displayName provided by the IDp or a concatenation of first and last name. * * @var string|null */ public $displayName = null; /** * A short about_me * * @var string|null */ public $description = null; /** * User's first name * * @var string|null */ public $firstName = null; /** * User's last name * * @var string|null */ public $lastName = null; /** * male or female * * @var string|null */ public $gender = null; /** * Language * * @var string|null */ public $language = null; /** * User age, we don't calculate it. we return it as is if the IDp provide it. * * @var int|null */ public $age = null; /** * User birth Day * * @var int|null */ public $birthDay = null; /** * User birth Month * * @var int|null */ public $birthMonth = null; /** * User birth Year * * @var int|null */ public $birthYear = null; /** * User email. Note: not all of IDp grant access to the user email * * @var string|null */ public $email = null; /** * Verified user email. Note: not all of IDp grant access to verified user email * * @var string|null */ public $emailVerified = null; /** * Phone number * * @var string|null */ public $phone = null; /** * Complete user address * * @var string|null */ public $address = null; /** * User country * * @var string|null */ public $country = null; /** * Region * * @var string|null */ public $region = null; /** * City * * @var string|null */ public $city = null; /** * Postal code * * @var string|null */ public $zip = null; /** * An extra data which is related to the user * * @var array */ public $data = []; /** * Prevent the providers adapters from adding new fields. * * @throws UnexpectedValueException * @var mixed $value * * @var string $name */ public function __set($name, $value) { throw new UnexpectedValueException(sprintf('Adding new property "%s" to %s is not allowed.', $name, __CLASS__)); } } hybridauth/Thirdparty/readme.md 0000644 00000001355 15104256626 0012633 0 ustar 00 ##### Third party libraries Here we include a number of third party libraries. Those libraries are used by the various providers supported by Hybridauth. Library | Description -------- | ------------- [LightOpenID](https://gitorious.org/lightopenid) | Contain LightOpenID. Solid OpenID library licensed under the MIT License. [OAuth Library](https://code.google.com/p/oauth/) | Contain OAuth Library licensed under the MIT License. Notes: We no longer use the old OAuth clients. Please don't add new libs to this folder, unless strictly necessary. Both LightOpenID and OAuth are (to be) partially/indirectly tested within the Hybridauth library. Both LightOpenID and OAuth libraries are excluded from Codeclimate.com Analysis/GPA. hybridauth/Thirdparty/OpenID/README.md 0000644 00000000323 15104256626 0013443 0 ustar 00 This file is part of the LightOpenID PHP Library LightOpenID is an open source software available under the MIT License. https://github.com/iignatov/LightOpenID http://opensource.org/licenses/mit-license.php hybridauth/Thirdparty/OpenID/LightOpenID.php 0000644 00000127653 15104256626 0015023 0 ustar 00 <?php /*! * This file is part of the LightOpenID PHP Library (https://github.com/iignatov/LightOpenID) * * LightOpenID is an open source software available under the MIT License. * * Updated: 52f9910 on 4 Mar 2016. */ namespace Hybridauth\Thirdparty\OpenID; use Hybridauth\Exception\Exception; use Hybridauth\Exception\ExceptionInterface; /** * Class ErrorException * * @package Hybridauth\Thirdparty\OpenID */ class ErrorException extends Exception implements ExceptionInterface { } /** * This class provides a simple interface for OpenID 1.1/2.0 authentication. * * It requires PHP >= 5.1.2 with cURL or HTTP/HTTPS stream wrappers enabled. * * @version v1.3.1 (2016-03-04) * @link https://code.google.com/p/lightopenid/ Project URL * @link https://github.com/iignatov/LightOpenID GitHub Repo * @author Mewp <mewp151 at gmail dot com> * @copyright Copyright (c) 2013 Mewp * @license http://opensource.org/licenses/mit-license.php MIT License */ class LightOpenID { public $returnUrl ; public $required = array() ; public $optional = array() ; public $verify_peer = null ; public $capath = null ; public $cainfo = null ; public $cnmatch = null ; public $data ; public $oauth = array() ; public $curl_time_out = 30 // in seconds ; public $curl_connect_time_out = 30; // in seconds private $identity; private $claimed_id; protected $server; protected $version; protected $trustRoot; protected $aliases; protected $identifier_select = false ; protected $ax = false; protected $sreg = false; protected $setup_url = null; protected $headers = array() ; protected $proxy = null; protected $user_agent = 'LightOpenID' ; protected $xrds_override_pattern = null; protected $xrds_override_replacement = null; protected static $ax_to_sreg = array( 'namePerson/friendly' => 'nickname', 'contact/email' => 'email', 'namePerson' => 'fullname', 'birthDate' => 'dob', 'person/gender' => 'gender', 'contact/postalCode/home' => 'postcode', 'contact/country/home' => 'country', 'pref/language' => 'language', 'pref/timezone' => 'timezone', ); /** * LightOpenID constructor. * * @param $host * @param null $proxy * * @throws ErrorException */ public function __construct($host, $proxy = null) { $this->set_realm($host); $this->set_proxy($proxy); $uri = rtrim(preg_replace('#((?<=\?)|&)openid\.[^&]+#', '', $_SERVER['REQUEST_URI']), '?'); $this->returnUrl = $this->trustRoot . $uri; $this->data = ($_SERVER['REQUEST_METHOD'] === 'POST') ? $_POST : $_GET; if (!function_exists('curl_init') && !in_array('https', stream_get_wrappers())) { throw new ErrorException('You must have either https wrappers or curl enabled.'); } } /** * @param $name * * @return bool */ public function __isset($name) { return in_array($name, array('identity', 'trustRoot', 'realm', 'xrdsOverride', 'mode')); } /** * @param $name * @param $value */ public function __set($name, $value) { switch ($name) { case 'identity': if (strlen($value = trim((String) $value))) { if (preg_match('#^xri:/*#i', $value, $m)) { $value = substr($value, strlen($m[0])); } elseif (!preg_match('/^(?:[=@+\$!\(]|https?:)/i', $value)) { $value = "http://$value"; } if (preg_match('#^https?://[^/]+$#i', $value, $m)) { $value .= '/'; } } $this->$name = $this->claimed_id = $value; break; case 'trustRoot': case 'realm': $this->trustRoot = trim($value); break; case 'xrdsOverride': if (is_array($value)) { list($pattern, $replacement) = $value; $this->xrds_override_pattern = $pattern; $this->xrds_override_replacement = $replacement; } else { trigger_error('Invalid value specified for "xrdsOverride".', E_USER_ERROR); } break; } } /** * @param $name * * @return |null */ public function __get($name) { switch ($name) { case 'identity': # We return claimed_id instead of identity, # because the developer should see the claimed identifier, # i.e. what he set as identity, not the op-local identifier (which is what we verify) return $this->claimed_id; case 'trustRoot': case 'realm': return $this->trustRoot; case 'mode': return empty($this->data['openid_mode']) ? null : $this->data['openid_mode']; } } /** * @param $proxy * * @throws ErrorException */ public function set_proxy($proxy) { if (!empty($proxy)) { // When the proxy is a string - try to parse it. if (!is_array($proxy)) { $proxy = parse_url($proxy); } // Check if $proxy is valid after the parsing. if ($proxy && !empty($proxy['host'])) { // Make sure that a valid port number is specified. if (array_key_exists('port', $proxy)) { if (!is_int($proxy['port'])) { $proxy['port'] = is_numeric($proxy['port']) ? intval($proxy['port']) : 0; } if ($proxy['port'] <= 0) { throw new ErrorException('The specified proxy port number is invalid.'); } } $this->proxy = $proxy; } } } /** * Checks if the server specified in the url exists. * * @param $url string url to check * @return true, if the server exists; false otherwise */ public function hostExists($url) { if (strpos($url, '/') === false) { $server = $url; } else { $server = @parse_url($url, PHP_URL_HOST); } if (!$server) { return false; } return !!gethostbynamel($server); } /** * @param $uri */ protected function set_realm($uri) { $realm = ''; # Set a protocol, if not specified. $realm .= (($offset = strpos($uri, '://')) === false) ? $this->get_realm_protocol() : ''; # Set the offset properly. $offset = (($offset !== false) ? $offset + 3 : 0); # Get only the root, without the path. $realm .= (($end = strpos($uri, '/', $offset)) === false) ? $uri : substr($uri, 0, $end); $this->trustRoot = $realm; } /** * @return string */ protected function get_realm_protocol() { if (!empty($_SERVER['HTTPS'])) { $use_secure_protocol = ($_SERVER['HTTPS'] !== 'off'); } elseif (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) { $use_secure_protocol = ($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https'); } elseif (isset($_SERVER['HTTP__WSSC'])) { $use_secure_protocol = ($_SERVER['HTTP__WSSC'] == 'https'); } else { $use_secure_protocol = false; } return $use_secure_protocol ? 'https://' : 'http://'; } /** * @param $url * @param string $method * @param array $params * @param $update_claimed_id * * @return array|bool|string * @throws ErrorException */ protected function request_curl($url, $method='GET', $params=array(), $update_claimed_id=false) { $params = http_build_query($params, '', '&'); $curl = curl_init($url . ($method == 'GET' && $params ? '?' . $params : '')); curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); curl_setopt($curl, CURLOPT_HEADER, false); curl_setopt($curl, CURLOPT_USERAGENT, $this->user_agent); curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); if ($method == 'POST') { curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-type: application/x-www-form-urlencoded')); } else { curl_setopt($curl, CURLOPT_HTTPHEADER, array('Accept: application/xrds+xml, */*')); } curl_setopt($curl, CURLOPT_TIMEOUT, $this->curl_time_out); // defaults to infinite curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $this->curl_connect_time_out); // defaults to 300s if (!empty($this->proxy)) { curl_setopt($curl, CURLOPT_PROXY, $this->proxy['host']); if (!empty($this->proxy['port'])) { curl_setopt($curl, CURLOPT_PROXYPORT, $this->proxy['port']); } if (!empty($this->proxy['user'])) { curl_setopt($curl, CURLOPT_PROXYUSERPWD, $this->proxy['user'] . ':' . $this->proxy['pass']); } } if ($this->verify_peer !== null) { curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verify_peer); if ($this->capath) { curl_setopt($curl, CURLOPT_CAPATH, $this->capath); } if ($this->cainfo) { curl_setopt($curl, CURLOPT_CAINFO, $this->cainfo); } } if ($method == 'POST') { curl_setopt($curl, CURLOPT_POST, true); curl_setopt($curl, CURLOPT_POSTFIELDS, $params); } elseif ($method == 'HEAD') { curl_setopt($curl, CURLOPT_HEADER, true); curl_setopt($curl, CURLOPT_NOBODY, true); } else { curl_setopt($curl, CURLOPT_HEADER, true); curl_setopt($curl, CURLOPT_HTTPGET, true); } $response = curl_exec($curl); if ($method == 'HEAD' && curl_getinfo($curl, CURLINFO_HTTP_CODE) == 405) { curl_setopt($curl, CURLOPT_HTTPGET, true); $response = curl_exec($curl); $response = substr($response, 0, strpos($response, "\r\n\r\n")); } if ($method == 'HEAD' || $method == 'GET') { $header_response = $response; # If it's a GET request, we want to only parse the header part. if ($method == 'GET') { $header_response = substr($response, 0, strpos($response, "\r\n\r\n")); } $headers = array(); foreach (explode("\n", $header_response) as $header) { $pos = strpos($header, ':'); if ($pos !== false) { $name = strtolower(trim(substr($header, 0, $pos))); $headers[$name] = trim(substr($header, $pos+1)); } } if ($update_claimed_id) { # Update the claimed_id value in case of redirections. $effective_url = curl_getinfo($curl, CURLINFO_EFFECTIVE_URL); # Ignore the fragment (some cURL versions don't handle it well). if (strtok($effective_url, '#') != strtok($url, '#')) { $this->identity = $this->claimed_id = $effective_url; } } if ($method == 'HEAD') { return $headers; } else { $this->headers = $headers; } } if (curl_errno($curl)) { throw new ErrorException(curl_error($curl), curl_errno($curl)); } return $response; } /** * @param $array * @param $update_claimed_id * * @return array */ protected function parse_header_array($array, $update_claimed_id) { $headers = array(); foreach ($array as $header) { $pos = strpos($header, ':'); if ($pos !== false) { $name = strtolower(trim(substr($header, 0, $pos))); $headers[$name] = trim(substr($header, $pos+1)); # Following possible redirections. The point is just to have # claimed_id change with them, because the redirections # are followed automatically. # We ignore redirections with relative paths. # If any known provider uses them, file a bug report. if ($name == 'location' && $update_claimed_id) { if (strpos($headers[$name], 'http') === 0) { $this->identity = $this->claimed_id = $headers[$name]; } elseif ($headers[$name][0] == '/') { $parsed_url = parse_url($this->claimed_id); $this->identity = $this->claimed_id = $parsed_url['scheme'] . '://' . $parsed_url['host'] . $headers[$name]; } } } } return $headers; } /** * @param $url * @param string $method * @param array $params * @param $update_claimed_id * * @return array|false|string * @throws ErrorException */ protected function request_streams($url, $method='GET', $params=array(), $update_claimed_id=false) { if (!$this->hostExists($url)) { throw new ErrorException("Could not connect to $url.", 404); } if (empty($this->cnmatch)) { $this->cnmatch = parse_url($url, PHP_URL_HOST); } $params = http_build_query($params, '', '&'); switch ($method) { case 'GET': $opts = array( 'http' => array( 'method' => 'GET', 'header' => 'Accept: application/xrds+xml, */*', 'user_agent' => $this->user_agent, 'ignore_errors' => true, ), 'ssl' => array( 'CN_match' => $this->cnmatch ) ); $url = $url . ($params ? '?' . $params : ''); if (!empty($this->proxy)) { $opts['http']['proxy'] = $this->proxy_url(); } break; case 'POST': $opts = array( 'http' => array( 'method' => 'POST', 'header' => 'Content-type: application/x-www-form-urlencoded', 'user_agent' => $this->user_agent, 'content' => $params, 'ignore_errors' => true, ), 'ssl' => array( 'CN_match' => $this->cnmatch ) ); if (!empty($this->proxy)) { $opts['http']['proxy'] = $this->proxy_url(); } break; case 'HEAD': // We want to send a HEAD request, but since get_headers() doesn't // accept $context parameter, we have to change the defaults. $default = stream_context_get_options(stream_context_get_default()); // PHP does not reset all options. Instead, it just sets the options // available in the passed array, therefore set the defaults manually. $default += array( 'http' => array(), 'ssl' => array() ); $default['http'] += array( 'method' => 'GET', 'header' => '', 'user_agent' => '', 'ignore_errors' => false ); $default['ssl'] += array( 'CN_match' => '' ); $opts = array( 'http' => array( 'method' => 'HEAD', 'header' => 'Accept: application/xrds+xml, */*', 'user_agent' => $this->user_agent, 'ignore_errors' => true, ), 'ssl' => array( 'CN_match' => $this->cnmatch ) ); // Enable validation of the SSL certificates. if ($this->verify_peer) { $default['ssl'] += array( 'verify_peer' => false, 'capath' => '', 'cafile' => '' ); $opts['ssl'] += array( 'verify_peer' => true, 'capath' => $this->capath, 'cafile' => $this->cainfo ); } // Change the stream context options. stream_context_get_default($opts); $headers = get_headers($url . ($params ? '?' . $params : '')); // Restore the stream context options. stream_context_get_default($default); if (!empty($headers)) { if (intval(substr($headers[0], strlen('HTTP/1.1 '))) == 405) { // The server doesn't support HEAD - emulate it with a GET. $args = func_get_args(); $args[1] = 'GET'; call_user_func_array(array($this, 'request_streams'), $args); $headers = $this->headers; } else { $headers = $this->parse_header_array($headers, $update_claimed_id); } } else { $headers = array(); } return $headers; } if ($this->verify_peer) { $opts['ssl'] += array( 'verify_peer' => true, 'capath' => $this->capath, 'cafile' => $this->cainfo ); } $context = stream_context_create($opts); $data = file_get_contents($url, false, $context); # This is a hack for providers who don't support HEAD requests. # It just creates the headers array for the last request in $this->headers. if (isset($http_response_header)) { $this->headers = $this->parse_header_array($http_response_header, $update_claimed_id); } return $data; } /** * @param $url * @param string $method * @param array $params * @param bool $update_claimed_id * * @return array|bool|false|string * @throws ErrorException */ protected function request($url, $method='GET', $params=array(), $update_claimed_id=false) { $use_curl = false; if (function_exists('curl_init')) { if (!$use_curl) { # When allow_url_fopen is disabled, PHP streams will not work. $use_curl = !ini_get('allow_url_fopen'); } if (!$use_curl) { # When there is no HTTPS wrapper, PHP streams cannott be used. $use_curl = !in_array('https', stream_get_wrappers()); } if (!$use_curl) { # With open_basedir or safe_mode set, cURL can't follow redirects. $use_curl = !(ini_get('safe_mode') || ini_get('open_basedir')); } } return $use_curl ? $this->request_curl($url, $method, $params, $update_claimed_id) : $this->request_streams($url, $method, $params, $update_claimed_id); } /** * @return string */ protected function proxy_url() { $result = ''; if (!empty($this->proxy)) { $result = $this->proxy['host']; if (!empty($this->proxy['port'])) { $result = $result . ':' . $this->proxy['port']; } if (!empty($this->proxy['user'])) { $result = $this->proxy['user'] . ':' . $this->proxy['pass'] . '@' . $result; } $result = 'http://' . $result; } return $result; } /** * @param $url * @param $parts * * @return string */ protected function build_url($url, $parts) { if (isset($url['query'], $parts['query'])) { $parts['query'] = $url['query'] . '&' . $parts['query']; } $url = $parts + $url; $url = $url['scheme'] . '://' . (empty($url['username'])?'' :(empty($url['password'])? "{$url['username']}@" :"{$url['username']}:{$url['password']}@")) . $url['host'] . (empty($url['port'])?'':":{$url['port']}") . (empty($url['path'])?'':$url['path']) . (empty($url['query'])?'':"?{$url['query']}") . (empty($url['fragment'])?'':"#{$url['fragment']}"); return $url; } /** * Helper function used to scan for <meta>/<link> tags and extract information * from them * * @param $content * @param $tag * @param $attrName * @param $attrValue * @param $valueName * * @return bool */ protected function htmlTag($content, $tag, $attrName, $attrValue, $valueName) { preg_match_all("#<{$tag}[^>]*$attrName=['\"].*?$attrValue.*?['\"][^>]*$valueName=['\"](.+?)['\"][^>]*/?>#i", $content, $matches1); preg_match_all("#<{$tag}[^>]*$valueName=['\"](.+?)['\"][^>]*$attrName=['\"].*?$attrValue.*?['\"][^>]*/?>#i", $content, $matches2); $result = array_merge($matches1[1], $matches2[1]); return empty($result)?false:$result[0]; } /** * Performs Yadis and HTML discovery. Normally not used. * @param $url Identity URL. * @return String OP Endpoint (i.e. OpenID provider address). * @throws ErrorException */ public function discover($url) { if (!$url) { throw new ErrorException('No identity supplied.'); } # Use xri.net proxy to resolve i-name identities if (!preg_match('#^https?:#', $url)) { $url = "https://xri.net/$url"; } # We save the original url in case of Yadis discovery failure. # It can happen when we'll be lead to an XRDS document # which does not have any OpenID2 services. $originalUrl = $url; # A flag to disable yadis discovery in case of failure in headers. $yadis = true; # Allows optional regex replacement of the URL, e.g. to use Google Apps # as an OpenID provider without setting up XRDS on the domain hosting. if (!is_null($this->xrds_override_pattern) && !is_null($this->xrds_override_replacement)) { $url = preg_replace($this->xrds_override_pattern, $this->xrds_override_replacement, $url); } # We'll jump a maximum of 5 times, to avoid endless redirections. for ($i = 0; $i < 5; $i ++) { if ($yadis) { $headers = $this->request($url, 'HEAD', array(), true); $next = false; if (isset($headers['x-xrds-location'])) { $url = $this->build_url(parse_url($url), parse_url(trim($headers['x-xrds-location']))); $next = true; } if (isset($headers['content-type']) && $this->is_allowed_type($headers['content-type'])) { # Found an XRDS document, now let's find the server, and optionally delegate. $content = $this->request($url, 'GET'); preg_match_all('#<Service.*?>(.*?)</Service>#s', $content, $m); foreach ($m[1] as $content) { $content = ' ' . $content; # The space is added, so that strpos doesn't return 0. # OpenID 2 $ns = preg_quote('http://specs.openid.net/auth/2.0/', '#'); if (preg_match('#<Type>\s*'.$ns.'(server|signon)\s*</Type>#s', $content, $type)) { if ($type[1] == 'server') { $this->identifier_select = true; } preg_match('#<URI.*?>(.*)</URI>#', $content, $server); preg_match('#<(Local|Canonical)ID>(.*)</\1ID>#', $content, $delegate); if (empty($server)) { return false; } # Does the server advertise support for either AX or SREG? $this->ax = (bool) strpos($content, '<Type>http://openid.net/srv/ax/1.0</Type>'); $this->sreg = strpos($content, '<Type>http://openid.net/sreg/1.0</Type>') || strpos($content, '<Type>http://openid.net/extensions/sreg/1.1</Type>'); $server = $server[1]; if (isset($delegate[2])) { $this->identity = trim($delegate[2]); } $this->version = 2; $this->server = $server; return $server; } # OpenID 1.1 $ns = preg_quote('http://openid.net/signon/1.1', '#'); if (preg_match('#<Type>\s*'.$ns.'\s*</Type>#s', $content)) { preg_match('#<URI.*?>(.*)</URI>#', $content, $server); preg_match('#<.*?Delegate>(.*)</.*?Delegate>#', $content, $delegate); if (empty($server)) { return false; } # AX can be used only with OpenID 2.0, so checking only SREG $this->sreg = strpos($content, '<Type>http://openid.net/sreg/1.0</Type>') || strpos($content, '<Type>http://openid.net/extensions/sreg/1.1</Type>'); $server = $server[1]; if (isset($delegate[1])) { $this->identity = $delegate[1]; } $this->version = 1; $this->server = $server; return $server; } } $next = true; $yadis = false; $url = $originalUrl; $content = null; break; } if ($next) { continue; } # There are no relevant information in headers, so we search the body. $content = $this->request($url, 'GET', array(), true); if (isset($this->headers['x-xrds-location'])) { $url = $this->build_url(parse_url($url), parse_url(trim($this->headers['x-xrds-location']))); continue; } $location = $this->htmlTag($content, 'meta', 'http-equiv', 'X-XRDS-Location', 'content'); if ($location) { $url = $this->build_url(parse_url($url), parse_url($location)); continue; } } if (!$content) { $content = $this->request($url, 'GET'); } # At this point, the YADIS Discovery has failed, so we'll switch # to openid2 HTML discovery, then fallback to openid 1.1 discovery. $server = $this->htmlTag($content, 'link', 'rel', 'openid2.provider', 'href'); $delegate = $this->htmlTag($content, 'link', 'rel', 'openid2.local_id', 'href'); $this->version = 2; if (!$server) { # The same with openid 1.1 $server = $this->htmlTag($content, 'link', 'rel', 'openid.server', 'href'); $delegate = $this->htmlTag($content, 'link', 'rel', 'openid.delegate', 'href'); $this->version = 1; } if ($server) { # We found an OpenID2 OP Endpoint if ($delegate) { # We have also found an OP-Local ID. $this->identity = $delegate; } $this->server = $server; return $server; } throw new ErrorException("No OpenID Server found at $url", 404); } throw new ErrorException('Endless redirection!', 500); } /** * @param $content_type * * @return bool */ protected function is_allowed_type($content_type) { # Apparently, some providers return XRDS documents as text/html. # While it is against the spec, allowing this here shouldn't break # compatibility with anything. $allowed_types = array('application/xrds+xml', 'text/xml'); # Only allow text/html content type for the Yahoo logins, since # it might cause an endless redirection for the other providers. if ($this->get_provider_name($this->claimed_id) == 'yahoo') { $allowed_types[] = 'text/html'; } foreach ($allowed_types as $type) { if (strpos($content_type, $type) !== false) { return true; } } return false; } /** * @param $provider_url * * @return string */ protected function get_provider_name($provider_url) { $result = ''; if (!empty($provider_url)) { $tokens = array_reverse( explode('.', parse_url($provider_url, PHP_URL_HOST)) ); $result = strtolower( (count($tokens) > 1 && strlen($tokens[1]) > 3) ? $tokens[1] : (count($tokens) > 2 ? $tokens[2] : '') ); } return $result; } /** * @return array */ protected function sregParams() { $params = array(); # We always use SREG 1.1, even if the server is advertising only support for 1.0. # That's because it's fully backwards compatible with 1.0, and some providers # advertise 1.0 even if they accept only 1.1. One such provider is myopenid.com $params['openid.ns.sreg'] = 'http://openid.net/extensions/sreg/1.1'; if ($this->required) { $params['openid.sreg.required'] = array(); foreach ($this->required as $required) { if (!isset(self::$ax_to_sreg[$required])) { continue; } $params['openid.sreg.required'][] = self::$ax_to_sreg[$required]; } $params['openid.sreg.required'] = implode(',', $params['openid.sreg.required']); } if ($this->optional) { $params['openid.sreg.optional'] = array(); foreach ($this->optional as $optional) { if (!isset(self::$ax_to_sreg[$optional])) { continue; } $params['openid.sreg.optional'][] = self::$ax_to_sreg[$optional]; } $params['openid.sreg.optional'] = implode(',', $params['openid.sreg.optional']); } return $params; } /** * @return array */ protected function axParams() { $params = array(); if ($this->required || $this->optional) { $params['openid.ns.ax'] = 'http://openid.net/srv/ax/1.0'; $params['openid.ax.mode'] = 'fetch_request'; $this->aliases = array(); $counts = array(); $required = array(); $optional = array(); foreach (array('required','optional') as $type) { foreach ($this->$type as $alias => $field) { if (is_int($alias)) { $alias = strtr($field, '/', '_'); } $this->aliases[$alias] = 'http://axschema.org/' . $field; if (empty($counts[$alias])) { $counts[$alias] = 0; } $counts[$alias] += 1; ${$type}[] = $alias; } } foreach ($this->aliases as $alias => $ns) { $params['openid.ax.type.' . $alias] = $ns; } foreach ($counts as $alias => $count) { if ($count == 1) { continue; } $params['openid.ax.count.' . $alias] = $count; } # Don't send empty ax.required and ax.if_available. # Google and possibly other providers refuse to support ax when one of these is empty. if ($required) { $params['openid.ax.required'] = implode(',', $required); } if ($optional) { $params['openid.ax.if_available'] = implode(',', $optional); } } return $params; } /** * @param $immediate * * @return string */ protected function authUrl_v1($immediate) { $returnUrl = $this->returnUrl; # If we have an openid.delegate that is different from our claimed id, # we need to somehow preserve the claimed id between requests. # The simplest way is to just send it along with the return_to url. if ($this->identity != $this->claimed_id) { $returnUrl .= (strpos($returnUrl, '?') ? '&' : '?') . 'openid.claimed_id=' . $this->claimed_id; } $params = array( 'openid.return_to' => $returnUrl, 'openid.mode' => $immediate ? 'checkid_immediate' : 'checkid_setup', 'openid.identity' => $this->identity, 'openid.trust_root' => $this->trustRoot, ) + $this->sregParams(); return $this->build_url(parse_url($this->server), array('query' => http_build_query($params, '', '&'))); } /** * @param $immediate * * @return string */ protected function authUrl_v2($immediate) { $params = array( 'openid.ns' => 'http://specs.openid.net/auth/2.0', 'openid.mode' => $immediate ? 'checkid_immediate' : 'checkid_setup', 'openid.return_to' => $this->returnUrl, 'openid.realm' => $this->trustRoot, ); if ($this->ax) { $params += $this->axParams(); } if ($this->sreg) { $params += $this->sregParams(); } if (!$this->ax && !$this->sreg) { # If OP doesn't advertise either SREG, nor AX, let's send them both # in worst case we don't get anything in return. $params += $this->axParams() + $this->sregParams(); } if (!empty($this->oauth) && is_array($this->oauth)) { $params['openid.ns.oauth'] = 'http://specs.openid.net/extensions/oauth/1.0'; $params['openid.oauth.consumer'] = str_replace(array('http://', 'https://'), '', $this->trustRoot); $params['openid.oauth.scope'] = implode(' ', $this->oauth); } if ($this->identifier_select) { $params['openid.identity'] = $params['openid.claimed_id'] = 'http://specs.openid.net/auth/2.0/identifier_select'; } else { $params['openid.identity'] = $this->identity; $params['openid.claimed_id'] = $this->claimed_id; } return $this->build_url(parse_url($this->server), array('query' => http_build_query($params, '', '&'))); } /** * Returns authentication url. Usually, you want to redirect your user to it. * @param bool $immediate * @return String The authentication url. * @throws ErrorException */ public function authUrl($immediate = false) { if ($this->setup_url && !$immediate) { return $this->setup_url; } if (!$this->server) { $this->discover($this->identity); } if ($this->version == 2) { return $this->authUrl_v2($immediate); } return $this->authUrl_v1($immediate); } /** * Performs OpenID verification with the OP. * @return Bool Whether the verification was successful. * @throws ErrorException */ public function validate() { # If the request was using immediate mode, a failure may be reported # by presenting user_setup_url (for 1.1) or reporting # mode 'setup_needed' (for 2.0). Also catching all modes other than # id_res, in order to avoid throwing errors. if (isset($this->data['openid_user_setup_url'])) { $this->setup_url = $this->data['openid_user_setup_url']; return false; } if ($this->mode != 'id_res') { return false; } $this->claimed_id = isset($this->data['openid_claimed_id'])?$this->data['openid_claimed_id']:$this->data['openid_identity']; $params = array( 'openid.assoc_handle' => $this->data['openid_assoc_handle'], 'openid.signed' => $this->data['openid_signed'], 'openid.sig' => $this->data['openid_sig'], ); if (isset($this->data['openid_ns'])) { # We're dealing with an OpenID 2.0 server, so let's set an ns # Even though we should know location of the endpoint, # we still need to verify it by discovery, so $server is not set here $params['openid.ns'] = 'http://specs.openid.net/auth/2.0'; } elseif (isset($this->data['openid_claimed_id']) && $this->data['openid_claimed_id'] != $this->data['openid_identity'] ) { # If it's an OpenID 1 provider, and we've got claimed_id, # we have to append it to the returnUrl, like authUrl_v1 does. $this->returnUrl .= (strpos($this->returnUrl, '?') ? '&' : '?') . 'openid.claimed_id=' . $this->claimed_id; } if ($this->data['openid_return_to'] != $this->returnUrl) { # The return_to url must match the url of current request. # I'm assuming that no one will set the returnUrl to something that doesn't make sense. return false; } $server = $this->discover($this->claimed_id); foreach (explode(',', $this->data['openid_signed']) as $item) { $value = $this->data['openid_' . str_replace('.', '_', $item)]; $params['openid.' . $item] = $value; } $params['openid.mode'] = 'check_authentication'; $response = $this->request($server, 'POST', $params); return preg_match('/is_valid\s*:\s*true/i', $response); } /** * @return array */ protected function getAxAttributes() { $result = array(); if ($alias = $this->getNamespaceAlias('http://openid.net/srv/ax/1.0', 'ax')) { $prefix = 'openid_' . $alias; $length = strlen('http://axschema.org/'); foreach (explode(',', $this->data['openid_signed']) as $key) { $keyMatch = $alias . '.type.'; if (strncmp($key, $keyMatch, strlen($keyMatch)) !== 0) { continue; } $key = substr($key, strlen($keyMatch)); $idv = $prefix . '_value_' . $key; $idc = $prefix . '_count_' . $key; $key = substr($this->getItem($prefix . '_type_' . $key), $length); if (!empty($key)) { if (($count = intval($this->getItem($idc))) > 0) { $value = array(); for ($i = 1; $i <= $count; $i++) { $value[] = $this->getItem($idv . '_' . $i); } $value = ($count == 1) ? reset($value) : $value; } else { $value = $this->getItem($idv); } if (!is_null($value)) { $result[$key] = $value; } } } } else { // No alias for the AX schema has been found, // so there is no AX data in the OP's response. } return $result; } /** * @return array */ protected function getSregAttributes() { $attributes = array(); $sreg_to_ax = array_flip(self::$ax_to_sreg); if ($alias = $this->getNamespaceAlias('http://openid.net/extensions/sreg/1.1', 'sreg')) { foreach (explode(',', $this->data['openid_signed']) as $key) { $keyMatch = $alias . '.'; if (strncmp($key, $keyMatch, strlen($keyMatch)) !== 0) { continue; } $key = substr($key, strlen($keyMatch)); if (!isset($sreg_to_ax[$key])) { # The field name isn't part of the SREG spec, so we ignore it. continue; } $attributes[$sreg_to_ax[$key]] = $this->data['openid_' . $alias . '_' . $key]; } } return $attributes; } /** * Gets AX/SREG attributes provided by OP. should be used only after successful validation. * Note that it does not guarantee that any of the required/optional parameters will be present, * or that there will be no other attributes besides those specified. * In other words. OP may provide whatever information it wants to. * * SREG names will be mapped to AX names. * * * @return array Array of attributes with keys being the AX schema names, e.g. 'contact/email' @see http://www.axschema.org/types/ */ public function getAttributes() { if (isset($this->data['openid_ns']) && $this->data['openid_ns'] == 'http://specs.openid.net/auth/2.0' ) { # OpenID 2.0 # We search for both AX and SREG attributes, with AX taking precedence. return $this->getAxAttributes() + $this->getSregAttributes(); } return $this->getSregAttributes(); } /** * Gets an OAuth request token if the OpenID+OAuth hybrid protocol has been used. * * In order to use the OpenID+OAuth hybrid protocol, you need to add at least one * scope to the $openid->oauth array before you get the call to getAuthUrl(), e.g.: * $openid->oauth[] = 'https://www.googleapis.com/auth/plus.me'; * * Furthermore the registered consumer name must fit the OpenID realm. * To register an OpenID consumer at Google use: https://www.google.com/accounts/ManageDomains * * @return string|bool OAuth request token on success, FALSE if no token was provided. */ public function getOAuthRequestToken() { $alias = $this->getNamespaceAlias('http://specs.openid.net/extensions/oauth/1.0'); return !empty($alias) ? $this->data['openid_' . $alias . '_request_token'] : false; } /** * Gets the alias for the specified namespace, if it's present. * * @param string $namespace The namespace for which an alias is needed. * @param string $hint Common alias of this namespace, used for optimization. * @return string|null The namespace alias if found, otherwise - NULL. */ private function getNamespaceAlias($namespace, $hint = null) { $result = null; if (empty($hint) || $this->getItem('openid_ns_' . $hint) != $namespace) { // The common alias is either undefined or points to // some other extension - search for another alias.. $prefix = 'openid_ns_'; $length = strlen($prefix); foreach ($this->data as $key => $val) { if (strncmp($key, $prefix, $length) === 0 && $val === $namespace) { $result = trim(substr($key, $length)); break; } } } else { $result = $hint; } return $result; } /** * Gets an item from the $data array by the specified id. * * @param string $id The id of the desired item. * @return string|null The item if found, otherwise - NULL. */ private function getItem($id) { return isset($this->data[$id]) ? $this->data[$id] : null; } } hybridauth/Thirdparty/OAuth/OAuthUtil.php 0000644 00000015636 15104256626 0014472 0 ustar 00 <?php /*! * This file is part of the OAuth PHP Library (https://code.google.com/p/oauth/) * * OAuth `PHP' Library is an open source software available under the MIT License. */ namespace Hybridauth\Thirdparty\OAuth; /** * Class OAuthUtil * * @package Hybridauth\Thirdparty\OAuth */ class OAuthUtil { /** * @param $input * * @return array|mixed|string */ public static function urlencode_rfc3986($input) { if (is_array($input)) { return array_map(array( '\Hybridauth\Thirdparty\OAuth\OAuthUtil', 'urlencode_rfc3986' ), $input); } elseif (is_scalar($input)) { return str_replace('+', ' ', str_replace('%7E', '~', rawurlencode($input))); } else { return ''; } } // This decode function isn't taking into consideration the above // modifications to the encoding process. However, this method doesn't // seem to be used anywhere so leaving it as is. /** * @param $string * * @return string */ public static function urldecode_rfc3986($string) { return urldecode($string); } // Utility function for turning the Authorization: header into // parameters, has to do some unescaping // Can filter out any non-oauth parameters if needed (default behaviour) // May 28th, 2010 - method updated to tjerk.meesters for a speed improvement. // see http://code.google.com/p/oauth/issues/detail?id=163 /** * @param $header * @param bool $only_allow_oauth_parameters * * @return array */ public static function split_header($header, $only_allow_oauth_parameters = true) { $params = array(); if (preg_match_all('/(' . ($only_allow_oauth_parameters ? 'oauth_' : '') . '[a-z_-]*)=(:?"([^"]*)"|([^,]*))/', $header, $matches)) { foreach ($matches[1] as $i => $h) { $params[$h] = OAuthUtil::urldecode_rfc3986(empty($matches[3][$i]) ? $matches[4][$i] : $matches[3][$i]); } if (isset($params['realm'])) { unset($params['realm']); } } return $params; } // helper to try to sort out headers for people who aren't running apache /** * @return array */ public static function get_headers() { if (function_exists('apache_request_headers')) { // we need this to get the actual Authorization: header // because apache tends to tell us it doesn't exist $headers = apache_request_headers(); // sanitize the output of apache_request_headers because // we always want the keys to be Cased-Like-This and arh() // returns the headers in the same case as they are in the // request $out = array(); foreach ($headers as $key => $value) { $key = str_replace(" ", "-", ucwords(strtolower(str_replace("-", " ", $key)))); $out[$key] = $value; } } else { // otherwise we don't have apache and are just going to have to hope // that $_SERVER actually contains what we need $out = array(); if (isset($_SERVER['CONTENT_TYPE'])) { $out['Content-Type'] = $_SERVER['CONTENT_TYPE']; } if (isset($_ENV['CONTENT_TYPE'])) { $out['Content-Type'] = $_ENV['CONTENT_TYPE']; } foreach ($_SERVER as $key => $value) { if (substr($key, 0, 5) == "HTTP_") { // this is chaos, basically it is just there to capitalize the first // letter of every word that is not an initial HTTP and strip HTTP // code from przemek $key = str_replace(" ", "-", ucwords(strtolower(str_replace("_", " ", substr($key, 5))))); $out[$key] = $value; } } } return $out; } // This function takes a input like a=b&a=c&d=e and returns the parsed // parameters like this // array('a' => array('b','c'), 'd' => 'e') /** * @param $input * * @return array */ public static function parse_parameters($input) { if (!isset($input) || !$input) { return array(); } $pairs = explode('&', $input); $parsed_parameters = array(); foreach ($pairs as $pair) { $split = explode('=', $pair, 2); $parameter = OAuthUtil::urldecode_rfc3986($split[0]); $value = isset($split[1]) ? OAuthUtil::urldecode_rfc3986($split[1]) : ''; if (isset($parsed_parameters[$parameter])) { // We have already recieved parameter(s) with this name, so add to the list // of parameters with this name if (is_scalar($parsed_parameters[$parameter])) { // This is the first duplicate, so transform scalar (string) into an array // so we can add the duplicates $parsed_parameters[$parameter] = array( $parsed_parameters[$parameter] ); } $parsed_parameters[$parameter][] = $value; } else { $parsed_parameters[$parameter] = $value; } } return $parsed_parameters; } /** * @param $params * * @return string */ public static function build_http_query($params) { if (!$params) { return ''; } // Urlencode both keys and values $keys = OAuthUtil::urlencode_rfc3986(array_keys($params)); $values = OAuthUtil::urlencode_rfc3986(array_values($params)); $params = array_combine($keys, $values); // Parameters are sorted by name, using lexicographical byte value ordering. // Ref: Spec: 9.1.1 (1) uksort($params, 'strcmp'); $pairs = array(); foreach ($params as $parameter => $value) { if (is_array($value)) { // If two or more parameters share the same name, they are sorted by their value // Ref: Spec: 9.1.1 (1) // June 12th, 2010 - changed to sort because of issue 164 by hidetaka sort($value, SORT_STRING); foreach ($value as $duplicate_value) { $pairs[] = $parameter . '=' . $duplicate_value; } } else { $pairs[] = $parameter . '=' . $value; } } // For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61) // Each name-value pair is separated by an '&' character (ASCII code 38) return implode('&', $pairs); } } hybridauth/Thirdparty/OAuth/OAuthSignatureMethodHMACSHA1.php 0000644 00000002034 15104256626 0017711 0 ustar 00 <?php /*! * This file is part of the OAuth PHP Library (https://code.google.com/p/oauth/) * * OAuth `PHP' Library is an open source software available under the MIT License. */ namespace Hybridauth\Thirdparty\OAuth; /** * Class OAuthSignatureMethodHMACSHA1 * * @package Hybridauth\Thirdparty\OAuth */ class OAuthSignatureMethodHMACSHA1 extends OAuthSignatureMethod { /** * @return string */ public function get_name() { return "HMAC-SHA1"; } /** * @param $request * @param $consumer * @param $token * * @return string */ public function build_signature($request, $consumer, $token) { $base_string = $request->get_signature_base_string(); $request->base_string = $base_string; $key_parts = array( $consumer->secret, $token ? $token->secret : '' ); $key_parts = OAuthUtil::urlencode_rfc3986($key_parts); $key = implode('&', $key_parts); return base64_encode(hash_hmac('sha1', $base_string, $key, true)); } } hybridauth/Thirdparty/OAuth/OAuthSignatureMethod.php 0000644 00000003361 15104256626 0016647 0 ustar 00 <?php /*! * This file is part of the OAuth PHP Library (https://code.google.com/p/oauth/) * * OAuth `PHP' Library is an open source software available under the MIT License. */ namespace Hybridauth\Thirdparty\OAuth; /** * Class OAuthSignatureMethod * * @package Hybridauth\Thirdparty\OAuth */ abstract class OAuthSignatureMethod { /** * Needs to return the name of the Signature Method (ie HMAC-SHA1) * * @return string */ abstract public function get_name(); /** * Build up the signature * NOTE: The output of this function MUST NOT be urlencoded. * the encoding is handled in OAuthRequest when the final * request is serialized * * @param OAuthRequest $request * @param OAuthConsumer $consumer * @param OAuthToken $token * @return string */ abstract public function build_signature($request, $consumer, $token); /** * Verifies that a given signature is correct * * @param OAuthRequest $request * @param OAuthConsumer $consumer * @param OAuthToken $token * @param string $signature * @return bool */ public function check_signature($request, $consumer, $token, $signature) { $built = $this->build_signature($request, $consumer, $token); // Check for zero length, although unlikely here if (strlen($built) == 0 || strlen($signature) == 0) { return false; } if (strlen($built) != strlen($signature)) { return false; } // Avoid a timing leak with a (hopefully) time insensitive compare $result = 0; for ($i = 0; $i < strlen($signature); $i ++) { $result |= ord($built[$i]) ^ ord($signature[$i]); } return $result == 0; } } hybridauth/Thirdparty/OAuth/OAuthRequest.php 0000644 00000024106 15104256626 0015175 0 ustar 00 <?php /*! * This file is part of the OAuth PHP Library (https://code.google.com/p/oauth/) * * OAuth `PHP' Library is an open source software available under the MIT License. */ namespace Hybridauth\Thirdparty\OAuth; /** * Class OAuthRequest * * @package Hybridauth\Thirdparty\OAuth */ class OAuthRequest { public $parameters; public $http_method; public $http_url; // for debug purposes public $base_string; public static $version = '1.0'; public static $POST_INPUT = 'php://input'; /** * OAuthRequest constructor. * * @param $http_method * @param $http_url * @param null $parameters */ public function __construct($http_method, $http_url, $parameters = null) { $parameters = ($parameters) ? $parameters : array(); $parameters = array_merge(OAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $parameters); $this->parameters = $parameters; $this->http_method = $http_method; $this->http_url = $http_url; } /** * attempt to build up a request from what was passed to the server * * @param null $http_method * @param null $http_url * @param null $parameters * * @return OAuthRequest */ public static function from_request($http_method = null, $http_url = null, $parameters = null) { $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on") ? 'http' : 'https'; $http_url = ($http_url) ? $http_url : $scheme . '://' . $_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT'] . $_SERVER['REQUEST_URI']; $http_method = ($http_method) ? $http_method : $_SERVER['REQUEST_METHOD']; // We weren't handed any parameters, so let's find the ones relevant to // this request. // If you run XML-RPC or similar you should use this to provide your own // parsed parameter-list if (!$parameters) { // Find request headers $request_headers = OAuthUtil::get_headers(); // Parse the query-string to find GET parameters $parameters = OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']); // It's a POST request of the proper content-type, so parse POST // parameters and add those overriding any duplicates from GET if ($http_method == "POST" && isset($request_headers['Content-Type']) && strstr($request_headers['Content-Type'], 'application/x-www-form-urlencoded')) { $post_data = OAuthUtil::parse_parameters(file_get_contents(self::$POST_INPUT)); $parameters = array_merge($parameters, $post_data); } // We have a Authorization-header with OAuth data. Parse the header // and add those overriding any duplicates from GET or POST if (isset($request_headers['Authorization']) && substr($request_headers['Authorization'], 0, 6) == 'OAuth ') { $header_parameters = OAuthUtil::split_header($request_headers['Authorization']); $parameters = array_merge($parameters, $header_parameters); } } return new OAuthRequest($http_method, $http_url, $parameters); } /** * pretty much a helper function to set up the request * @param $consumer * @param $token * @param $http_method * @param $http_url * @param null $parameters * @return OAuthRequest */ public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters = null) { $parameters = ($parameters) ? $parameters : array(); $defaults = array( "oauth_version" => OAuthRequest::$version, "oauth_nonce" => OAuthRequest::generate_nonce(), "oauth_timestamp" => OAuthRequest::generate_timestamp(), "oauth_consumer_key" => $consumer->key ); if ($token) { $defaults['oauth_token'] = $token->key; } $parameters = array_merge($defaults, $parameters); return new OAuthRequest($http_method, $http_url, $parameters); } /** * @param $name * @param $value * @param bool $allow_duplicates */ public function set_parameter($name, $value, $allow_duplicates = true) { if ($allow_duplicates && isset($this->parameters[$name])) { // We have already added parameter(s) with this name, so add to the list if (is_scalar($this->parameters[$name])) { // This is the first duplicate, so transform scalar (string) // into an array so we can add the duplicates $this->parameters[$name] = array( $this->parameters[$name] ); } $this->parameters[$name][] = $value; } else { $this->parameters[$name] = $value; } } /** * @param $name * * @return |null */ public function get_parameter($name) { return isset($this->parameters[$name]) ? $this->parameters[$name] : null; } /** * @return array */ public function get_parameters() { return $this->parameters; } /** * @param $name */ public function unset_parameter($name) { unset($this->parameters[$name]); } /** * The request parameters, sorted and concatenated into a normalized string. * * @return string */ public function get_signable_parameters() { $params = []; // Grab all parameters. foreach ($this->parameters as $key_param => $value_param) { // Process only scalar values. if (is_scalar($value_param)) { $params[$key_param] = $value_param; } } // Remove oauth_signature if present // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.") if (isset($params['oauth_signature'])) { unset($params['oauth_signature']); } return OAuthUtil::build_http_query($params); } /** * Returns the base string of this request * * The base string defined as the method, the url * and the parameters (normalized), each urlencoded * and the concated with &. */ public function get_signature_base_string() { $parts = array( $this->get_normalized_http_method(), $this->get_normalized_http_url(), $this->get_signable_parameters() ); $parts = OAuthUtil::urlencode_rfc3986($parts); return implode('&', $parts); } /** * just uppercases the http method */ public function get_normalized_http_method() { return strtoupper($this->http_method); } /** * parses the url and rebuilds it to be * scheme://host/path */ public function get_normalized_http_url() { $parts = parse_url($this->http_url); $scheme = (isset($parts['scheme'])) ? $parts['scheme'] : 'http'; $port = (isset($parts['port'])) ? $parts['port'] : (($scheme == 'https') ? '443' : '80'); $host = (isset($parts['host'])) ? strtolower($parts['host']) : ''; $path = (isset($parts['path'])) ? $parts['path'] : ''; if (($scheme == 'https' && $port != '443') || ($scheme == 'http' && $port != '80')) { $host = "$host:$port"; } return "$scheme://$host$path"; } /** * builds a url usable for a GET request */ public function to_url() { $post_data = $this->to_postdata(); $out = $this->get_normalized_http_url(); if ($post_data) { $out .= '?' . $post_data; } return $out; } /** * builds the data one would send in a POST request */ public function to_postdata() { return OAuthUtil::build_http_query($this->parameters); } /** * builds the Authorization: header * @param null $realm * @return array */ public function to_header($realm = null) { $first = true; if ($realm) { $out = 'OAuth realm="' . OAuthUtil::urlencode_rfc3986($realm) . '"'; $first = false; } else { $out = 'OAuth'; } foreach ($this->parameters as $k => $v) { if (substr($k, 0, 5) != "oauth") { continue; } if (is_array($v)) { continue; } $out .= ($first) ? ' ' : ','; $out .= OAuthUtil::urlencode_rfc3986($k) . '="' . OAuthUtil::urlencode_rfc3986($v) . '"'; $first = false; } return array( 'Authorization' => $out ); //- hacked into this to make it return an array. 15/11/2014. } /** * @return string */ public function __toString() { return $this->to_url(); } /** * @param $signature_method * @param $consumer * @param $token */ public function sign_request($signature_method, $consumer, $token) { $this->set_parameter("oauth_signature_method", $signature_method->get_name(), false); $signature = $this->build_signature($signature_method, $consumer, $token); $this->set_parameter("oauth_signature", $signature, false); } /** * @param $signature_method * @param $consumer * @param $token * * @return mixed */ public function build_signature($signature_method, $consumer, $token) { $signature = $signature_method->build_signature($this, $consumer, $token); return $signature; } /** * util function: current timestamp */ private static function generate_timestamp() { return time(); } /** * util function: current nonce */ private static function generate_nonce() { $mt = microtime(); $rand = mt_rand(); return md5($mt . $rand); // md5s look nicer than numbers } } hybridauth/Thirdparty/OAuth/OAuthConsumer.php 0000644 00000001540 15104256626 0015335 0 ustar 00 <?php /*! * This file is part of the OAuth PHP Library (https://code.google.com/p/oauth/) * * OAuth `PHP' Library is an open source software available under the MIT License. */ namespace Hybridauth\Thirdparty\OAuth; /** * Class OAuthConsumer * * @package Hybridauth\Thirdparty\OAuth */ class OAuthConsumer { public $key; public $secret; public $callback_url; /** * OAuthConsumer constructor. * * @param $key * @param $secret * @param null $callback_url */ public function __construct($key, $secret, $callback_url = null) { $this->key = $key; $this->secret = $secret; $this->callback_url = $callback_url; } /** * @return string */ public function __toString() { return "OAuthConsumer[key=$this->key,secret=$this->secret]"; } } hybridauth/Thirdparty/OAuth/README.md 0000644 00000000320 15104256626 0013342 0 ustar 00 This package contains OAuth PHP Library. OAuth PHP Library is an open source software available under the MIT License. https://code.google.com/p/oauth/ http://oauth.googlecode.com/svn/code/php/LICENSE.txt hybridauth/Storage/Transient.php 0000644 00000006072 15104256626 0013007 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Storage; use Hybridauth\Exception\RuntimeException; /** * Hybridauth storage manager */ class Transient implements StorageInterface { /** * Namespace * * @var string */ protected $storeNamespace = 'HYBRIDAUTH::STORAGE'; /** * Key prefix * * @var string */ protected $keyPrefix = ''; /** * We need short term data storage, so we will be using WordPress transient * * @var array */ protected $transient = []; /** * Key used to identify the Transient * * @var string */ protected $key = ''; /** * Initiate a new session * * @throws RuntimeException */ public function __construct() { if(!empty($_COOKIE['lz_social_login'])){ $this->key = sanitize_text_field(wp_unslash($_COOKIE['lz_social_login'])); $transient = get_transient($this->key); if(!empty($transient)){ $this->transient = $transient; } return; } if(headers_sent()){ // phpcs:ignore throw new RuntimeException('HTTP headers already sent to browser and Hybridauth won\'t be able to start/resume LZ session. To resolve this, cookie must be set before outputing any data.'); } $this->key = 'lz_' . bin2hex(random_bytes(12)); if(!setcookie('lz_social_login', $this->key, time() + 90, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true)){ throw new RuntimeException('LZ session failed to start.'); } } /** * {@inheritdoc} */ public function get($key) { if(empty($this->transient)){ $this->transient = get_transient($this->key); if(empty($this->transient) || !is_array($this->transient)){ return null; } } if(isset($this->transient[$key])){ $value = $this->transient[$key]; if(is_array($value) && array_key_exists('lateObject', $value)){ $value = unserialize($value['lateObject']); } return $value; } return null; } /** * {@inheritdoc} */ public function set($key, $value) { if(is_object($value)){ // We encapsulate as our classes may be defined after session is initialized. $value = ['lateObject' => serialize($value)]; } $this->transient[$key] = $value; set_transient($this->key, $this->transient, 60); } /** * {@inheritdoc} */ public function clear() { delete_transient($this->key); setcookie('lz_social_login', '', time() - 3600, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true); } /** * {@inheritdoc} */ public function delete($key) { if(isset($this->transient, $this->transient[$key])){ unset($this->transient[$key]); set_transient($this->key, $this->transient, 60); } } /** * {@inheritdoc} */ public function deleteMatch($key) { if(isset($this->transient) && count($this->transient)){ foreach($this->transient as $k => $v) { if(strstr($k, $key)){ unset($this->transient[$k]); } } set_transient($this->key, $this->transient, 60); } } } hybridauth/Storage/Session.php 0000644 00000005731 15104256626 0012464 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Storage; use Hybridauth\Exception\RuntimeException; /** * Hybridauth storage manager */ class Session implements StorageInterface { /** * Namespace * * @var string */ protected $storeNamespace = 'HYBRIDAUTH::STORAGE'; /** * Key prefix * * @var string */ protected $keyPrefix = ''; /** * Initiate a new session * * @throws RuntimeException */ public function __construct() { if (session_id()) { return; } if (headers_sent()) { // phpcs:ignore throw new RuntimeException('HTTP headers already sent to browser and Hybridauth won\'t be able to start/resume PHP session. To resolve this, session_start() must be called before outputing any data.'); } if (!session_start()) { throw new RuntimeException('PHP session failed to start.'); } } /** * {@inheritdoc} */ public function get($key) { $key = $this->keyPrefix . strtolower($key); if (isset($_SESSION[$this->storeNamespace], $_SESSION[$this->storeNamespace][$key])) { $value = $_SESSION[$this->storeNamespace][$key]; if (is_array($value) && array_key_exists('lateObject', $value)) { $value = unserialize($value['lateObject']); } return $value; } return null; } /** * {@inheritdoc} */ public function set($key, $value) { $key = $this->keyPrefix . strtolower($key); if (is_object($value)) { // We encapsulate as our classes may be defined after session is initialized. $value = ['lateObject' => serialize($value)]; } $_SESSION[$this->storeNamespace][$key] = $value; } /** * {@inheritdoc} */ public function clear() { $_SESSION[$this->storeNamespace] = []; } /** * {@inheritdoc} */ public function delete($key) { $key = $this->keyPrefix . strtolower($key); if (isset($_SESSION[$this->storeNamespace], $_SESSION[$this->storeNamespace][$key])) { $tmp = $_SESSION[$this->storeNamespace]; unset($tmp[$key]); $_SESSION[$this->storeNamespace] = $tmp; } } /** * {@inheritdoc} */ public function deleteMatch($key) { $key = $this->keyPrefix . strtolower($key); if (isset($_SESSION[$this->storeNamespace]) && count($_SESSION[$this->storeNamespace])) { $tmp = $_SESSION[$this->storeNamespace]; foreach ($tmp as $k => $v) { if (strstr($k, $key)) { unset($tmp[$k]); } } $_SESSION[$this->storeNamespace] = $tmp; } } } hybridauth/Storage/StorageInterface.php 0000644 00000001615 15104256626 0014263 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Storage; /** * Hybridauth storage manager interface */ interface StorageInterface { /** * Retrieve a item from storage * * @param string $key * * @return mixed */ public function get($key); /** * Add or Update an item to storage * * @param string $key * @param string $value */ public function set($key, $value); /** * Delete an item from storage * * @param string $key */ public function delete($key); /** * Delete a item from storage * * @param string $key */ public function deleteMatch($key); /** * Clear all items in storage */ public function clear(); } hybridauth/autoload.php 0000644 00000003676 15104256626 0011253 0 ustar 00 <?php /** * Basic autoloader for Hybridauth library which you may use as it is or as a template * to suit your application's environment. * * Note that you'd ONLY need this file if you are not using composer. */ if (version_compare(PHP_VERSION, '5.4.0', '<')) { throw new Exception('Hybridauth 3 requires PHP version 5.4 or higher.'); } /** * Register the autoloader for Hybridauth classes. * * Based off the official PSR-4 autoloader example found at * http://www.php-fig.org/psr/psr-4/examples/ * * @param string $class The fully-qualified class name. * * @return void */ spl_autoload_register( function ($class) { // project-specific namespace prefix. Will only kicks in for Hybridauth's namespace. $prefix = 'Hybridauth\\'; // base directory for the namespace prefix. $base_dir = __DIR__; // By default, it points to this same folder. // You may change this path if having trouble detecting the path to // the source files. // does the class use the namespace prefix? $len = strlen($prefix); if (strncmp($prefix, $class, $len) !== 0) { // no, move to the next registered autoloader. return; } // get the relative class name. $relative_class = substr($class, $len); // replace the namespace prefix with the base directory, replace namespace // separators with directory separators in the relative class name, append // with .php $file = $base_dir . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $relative_class) . '.php'; // if the file exists, require it if (file_exists($file)) { require $file; return; } if(!defined('LOGINIZER_PRO_DIR')) { return; } $file = LOGINIZER_PRO_DIR . 'lib/hybridauth/'. str_replace('\\', DIRECTORY_SEPARATOR, $relative_class) . '.php'; if(file_exists($file)){ require $file; } } ); hybridauth/Data/Parser.php 0000644 00000004545 15104256626 0011544 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Data; /** * Parser * * This class is used to parse plain text into objects. It's used by hybriauth adapters to converts * providers api responses to a more 'manageable' format. */ final class Parser { /** * Decodes a string into an object. * * This method will first attempt to parse data as a JSON string (since most providers use this format) * then XML and parse_str. * * @param string $raw * * @return mixed */ public function parse($raw = null) { $data = $this->parseJson($raw); if (!$data) { $data = $this->parseXml($raw); if (!$data) { $data = $this->parseQueryString($raw); } } return $data; } /** * Decodes a JSON string * * @param $result * * @return mixed */ public function parseJson($result) { return json_decode($result); } /** * Decodes a XML string * * @param $result * * @return mixed */ public function parseXml($result) { libxml_use_internal_errors(true); $result = preg_replace('/([<\/])([a-z0-9-]+):/i', '$1', $result); $xml = simplexml_load_string($result); libxml_use_internal_errors(false); if (!$xml) { return []; } $arr = json_decode(json_encode((array)$xml), true); $arr = array($xml->getName() => $arr); return $arr; } /** * Parses a string into variables * * @param $result * * @return \StdClass */ public function parseQueryString($result) { parse_str($result, $output); if (!is_array($output)) { return $result; } $result = new \StdClass(); foreach ($output as $k => $v) { $result->$k = $v; } return $result; } /** * needs to be improved * * @param $birthday * * @return array */ public function parseBirthday($birthday) { $birthday = date_parse((string) $birthday); return [$birthday['year'], $birthday['month'], $birthday['day']]; } } hybridauth/Data/Collection.php 0000644 00000005305 15104256626 0012376 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Data; /** * A very basic Data collection. */ final class Collection { /** * Data collection * * @var mixed */ protected $collection = null; /** * @param mixed $data */ public function __construct($data = null) { $this->collection = (object)$data; } /** * Retrieves the whole collection as array * * @return mixed */ public function toArray() { return (array)$this->collection; } /** * Retrieves an item * * @param $property * * @return mixed */ public function get($property) { if ($this->exists($property)) { return $this->collection->$property; } return null; } /** * Add or update an item * * @param $property * @param mixed $value */ public function set($property, $value) { if ($property) { $this->collection->$property = $value; } } /** * .. until I come with a better name.. * * @param $property * * @return Collection */ public function filter($property) { if ($this->exists($property)) { $data = $this->get($property); if (!is_a($data, 'Collection')) { $data = new Collection($data); } return $data; } return new Collection([]); } /** * Checks whether an item within the collection * * @param $property * * @return bool */ public function exists($property) { return property_exists($this->collection, $property); } /** * Finds whether the collection is empty * * @return bool */ public function isEmpty() { return !(bool)$this->count(); } /** * Count all items in collection * * @return int */ public function count() { return count($this->properties()); } /** * Returns all items properties names * * @return array */ public function properties() { $properties = []; foreach ($this->collection as $key => $value) { $properties[] = $key; } return $properties; } /** * Returns all items values * * @return array */ public function values() { $values = []; foreach ($this->collection as $value) { $values[] = $value; } return $values; } } hybridauth/Provider/OpenID.php 0000644 00000002067 15104256626 0012344 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Provider; use Hybridauth\Adapter; /** * Generic OpenID providers adapter. * * Example: * * $config = [ * 'callback' => Hybridauth\HttpClient\Util::getCurrentUrl(), * * // authenticate with Yahoo openid * 'openid_identifier' => 'https://open.login.yahooapis.com/openid20/www.yahoo.com/xrds' * * // authenticate with stackexchange network openid * // 'openid_identifier' => 'https://openid.stackexchange.com/', * * // authenticate with Steam openid * // 'openid_identifier' => 'http://steamcommunity.com/openid', * * // etc. * ]; * * $adapter = new Hybridauth\Provider\OpenID($config); * * try { * $adapter->authenticate(); * * $userProfile = $adapter->getUserProfile(); * } catch (\Exception $e) { * echo $e->getMessage() ; * } */ class OpenID extends Adapter\OpenID { } hybridauth/Provider/LinkedInOpenID.php 0000644 00000004175 15104256626 0013764 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\Provider; use Hybridauth\Adapter\OAuth2; use Hybridauth\Data; use Hybridauth\Exception\UnexpectedApiResponseException; use Hybridauth\User; /** * LinkedIn OAuth2 provider adapter. */ class LinkedInOpenID extends OAuth2 { /** * {@inheritdoc} */ protected $scope = 'openid profile email'; /** * {@inheritdoc} */ protected $apiBaseUrl = 'https://api.linkedin.com/v2/'; /** * {@inheritdoc} */ protected $authorizeUrl = 'https://www.linkedin.com/oauth/v2/authorization'; /** * {@inheritdoc} */ protected $accessTokenUrl = 'https://www.linkedin.com/oauth/v2/accessToken'; /** * {@inheritdoc} */ protected $apiDocumentation = 'https://docs.microsoft.com/en-us/linkedin/shared/authentication/authentication'; /** * {@inheritdoc} */ protected function initialize() { parent::initialize(); if ($this->isRefreshTokenAvailable()) { $this->tokenRefreshParameters += [ 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret ]; } } /** * {@inheritdoc} */ public function getUserProfile() { $response = $this->apiRequest('/userinfo', 'GET', []); $data = new Data\Collection($response); if (!$data->exists('sub')) { throw new UnexpectedApiResponseException('Provider API returned an unexpected response.'); } $userProfile = new User\Profile(); $userProfile->firstName = $data->get('given_name'); $userProfile->lastName = $data->get('family_name'); $userProfile->identifier = $data->get('sub'); $userProfile->email = $data->get('email'); $userProfile->emailVerified = $data->get('email_verified'); $userProfile->displayName = $data->get('name'); $userProfile->photoURL = $data->get('picture'); return $userProfile; } } hybridauth/HttpClient/HttpClientInterface.php 0000644 00000002506 15104256626 0015407 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\HttpClient; /** * Hybridauth Http clients interface */ interface HttpClientInterface { /** * Send request to the remote server * * Returns the result (Raw response from the server) on success, FALSE on failure * * @param string $uri * @param string $method * @param array $parameters * @param array $headers * @param bool $multipart * * @return mixed */ public function request($uri, $method = 'GET', $parameters = [], $headers = [], $multipart = false); /** * Returns raw response from the server on success, FALSE on failure * * @return mixed */ public function getResponseBody(); /** * Retriever the headers returned in the response * * @return array */ public function getResponseHeader(); /** * Returns latest request HTTP status code * * @return int */ public function getResponseHttpCode(); /** * Returns latest error encountered by the client * This can be either a code or error message * * @return mixed */ public function getResponseClientError(); } hybridauth/HttpClient/Curl.php 0000644 00000017120 15104256626 0012413 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\HttpClient; /** * Hybridauth default Http client */ class Curl implements HttpClientInterface { /** * Default curl options * * These defaults options can be overwritten when sending requests. * * See setCurlOptions() * * @var array */ protected $curlOptions = [ CURLOPT_TIMEOUT => 30, CURLOPT_CONNECTTIMEOUT => 30, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_MAXREDIRS => 5, CURLINFO_HEADER_OUT => true, CURLOPT_ENCODING => 'identity', // phpcs:ignore CURLOPT_USERAGENT => 'Hybridauth, PHP Social Authentication Library (https://github.com/hybridauth/hybridauth)', ]; /** * Method request() arguments * * This is used for debugging. * * @var array */ protected $requestArguments = []; /** * Default request headers * * @var array */ protected $requestHeader = [ 'Accept' => '*/*', 'Cache-Control' => 'max-age=0', 'Connection' => 'keep-alive', 'Expect' => '', 'Pragma' => '', ]; /** * Raw response returned by server * * @var string */ protected $responseBody = ''; /** * Headers returned in the response * * @var array */ protected $responseHeader = []; /** * Response HTTP status code * * @var int */ protected $responseHttpCode = 0; /** * Last curl error number * * @var mixed */ protected $responseClientError = null; /** * Information about the last transfer * * @var mixed */ protected $responseClientInfo = []; /** * Hybridauth logger instance * * @var object */ protected $logger = null; /** * {@inheritdoc} */ public function request($uri, $method = 'GET', $parameters = [], $headers = [], $multipart = false) { $this->requestHeader = array_replace($this->requestHeader, (array)$headers); $this->requestArguments = [ 'uri' => $uri, 'method' => $method, 'parameters' => $parameters, 'headers' => $this->requestHeader, ]; $curl = curl_init(); switch ($method) { case 'GET': case 'DELETE': unset($this->curlOptions[CURLOPT_POST]); unset($this->curlOptions[CURLOPT_POSTFIELDS]); $uri = $uri . (strpos($uri, '?') ? '&' : '?') . http_build_query($parameters); if ($method === 'DELETE') { $this->curlOptions[CURLOPT_CUSTOMREQUEST] = 'DELETE'; } break; case 'PUT': case 'POST': case 'PATCH': $body_content = $multipart ? $parameters : http_build_query($parameters); if (isset($this->requestHeader['Content-Type']) && $this->requestHeader['Content-Type'] == 'application/json' ) { $body_content = json_encode($parameters); } if ($method === 'POST') { $this->curlOptions[CURLOPT_POST] = true; } else { $this->curlOptions[CURLOPT_CUSTOMREQUEST] = $method; } $this->curlOptions[CURLOPT_POSTFIELDS] = $body_content; break; } $this->curlOptions[CURLOPT_URL] = $uri; $this->curlOptions[CURLOPT_HTTPHEADER] = $this->prepareRequestHeaders(); $this->curlOptions[CURLOPT_HEADERFUNCTION] = [$this, 'fetchResponseHeader']; foreach ($this->curlOptions as $opt => $value) { curl_setopt($curl, $opt, $value); } $response = curl_exec($curl); $this->responseBody = $response; $this->responseHttpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); $this->responseClientError = curl_error($curl); $this->responseClientInfo = curl_getinfo($curl); if ($this->logger) { // phpcs:ignore $this->logger->debug(sprintf('%s::request( %s, %s ), response:', get_class($this), $uri, $method), $this->getResponse()); if (false === $response) { // phpcs:ignore $this->logger->error(sprintf('%s::request( %s, %s ), error:', get_class($this), $uri, $method), [$this->responseClientError]); } } curl_close($curl); return $this->responseBody; } /** * Get response details * * @return array Map structure of details */ public function getResponse() { $curlOptions = $this->curlOptions; $curlOptions[CURLOPT_HEADERFUNCTION] = '*omitted'; return [ 'request' => $this->getRequestArguments(), 'response' => [ 'code' => $this->getResponseHttpCode(), 'headers' => $this->getResponseHeader(), 'body' => $this->getResponseBody(), ], 'client' => [ 'error' => $this->getResponseClientError(), 'info' => $this->getResponseClientInfo(), 'opts' => $curlOptions, ], ]; } /** * Reset curl options * * @param array $curlOptions */ public function setCurlOptions($curlOptions) { foreach ($curlOptions as $opt => $value) { $this->curlOptions[$opt] = $value; } } /** * Set logger instance * * @param object $logger */ public function setLogger($logger) { $this->logger = $logger; } /** * {@inheritdoc} */ public function getResponseBody() { return $this->responseBody; } /** * {@inheritdoc} */ public function getResponseHeader() { return $this->responseHeader; } /** * {@inheritdoc} */ public function getResponseHttpCode() { return $this->responseHttpCode; } /** * {@inheritdoc} */ public function getResponseClientError() { return $this->responseClientError; } /** * @return array */ protected function getResponseClientInfo() { return $this->responseClientInfo; } /** * Returns method request() arguments * * This is used for debugging. * * @return array */ protected function getRequestArguments() { return $this->requestArguments; } /** * Fetch server response headers * * @param mixed $curl * @param string $header * * @return int */ protected function fetchResponseHeader($curl, $header) { $pos = strpos($header, ':'); if (!empty($pos)) { $key = str_replace('-', '_', strtolower(substr($header, 0, $pos))); $value = trim(substr($header, $pos + 2)); $this->responseHeader[$key] = $value; } return strlen($header); } /** * Convert request headers to the expect curl format * * @return array */ protected function prepareRequestHeaders() { $headers = []; foreach ($this->requestHeader as $header => $value) { $headers[] = trim($header) . ': ' . trim($value); } return $headers; } } hybridauth/HttpClient/Guzzle.php 0000644 00000015113 15104256626 0012766 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\HttpClient; use GuzzleHttp\Client; use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Exception\ServerException; use GuzzleHttp\Exception\TransferException; /** * Hybridauth Guzzle Http client * * Note: This is just a proof of concept. Feel free to improve it. * * Example: * * <code> * $guzzle = new Hybridauth\HttpClient\Guzzle(new GuzzleHttp\Client(), [ * 'verify' => '/path/to/your/certificate.crt', * 'headers' => ['User-Agent' => '..'] * // 'proxy' => ... * ]); * * $adapter = new Hybridauth\Provider\Github($config, $guzzle); * * $adapter->authenticate(); * </code> */ class Guzzle implements HttpClientInterface { /** * Method request() arguments * * This is used for debugging. * * @var array */ protected $requestArguments = []; /** * Default request headers * * @var array */ protected $requestHeader = []; /** * Raw response returned by server * * @var string */ protected $responseBody = ''; /** * Headers returned in the response * * @var array */ protected $responseHeader = []; /** * Response HTTP status code * * @var int */ protected $responseHttpCode = 0; /** * Last curl error number * * @var mixed */ protected $responseClientError = null; /** * Information about the last transfer * * @var mixed */ protected $responseClientInfo = []; /** * Hybridauth logger instance * * @var object */ protected $logger = null; /** * GuzzleHttp client * * @var \GuzzleHttp\Client */ protected $client = null; /** * .. * @param null $client * @param array $config */ public function __construct($client = null, $config = []) { $this->client = $client ? $client : new Client($config); } /** * {@inheritdoc} */ public function request($uri, $method = 'GET', $parameters = [], $headers = [], $multipart = false) { $this->requestHeader = array_replace($this->requestHeader, (array)$headers); $this->requestArguments = [ 'uri' => $uri, 'method' => $method, 'parameters' => $parameters, 'headers' => $this->requestHeader, ]; $response = null; try { switch ($method) { case 'GET': case 'DELETE': $response = $this->client->request($method, $uri, [ 'query' => $parameters, 'headers' => $this->requestHeader, ]); break; case 'PUT': case 'PATCH': case 'POST': $body_type = $multipart ? 'multipart' : 'form_params'; if (isset($this->requestHeader['Content-Type']) && $this->requestHeader['Content-Type'] === 'application/json' ) { $body_type = 'json'; } $body_content = $parameters; if ($multipart) { $body_content = []; foreach ($parameters as $key => $val) { if ($val instanceof \CURLFile) { $val = fopen($val->getFilename(), 'r'); } $body_content[] = [ 'name' => $key, 'contents' => $val, ]; } } $response = $this->client->request($method, $uri, [ $body_type => $body_content, 'headers' => $this->requestHeader, ]); break; } } catch (\Exception $e) { $response = $e->getResponse(); $this->responseClientError = $e->getMessage(); } if (!$this->responseClientError) { $this->responseBody = $response->getBody(); $this->responseHttpCode = $response->getStatusCode(); $this->responseHeader = $response->getHeaders(); } if ($this->logger) { // phpcs:ignore $this->logger->debug(sprintf('%s::request( %s, %s ), response:', get_class($this), $uri, $method), $this->getResponse()); if ($this->responseClientError) { // phpcs:ignore $this->logger->error(sprintf('%s::request( %s, %s ), error:', get_class($this), $uri, $method), [$this->responseClientError]); } } return $this->responseBody; } /** * Get response details * * @return array Map structure of details */ public function getResponse() { return [ 'request' => $this->getRequestArguments(), 'response' => [ 'code' => $this->getResponseHttpCode(), 'headers' => $this->getResponseHeader(), 'body' => $this->getResponseBody(), ], 'client' => [ 'error' => $this->getResponseClientError(), 'info' => $this->getResponseClientInfo(), 'opts' => null, ], ]; } /** * Set logger instance * * @param object $logger */ public function setLogger($logger) { $this->logger = $logger; } /** * {@inheritdoc} */ public function getResponseBody() { return $this->responseBody; } /** * {@inheritdoc} */ public function getResponseHeader() { return $this->responseHeader; } /** * {@inheritdoc} */ public function getResponseHttpCode() { return $this->responseHttpCode; } /** * {@inheritdoc} */ public function getResponseClientError() { return $this->responseClientError; } /** * @return array */ protected function getResponseClientInfo() { return $this->responseClientInfo; } /** * Returns method request() arguments * * This is used for debugging. * * @return array */ protected function getRequestArguments() { return $this->requestArguments; } } hybridauth/HttpClient/Util.php 0000644 00000004773 15104256626 0012435 0 ustar 00 <?php /*! * Hybridauth * https://hybridauth.github.io | https://github.com/hybridauth/hybridauth * (c) 2017 Hybridauth authors | https://hybridauth.github.io/license.html */ namespace Hybridauth\HttpClient; use Hybridauth\Data; /** * HttpClient\Util home to a number of utility functions. */ class Util { /** * Redirect handler. * * @var callable|null */ protected static $redirectHandler; /** * Exit handler. * * @var callable|null */ protected static $exitHandler; /** * Redirect to a given URL. * * In case your application need to perform certain required actions before Hybridauth redirect users * to IDPs websites, the default behaviour can be altered in one of two ways: * If callable $redirectHandler is defined, it will be called instead. * If callable $exitHandler is defined, it will be called instead of exit(). * * @param string $url * * @return mixed */ public static function redirect($url) { if (static::$redirectHandler) { return call_user_func(static::$redirectHandler, $url); } header(sprintf('Location: %s', $url)); if (static::$exitHandler) { return call_user_func(static::$exitHandler); } exit(1); } /** * Redirect handler to which the regular redirect() will yield the action of redirecting users. * * @param callable $callback */ public static function setRedirectHandler($callback) { self::$redirectHandler = $callback; } /** * Exit handler will be called instead of regular exit() when calling Util::redirect() method. * * @param callable $callback */ public static function setExitHandler($callback) { self::$exitHandler = $callback; } /** * Returns the Current URL. * * @param bool $requestUri TRUE to use $_SERVER['REQUEST_URI'], FALSE for $_SERVER['PHP_SELF'] * * @return string */ public static function getCurrentUrl($requestUri = false) { $collection = new Data\Collection($_SERVER); $protocol = 'http://'; if (($collection->get('HTTPS') && $collection->get('HTTPS') !== 'off') || $collection->get('HTTP_X_FORWARDED_PROTO') === 'https') { $protocol = 'https://'; } return $protocol . $collection->get('HTTP_HOST') . $collection->get($requestUri ? 'REQUEST_URI' : 'PHP_SELF'); } } IPv6/BigInteger.php 0000644 00000347502 15104256626 0010102 0 ustar 00 <?php /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ /** * Pure-PHP arbitrary precision integer arithmetic library. * * Supports base-2, base-10, base-16, and base-256 numbers. Uses the GMP or BCMath extensions, if available, * and an internal implementation, otherwise. * * PHP versions 4 and 5 * * {@internal (all DocBlock comments regarding implementation - such as the one that follows - refer to the * {@link MATH_BIGINTEGER_MODE_INTERNAL MATH_BIGINTEGER_MODE_INTERNAL} mode) * * Math_BigInteger uses base-2**26 to perform operations such as multiplication and division and * base-2**52 (ie. two base 2**26 digits) to perform addition and subtraction. Because the largest possible * value when multiplying two base-2**26 numbers together is a base-2**52 number, double precision floating * point numbers - numbers that should be supported on most hardware and whose significand is 53 bits - are * used. As a consequence, bitwise operators such as >> and << cannot be used, nor can the modulo operator %, * which only supports integers. Although this fact will slow this library down, the fact that such a high * base is being used should more than compensate. * * When PHP version 6 is officially released, we'll be able to use 64-bit integers. This should, once again, * allow bitwise operators, and will increase the maximum possible base to 2**31 (or 2**62 for addition / * subtraction). * * Numbers are stored in {@link http://en.wikipedia.org/wiki/Endianness little endian} format. ie. * (new Math_BigInteger(pow(2, 26)))->value = array(0, 1) * * Useful resources are as follows: * * - {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf Handbook of Applied Cryptography (HAC)} * - {@link http://math.libtomcrypt.com/files/tommath.pdf Multi-Precision Math (MPM)} * - Java's BigInteger classes. See /j2se/src/share/classes/java/math in jdk-1_5_0-src-jrl.zip * * Here's an example of how to use this library: * <code> * <?php * include('Math/BigInteger.php'); * * $a = new Math_BigInteger(2); * $b = new Math_BigInteger(3); * * $c = $a->add($b); * * echo $c->toString(); // outputs 5 * ?> * </code> * * LICENSE: This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA * * @category Math * @package Math_BigInteger * @author Jim Wigginton <terrafrost@php.net> * @copyright MMVI Jim Wigginton * @license http://www.opensource.org/licenses/mit-license.html MIT License * @version $Id: BigInteger.php,v 1.33 2010/03/22 22:32:03 terrafrost Exp $ * @link http://pear.php.net/package/Math_BigInteger */ /**#@+ * Reduction constants * * @access private * @see Math_BigInteger::_reduce() */ /** * @see Math_BigInteger::_montgomery() * @see Math_BigInteger::_prepMontgomery() */ define('MATH_BIGINTEGER_MONTGOMERY', 0); /** * @see Math_BigInteger::_barrett() */ define('MATH_BIGINTEGER_BARRETT', 1); /** * @see Math_BigInteger::_mod2() */ define('MATH_BIGINTEGER_POWEROF2', 2); /** * @see Math_BigInteger::_remainder() */ define('MATH_BIGINTEGER_CLASSIC', 3); /** * @see Math_BigInteger::__clone() */ define('MATH_BIGINTEGER_NONE', 4); /**#@-*/ /**#@+ * Array constants * * Rather than create a thousands and thousands of new Math_BigInteger objects in repeated function calls to add() and * multiply() or whatever, we'll just work directly on arrays, taking them in as parameters and returning them. * * @access private */ /** * $result[MATH_BIGINTEGER_VALUE] contains the value. */ define('MATH_BIGINTEGER_VALUE', 0); /** * $result[MATH_BIGINTEGER_SIGN] contains the sign. */ define('MATH_BIGINTEGER_SIGN', 1); /**#@-*/ /**#@+ * @access private * @see Math_BigInteger::_montgomery() * @see Math_BigInteger::_barrett() */ /** * Cache constants * * $cache[MATH_BIGINTEGER_VARIABLE] tells us whether or not the cached data is still valid. */ define('MATH_BIGINTEGER_VARIABLE', 0); /** * $cache[MATH_BIGINTEGER_DATA] contains the cached data. */ define('MATH_BIGINTEGER_DATA', 1); /**#@-*/ /**#@+ * Mode constants. * * @access private * @see Math_BigInteger::Math_BigInteger() */ /** * To use the pure-PHP implementation */ define('MATH_BIGINTEGER_MODE_INTERNAL', 1); /** * To use the BCMath library * * (if enabled; otherwise, the internal implementation will be used) */ define('MATH_BIGINTEGER_MODE_BCMATH', 2); /** * To use the GMP library * * (if present; otherwise, either the BCMath or the internal implementation will be used) */ define('MATH_BIGINTEGER_MODE_GMP', 3); /**#@-*/ /** * The largest digit that may be used in addition / subtraction * * (we do pow(2, 52) instead of using 4503599627370496, directly, because some PHP installations * will truncate 4503599627370496) * * @access private */ define('MATH_BIGINTEGER_MAX_DIGIT52', pow(2, 52)); /** * Karatsuba Cutoff * * At what point do we switch between Karatsuba multiplication and schoolbook long multiplication? * * @access private */ define('MATH_BIGINTEGER_KARATSUBA_CUTOFF', 25); /** * Pure-PHP arbitrary precision integer arithmetic library. Supports base-2, base-10, base-16, and base-256 * numbers. * * @author Jim Wigginton <terrafrost@php.net> * @version 1.0.0RC4 * @access public * @package Math_BigInteger */ class Math_BigInteger { /** * Holds the BigInteger's value. * * @var Array * @access private */ var $value; /** * Holds the BigInteger's magnitude. * * @var Boolean * @access private */ var $is_negative = false; /** * Random number generator function * * @see setRandomGenerator() * @access private */ var $generator = 'mt_rand'; /** * Precision * * @see setPrecision() * @access private */ var $precision = -1; /** * Precision Bitmask * * @see setPrecision() * @access private */ var $bitmask = false; /** * Mode independant value used for serialization. * * If the bcmath or gmp extensions are installed $this->value will be a non-serializable resource, hence the need for * a variable that'll be serializable regardless of whether or not extensions are being used. Unlike $this->value, * however, $this->hex is only calculated when $this->__sleep() is called. * * @see __sleep() * @see __wakeup() * @var String * @access private */ var $hex; /** * Converts base-2, base-10, base-16, and binary strings (eg. base-256) to BigIntegers. * * If the second parameter - $base - is negative, then it will be assumed that the number's are encoded using * two's compliment. The sole exception to this is -10, which is treated the same as 10 is. * * Here's an example: * <code> * <?php * include('Math/BigInteger.php'); * * $a = new Math_BigInteger('0x32', 16); // 50 in base-16 * * echo $a->toString(); // outputs 50 * ?> * </code> * * @param optional $x base-10 number or base-$base number if $base set. * @param optional integer $base * @return Math_BigInteger * @access public */ function __construct($x = 0, $base = 10) { if ( !defined('MATH_BIGINTEGER_MODE') ) { switch (true) { case extension_loaded('gmp'): define('MATH_BIGINTEGER_MODE', MATH_BIGINTEGER_MODE_GMP); break; case extension_loaded('bcmath'): define('MATH_BIGINTEGER_MODE', MATH_BIGINTEGER_MODE_BCMATH); break; default: define('MATH_BIGINTEGER_MODE', MATH_BIGINTEGER_MODE_INTERNAL); } } switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: if (is_resource($x) && get_resource_type($x) == 'GMP integer') { $this->value = $x; return; } $this->value = gmp_init(0); break; case MATH_BIGINTEGER_MODE_BCMATH: $this->value = '0'; break; default: $this->value = array(); } if (empty($x)) { return; } switch ($base) { case -256: if (ord($x[0]) & 0x80) { $x = ~$x; $this->is_negative = true; } case 256: switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: $sign = $this->is_negative ? '-' : ''; $this->value = gmp_init($sign . '0x' . bin2hex($x)); break; case MATH_BIGINTEGER_MODE_BCMATH: // round $len to the nearest 4 (thanks, DavidMJ!) $len = (strlen($x) + 3) & 0xFFFFFFFC; $x = str_pad($x, $len, chr(0), STR_PAD_LEFT); for ($i = 0; $i < $len; $i+= 4) { $this->value = bcmul($this->value, '4294967296', 0); // 4294967296 == 2**32 $this->value = bcadd($this->value, 0x1000000 * ord($x[$i]) + ((ord($x[$i + 1]) << 16) | (ord($x[$i + 2]) << 8) | ord($x[$i + 3])), 0); } if ($this->is_negative) { $this->value = '-' . $this->value; } break; // converts a base-2**8 (big endian / msb) number to base-2**26 (little endian / lsb) default: while (strlen($x)) { $this->value[] = $this->_bytes2int($this->_base256_rshift($x, 26)); } } if ($this->is_negative) { if (MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_INTERNAL) { $this->is_negative = false; } $temp = $this->add(new Math_BigInteger('-1')); $this->value = $temp->value; } break; case 16: case -16: if ($base > 0 && $x[0] == '-') { $this->is_negative = true; $x = substr($x, 1); } $x = preg_replace('#^(?:0x)?([A-Fa-f0-9]*).*#', '$1', $x); $is_negative = false; if ($base < 0 && hexdec($x[0]) >= 8) { $this->is_negative = $is_negative = true; $x = bin2hex(~pack('H*', $x)); } switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: $temp = $this->is_negative ? '-0x' . $x : '0x' . $x; $this->value = gmp_init($temp); $this->is_negative = false; break; case MATH_BIGINTEGER_MODE_BCMATH: $x = ( strlen($x) & 1 ) ? '0' . $x : $x; $temp = new Math_BigInteger(pack('H*', $x), 256); $this->value = $this->is_negative ? '-' . $temp->value : $temp->value; $this->is_negative = false; break; default: $x = ( strlen($x) & 1 ) ? '0' . $x : $x; $temp = new Math_BigInteger(pack('H*', $x), 256); $this->value = $temp->value; } if ($is_negative) { $temp = $this->add(new Math_BigInteger('-1')); $this->value = $temp->value; } break; case 10: case -10: $x = preg_replace('#^(-?[0-9]*).*#', '$1', $x); switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: $this->value = gmp_init($x); break; case MATH_BIGINTEGER_MODE_BCMATH: // explicitly casting $x to a string is necessary, here, since doing $x[0] on -1 yields different // results then doing it on '-1' does (modInverse does $x[0]) $this->value = (string) $x; break; default: $temp = new Math_BigInteger(); // array(10000000) is 10**7 in base-2**26. 10**7 is the closest to 2**26 we can get without passing it. $multiplier = new Math_BigInteger(); $multiplier->value = array(10000000); if ($x[0] == '-') { $this->is_negative = true; $x = substr($x, 1); } $x = str_pad($x, strlen($x) + (6 * strlen($x)) % 7, 0, STR_PAD_LEFT); while (strlen($x)) { $temp = $temp->multiply($multiplier); $temp = $temp->add(new Math_BigInteger($this->_int2bytes(substr($x, 0, 7)), 256)); $x = substr($x, 7); } $this->value = $temp->value; } break; case 2: // base-2 support originally implemented by Lluis Pamies - thanks! case -2: if ($base > 0 && $x[0] == '-') { $this->is_negative = true; $x = substr($x, 1); } $x = preg_replace('#^([01]*).*#', '$1', $x); $x = str_pad($x, strlen($x) + (3 * strlen($x)) % 4, 0, STR_PAD_LEFT); $str = '0x'; while (strlen($x)) { $part = substr($x, 0, 4); $str.= dechex(bindec($part)); $x = substr($x, 4); } if ($this->is_negative) { $str = '-' . $str; } $temp = new Math_BigInteger($str, 8 * $base); // ie. either -16 or +16 $this->value = $temp->value; $this->is_negative = $temp->is_negative; break; default: // base not supported, so we'll let $this == 0 } } /** * This function exists to maintain backwards compatibility with older code * * @param int $x base-10 number or base-$base number if $base set. * @param int $base Number base */ public function Math_BigInteger($x = 0, $base = 10) { self::__construct($x, $base); } /** * Converts a BigInteger to a byte string (eg. base-256). * * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're * saved as two's compliment. * * Here's an example: * <code> * <?php * include('Math/BigInteger.php'); * * $a = new Math_BigInteger('65'); * * echo $a->toBytes(); // outputs chr(65) * ?> * </code> * * @param Boolean $twos_compliment * @return String * @access public * @internal Converts a base-2**26 number to base-2**8 */ function toBytes($twos_compliment = false) { if ($twos_compliment) { $comparison = $this->compare(new Math_BigInteger()); if ($comparison == 0) { return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; } $temp = $comparison < 0 ? $this->add(new Math_BigInteger(1)) : $this->copy(); $bytes = $temp->toBytes(); if (empty($bytes)) { // eg. if the number we're trying to convert is -1 $bytes = chr(0); } if (ord($bytes[0]) & 0x80) { $bytes = chr(0) . $bytes; } return $comparison < 0 ? ~$bytes : $bytes; } switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: if (gmp_cmp($this->value, gmp_init(0)) == 0) { return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; } $temp = gmp_strval(gmp_abs($this->value), 16); $temp = ( strlen($temp) & 1 ) ? '0' . $temp : $temp; $temp = pack('H*', $temp); return $this->precision > 0 ? substr(str_pad($temp, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) : ltrim($temp, chr(0)); case MATH_BIGINTEGER_MODE_BCMATH: if ($this->value === '0') { return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; } $value = ''; $current = $this->value; if ($current[0] == '-') { $current = substr($current, 1); } while (bccomp($current, '0', 0) > 0) { $temp = bcmod($current, '16777216'); $value = chr($temp >> 16) . chr($temp >> 8) . chr($temp) . $value; $current = bcdiv($current, '16777216', 0); } return $this->precision > 0 ? substr(str_pad($value, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) : ltrim($value, chr(0)); } if (!count($this->value)) { return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; } $result = $this->_int2bytes($this->value[count($this->value) - 1]); $temp = $this->copy(); for ($i = count($temp->value) - 2; $i >= 0; --$i) { $temp->_base256_lshift($result, 26); $result = $result | str_pad($temp->_int2bytes($temp->value[$i]), strlen($result), chr(0), STR_PAD_LEFT); } return $this->precision > 0 ? str_pad(substr($result, -(($this->precision + 7) >> 3)), ($this->precision + 7) >> 3, chr(0), STR_PAD_LEFT) : $result; } /** * Converts a BigInteger to a hex string (eg. base-16)). * * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're * saved as two's compliment. * * Here's an example: * <code> * <?php * include('Math/BigInteger.php'); * * $a = new Math_BigInteger('65'); * * echo $a->toHex(); // outputs '41' * ?> * </code> * * @param Boolean $twos_compliment * @return String * @access public * @internal Converts a base-2**26 number to base-2**8 */ function toHex($twos_compliment = false) { return bin2hex($this->toBytes($twos_compliment)); } /** * Converts a BigInteger to a bit string (eg. base-2). * * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're * saved as two's compliment. * * Here's an example: * <code> * <?php * include('Math/BigInteger.php'); * * $a = new Math_BigInteger('65'); * * echo $a->toBits(); // outputs '1000001' * ?> * </code> * * @param Boolean $twos_compliment * @return String * @access public * @internal Converts a base-2**26 number to base-2**2 */ function toBits($twos_compliment = false) { $hex = $this->toHex($twos_compliment); $bits = ''; for ($i = 0, $end = strlen($hex) & 0xFFFFFFF8; $i < $end; $i+=8) { $bits.= str_pad(decbin(hexdec(substr($hex, $i, 8))), 32, '0', STR_PAD_LEFT); } if ($end != strlen($hex)) { // hexdec('') == 0 $bits.= str_pad(decbin(hexdec(substr($hex, $end))), strlen($hex) & 7, '0', STR_PAD_LEFT); } return $this->precision > 0 ? substr($bits, -$this->precision) : ltrim($bits, '0'); } /** * Converts a BigInteger to a base-10 number. * * Here's an example: * <code> * <?php * include('Math/BigInteger.php'); * * $a = new Math_BigInteger('50'); * * echo $a->toString(); // outputs 50 * ?> * </code> * * @return String * @access public * @internal Converts a base-2**26 number to base-10**7 (which is pretty much base-10) */ function toString() { switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: return gmp_strval($this->value); case MATH_BIGINTEGER_MODE_BCMATH: if ($this->value === '0') { return '0'; } return ltrim($this->value, '0'); } if (!count($this->value)) { return '0'; } $temp = $this->copy(); $temp->is_negative = false; $divisor = new Math_BigInteger(); $divisor->value = array(10000000); // eg. 10**7 $result = ''; while (count($temp->value)) { list($temp, $mod) = $temp->divide($divisor); $result = str_pad(isset($mod->value[0]) ? $mod->value[0] : '', 7, '0', STR_PAD_LEFT) . $result; } $result = ltrim($result, '0'); if (empty($result)) { $result = '0'; } if ($this->is_negative) { $result = '-' . $result; } return $result; } /** * Copy an object * * PHP5 passes objects by reference while PHP4 passes by value. As such, we need a function to guarantee * that all objects are passed by value, when appropriate. More information can be found here: * * {@link http://php.net/language.oop5.basic#51624} * * @access public * @see __clone() * @return Math_BigInteger */ function copy() { $temp = new Math_BigInteger(); $temp->value = $this->value; $temp->is_negative = $this->is_negative; $temp->generator = $this->generator; $temp->precision = $this->precision; $temp->bitmask = $this->bitmask; return $temp; } /** * __toString() magic method * * Will be called, automatically, if you're supporting just PHP5. If you're supporting PHP4, you'll need to call * toString(). * * @access public * @internal Implemented per a suggestion by Techie-Michael - thanks! */ function __toString() { return $this->toString(); } /** * __clone() magic method * * Although you can call Math_BigInteger::__toString() directly in PHP5, you cannot call Math_BigInteger::__clone() * directly in PHP5. You can in PHP4 since it's not a magic method, but in PHP5, you have to call it by using the PHP5 * only syntax of $y = clone $x. As such, if you're trying to write an application that works on both PHP4 and PHP5, * call Math_BigInteger::copy(), instead. * * @access public * @see copy() * @return Math_BigInteger */ function __clone() { return $this->copy(); } /** * __sleep() magic method * * Will be called, automatically, when serialize() is called on a Math_BigInteger object. * * @see __wakeup() * @access public */ function __sleep() { $this->hex = $this->toHex(true); $vars = array('hex'); if ($this->generator != 'mt_rand') { $vars[] = 'generator'; } if ($this->precision > 0) { $vars[] = 'precision'; } return $vars; } /** * __wakeup() magic method * * Will be called, automatically, when unserialize() is called on a Math_BigInteger object. * * @see __sleep() * @access public */ function __wakeup() { $temp = new Math_BigInteger($this->hex, -16); $this->value = $temp->value; $this->is_negative = $temp->is_negative; $this->setRandomGenerator($this->generator); if ($this->precision > 0) { // recalculate $this->bitmask $this->setPrecision($this->precision); } } /** * Adds two BigIntegers. * * Here's an example: * <code> * <?php * include('Math/BigInteger.php'); * * $a = new Math_BigInteger('10'); * $b = new Math_BigInteger('20'); * * $c = $a->add($b); * * echo $c->toString(); // outputs 30 * ?> * </code> * * @param Math_BigInteger $y * @return Math_BigInteger * @access public * @internal Performs base-2**52 addition */ function add($y) { switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: $temp = new Math_BigInteger(); $temp->value = gmp_add($this->value, $y->value); return $this->_normalize($temp); case MATH_BIGINTEGER_MODE_BCMATH: $temp = new Math_BigInteger(); $temp->value = bcadd($this->value, $y->value, 0); return $this->_normalize($temp); } $temp = $this->_add($this->value, $this->is_negative, $y->value, $y->is_negative); $result = new Math_BigInteger(); $result->value = $temp[MATH_BIGINTEGER_VALUE]; $result->is_negative = $temp[MATH_BIGINTEGER_SIGN]; return $this->_normalize($result); } /** * Performs addition. * * @param Array $x_value * @param Boolean $x_negative * @param Array $y_value * @param Boolean $y_negative * @return Array * @access private */ function _add($x_value, $x_negative, $y_value, $y_negative) { $x_size = count($x_value); $y_size = count($y_value); if ($x_size == 0) { return array( MATH_BIGINTEGER_VALUE => $y_value, MATH_BIGINTEGER_SIGN => $y_negative ); } else if ($y_size == 0) { return array( MATH_BIGINTEGER_VALUE => $x_value, MATH_BIGINTEGER_SIGN => $x_negative ); } // subtract, if appropriate if ( $x_negative != $y_negative ) { if ( $x_value == $y_value ) { return array( MATH_BIGINTEGER_VALUE => array(), MATH_BIGINTEGER_SIGN => false ); } $temp = $this->_subtract($x_value, false, $y_value, false); $temp[MATH_BIGINTEGER_SIGN] = $this->_compare($x_value, false, $y_value, false) > 0 ? $x_negative : $y_negative; return $temp; } if ($x_size < $y_size) { $size = $x_size; $value = $y_value; } else { $size = $y_size; $value = $x_value; } $value[] = 0; // just in case the carry adds an extra digit $carry = 0; for ($i = 0, $j = 1; $j < $size; $i+=2, $j+=2) { $sum = $x_value[$j] * 0x4000000 + $x_value[$i] + $y_value[$j] * 0x4000000 + $y_value[$i] + $carry; $carry = $sum >= MATH_BIGINTEGER_MAX_DIGIT52; // eg. floor($sum / 2**52); only possible values (in any base) are 0 and 1 $sum = $carry ? $sum - MATH_BIGINTEGER_MAX_DIGIT52 : $sum; $temp = (int) ($sum / 0x4000000); $value[$i] = (int) ($sum - 0x4000000 * $temp); // eg. a faster alternative to fmod($sum, 0x4000000) $value[$j] = $temp; } if ($j == $size) { // ie. if $y_size is odd $sum = $x_value[$i] + $y_value[$i] + $carry; $carry = $sum >= 0x4000000; $value[$i] = $carry ? $sum - 0x4000000 : $sum; ++$i; // ie. let $i = $j since we've just done $value[$i] } if ($carry) { for (; $value[$i] == 0x3FFFFFF; ++$i) { $value[$i] = 0; } ++$value[$i]; } return array( MATH_BIGINTEGER_VALUE => $this->_trim($value), MATH_BIGINTEGER_SIGN => $x_negative ); } /** * Subtracts two BigIntegers. * * Here's an example: * <code> * <?php * include('Math/BigInteger.php'); * * $a = new Math_BigInteger('10'); * $b = new Math_BigInteger('20'); * * $c = $a->subtract($b); * * echo $c->toString(); // outputs -10 * ?> * </code> * * @param Math_BigInteger $y * @return Math_BigInteger * @access public * @internal Performs base-2**52 subtraction */ function subtract($y) { switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: $temp = new Math_BigInteger(); $temp->value = gmp_sub($this->value, $y->value); return $this->_normalize($temp); case MATH_BIGINTEGER_MODE_BCMATH: $temp = new Math_BigInteger(); $temp->value = bcsub($this->value, $y->value, 0); return $this->_normalize($temp); } $temp = $this->_subtract($this->value, $this->is_negative, $y->value, $y->is_negative); $result = new Math_BigInteger(); $result->value = $temp[MATH_BIGINTEGER_VALUE]; $result->is_negative = $temp[MATH_BIGINTEGER_SIGN]; return $this->_normalize($result); } /** * Performs subtraction. * * @param Array $x_value * @param Boolean $x_negative * @param Array $y_value * @param Boolean $y_negative * @return Array * @access private */ function _subtract($x_value, $x_negative, $y_value, $y_negative) { $x_size = count($x_value); $y_size = count($y_value); if ($x_size == 0) { return array( MATH_BIGINTEGER_VALUE => $y_value, MATH_BIGINTEGER_SIGN => !$y_negative ); } else if ($y_size == 0) { return array( MATH_BIGINTEGER_VALUE => $x_value, MATH_BIGINTEGER_SIGN => $x_negative ); } // add, if appropriate (ie. -$x - +$y or +$x - -$y) if ( $x_negative != $y_negative ) { $temp = $this->_add($x_value, false, $y_value, false); $temp[MATH_BIGINTEGER_SIGN] = $x_negative; return $temp; } $diff = $this->_compare($x_value, $x_negative, $y_value, $y_negative); if ( !$diff ) { return array( MATH_BIGINTEGER_VALUE => array(), MATH_BIGINTEGER_SIGN => false ); } // switch $x and $y around, if appropriate. if ( (!$x_negative && $diff < 0) || ($x_negative && $diff > 0) ) { $temp = $x_value; $x_value = $y_value; $y_value = $temp; $x_negative = !$x_negative; $x_size = count($x_value); $y_size = count($y_value); } // at this point, $x_value should be at least as big as - if not bigger than - $y_value $carry = 0; for ($i = 0, $j = 1; $j < $y_size; $i+=2, $j+=2) { $sum = $x_value[$j] * 0x4000000 + $x_value[$i] - $y_value[$j] * 0x4000000 - $y_value[$i] - $carry; $carry = $sum < 0; // eg. floor($sum / 2**52); only possible values (in any base) are 0 and 1 $sum = $carry ? $sum + MATH_BIGINTEGER_MAX_DIGIT52 : $sum; $temp = (int) ($sum / 0x4000000); $x_value[$i] = (int) ($sum - 0x4000000 * $temp); $x_value[$j] = $temp; } if ($j == $y_size) { // ie. if $y_size is odd $sum = $x_value[$i] - $y_value[$i] - $carry; $carry = $sum < 0; $x_value[$i] = $carry ? $sum + 0x4000000 : $sum; ++$i; } if ($carry) { for (; !$x_value[$i]; ++$i) { $x_value[$i] = 0x3FFFFFF; } --$x_value[$i]; } return array( MATH_BIGINTEGER_VALUE => $this->_trim($x_value), MATH_BIGINTEGER_SIGN => $x_negative ); } /** * Multiplies two BigIntegers * * Here's an example: * <code> * <?php * include('Math/BigInteger.php'); * * $a = new Math_BigInteger('10'); * $b = new Math_BigInteger('20'); * * $c = $a->multiply($b); * * echo $c->toString(); // outputs 200 * ?> * </code> * * @param Math_BigInteger $x * @return Math_BigInteger * @access public */ function multiply($x) { switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: $temp = new Math_BigInteger(); $temp->value = gmp_mul($this->value, $x->value); return $this->_normalize($temp); case MATH_BIGINTEGER_MODE_BCMATH: $temp = new Math_BigInteger(); $temp->value = bcmul($this->value, $x->value, 0); return $this->_normalize($temp); } $temp = $this->_multiply($this->value, $this->is_negative, $x->value, $x->is_negative); $product = new Math_BigInteger(); $product->value = $temp[MATH_BIGINTEGER_VALUE]; $product->is_negative = $temp[MATH_BIGINTEGER_SIGN]; return $this->_normalize($product); } /** * Performs multiplication. * * @param Array $x_value * @param Boolean $x_negative * @param Array $y_value * @param Boolean $y_negative * @return Array * @access private */ function _multiply($x_value, $x_negative, $y_value, $y_negative) { //if ( $x_value == $y_value ) { // return array( // MATH_BIGINTEGER_VALUE => $this->_square($x_value), // MATH_BIGINTEGER_SIGN => $x_sign != $y_value // ); //} $x_length = count($x_value); $y_length = count($y_value); if ( !$x_length || !$y_length ) { // a 0 is being multiplied return array( MATH_BIGINTEGER_VALUE => array(), MATH_BIGINTEGER_SIGN => false ); } return array( MATH_BIGINTEGER_VALUE => min($x_length, $y_length) < 2 * MATH_BIGINTEGER_KARATSUBA_CUTOFF ? $this->_trim($this->_regularMultiply($x_value, $y_value)) : $this->_trim($this->_karatsuba($x_value, $y_value)), MATH_BIGINTEGER_SIGN => $x_negative != $y_negative ); } /** * Performs long multiplication on two BigIntegers * * Modeled after 'multiply' in MutableBigInteger.java. * * @param Array $x_value * @param Array $y_value * @return Array * @access private */ function _regularMultiply($x_value, $y_value) { $x_length = count($x_value); $y_length = count($y_value); if ( !$x_length || !$y_length ) { // a 0 is being multiplied return array(); } if ( $x_length < $y_length ) { $temp = $x_value; $x_value = $y_value; $y_value = $temp; $x_length = count($x_value); $y_length = count($y_value); } $product_value = $this->_array_repeat(0, $x_length + $y_length); // the following for loop could be removed if the for loop following it // (the one with nested for loops) initially set $i to 0, but // doing so would also make the result in one set of unnecessary adds, // since on the outermost loops first pass, $product->value[$k] is going // to always be 0 $carry = 0; for ($j = 0; $j < $x_length; ++$j) { // ie. $i = 0 $temp = (!empty($x_value[$j]) ? $x_value[$j] : 0) * (!empty($y_value[0]) ? $y_value[0] : 0) + $carry; // $product_value[$k] == 0 $carry = (int) ($temp / 0x4000000); $product_value[$j] = (int) ($temp - 0x4000000 * $carry); } $product_value[$j] = $carry; // the above for loop is what the previous comment was talking about. the // following for loop is the "one with nested for loops" for ($i = 1; $i < $y_length; ++$i) { $carry = 0; for ($j = 0, $k = $i; $j < $x_length; ++$j, ++$k) { $temp = $product_value[$k] + $x_value[$j] * $y_value[$i] + $carry; $carry = (int) ($temp / 0x4000000); $product_value[$k] = (int) ($temp - 0x4000000 * $carry); } $product_value[$k] = $carry; } return $product_value; } /** * Performs Karatsuba multiplication on two BigIntegers * * See {@link http://en.wikipedia.org/wiki/Karatsuba_algorithm Karatsuba algorithm} and * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=120 MPM 5.2.3}. * * @param Array $x_value * @param Array $y_value * @return Array * @access private */ function _karatsuba($x_value, $y_value) { $m = min(count($x_value) >> 1, count($y_value) >> 1); if ($m < MATH_BIGINTEGER_KARATSUBA_CUTOFF) { return $this->_regularMultiply($x_value, $y_value); } $x1 = array_slice($x_value, $m); $x0 = array_slice($x_value, 0, $m); $y1 = array_slice($y_value, $m); $y0 = array_slice($y_value, 0, $m); $z2 = $this->_karatsuba($x1, $y1); $z0 = $this->_karatsuba($x0, $y0); $z1 = $this->_add($x1, false, $x0, false); $temp = $this->_add($y1, false, $y0, false); $z1 = $this->_karatsuba($z1[MATH_BIGINTEGER_VALUE], $temp[MATH_BIGINTEGER_VALUE]); $temp = $this->_add($z2, false, $z0, false); $z1 = $this->_subtract($z1, false, $temp[MATH_BIGINTEGER_VALUE], false); $z2 = array_merge(array_fill(0, 2 * $m, 0), $z2); $z1[MATH_BIGINTEGER_VALUE] = array_merge(array_fill(0, $m, 0), $z1[MATH_BIGINTEGER_VALUE]); $xy = $this->_add($z2, false, $z1[MATH_BIGINTEGER_VALUE], $z1[MATH_BIGINTEGER_SIGN]); $xy = $this->_add($xy[MATH_BIGINTEGER_VALUE], $xy[MATH_BIGINTEGER_SIGN], $z0, false); return $xy[MATH_BIGINTEGER_VALUE]; } /** * Performs squaring * * @param Array $x * @return Array * @access private */ function _square($x = false) { return count($x) < 2 * MATH_BIGINTEGER_KARATSUBA_CUTOFF ? $this->_trim($this->_baseSquare($x)) : $this->_trim($this->_karatsubaSquare($x)); } /** * Performs traditional squaring on two BigIntegers * * Squaring can be done faster than multiplying a number by itself can be. See * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=7 HAC 14.2.4} / * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=141 MPM 5.3} for more information. * * @param Array $value * @return Array * @access private */ function _baseSquare($value) { if ( empty($value) ) { return array(); } $square_value = $this->_array_repeat(0, 2 * count($value)); for ($i = 0, $max_index = count($value) - 1; $i <= $max_index; ++$i) { $i2 = $i << 1; $temp = $square_value[$i2] + $value[$i] * $value[$i]; $carry = (int) ($temp / 0x4000000); $square_value[$i2] = (int) ($temp - 0x4000000 * $carry); // note how we start from $i+1 instead of 0 as we do in multiplication. for ($j = $i + 1, $k = $i2 + 1; $j <= $max_index; ++$j, ++$k) { $temp = $square_value[$k] + 2 * $value[$j] * $value[$i] + $carry; $carry = (int) ($temp / 0x4000000); $square_value[$k] = (int) ($temp - 0x4000000 * $carry); } // the following line can yield values larger 2**15. at this point, PHP should switch // over to floats. $square_value[$i + $max_index + 1] = $carry; } return $square_value; } /** * Performs Karatsuba "squaring" on two BigIntegers * * See {@link http://en.wikipedia.org/wiki/Karatsuba_algorithm Karatsuba algorithm} and * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=151 MPM 5.3.4}. * * @param Array $value * @return Array * @access private */ function _karatsubaSquare($value) { $m = count($value) >> 1; if ($m < MATH_BIGINTEGER_KARATSUBA_CUTOFF) { return $this->_baseSquare($value); } $x1 = array_slice($value, $m); $x0 = array_slice($value, 0, $m); $z2 = $this->_karatsubaSquare($x1); $z0 = $this->_karatsubaSquare($x0); $z1 = $this->_add($x1, false, $x0, false); $z1 = $this->_karatsubaSquare($z1[MATH_BIGINTEGER_VALUE]); $temp = $this->_add($z2, false, $z0, false); $z1 = $this->_subtract($z1, false, $temp[MATH_BIGINTEGER_VALUE], false); $z2 = array_merge(array_fill(0, 2 * $m, 0), $z2); $z1[MATH_BIGINTEGER_VALUE] = array_merge(array_fill(0, $m, 0), $z1[MATH_BIGINTEGER_VALUE]); $xx = $this->_add($z2, false, $z1[MATH_BIGINTEGER_VALUE], $z1[MATH_BIGINTEGER_SIGN]); $xx = $this->_add($xx[MATH_BIGINTEGER_VALUE], $xx[MATH_BIGINTEGER_SIGN], $z0, false); return $xx[MATH_BIGINTEGER_VALUE]; } /** * Divides two BigIntegers. * * Returns an array whose first element contains the quotient and whose second element contains the * "common residue". If the remainder would be positive, the "common residue" and the remainder are the * same. If the remainder would be negative, the "common residue" is equal to the sum of the remainder * and the divisor (basically, the "common residue" is the first positive modulo). * * Here's an example: * <code> * <?php * include('Math/BigInteger.php'); * * $a = new Math_BigInteger('10'); * $b = new Math_BigInteger('20'); * * list($quotient, $remainder) = $a->divide($b); * * echo $quotient->toString(); // outputs 0 * echo "\r\n"; * echo $remainder->toString(); // outputs 10 * ?> * </code> * * @param Math_BigInteger $y * @return Array * @access public * @internal This function is based off of {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=9 HAC 14.20}. */ function divide($y) { switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: $quotient = new Math_BigInteger(); $remainder = new Math_BigInteger(); list($quotient->value, $remainder->value) = gmp_div_qr($this->value, $y->value); if (gmp_sign($remainder->value) < 0) { $remainder->value = gmp_add($remainder->value, gmp_abs($y->value)); } return array($this->_normalize($quotient), $this->_normalize($remainder)); case MATH_BIGINTEGER_MODE_BCMATH: $quotient = new Math_BigInteger(); $remainder = new Math_BigInteger(); $quotient->value = bcdiv($this->value, $y->value, 0); $remainder->value = bcmod($this->value, $y->value); if ($remainder->value[0] == '-') { $remainder->value = bcadd($remainder->value, $y->value[0] == '-' ? substr($y->value, 1) : $y->value, 0); } return array($this->_normalize($quotient), $this->_normalize($remainder)); } if (count($y->value) == 1) { list($q, $r) = $this->_divide_digit($this->value, $y->value[0]); $quotient = new Math_BigInteger(); $remainder = new Math_BigInteger(); $quotient->value = $q; $remainder->value = array($r); $quotient->is_negative = $this->is_negative != $y->is_negative; return array($this->_normalize($quotient), $this->_normalize($remainder)); } static $zero; if ( !isset($zero) ) { $zero = new Math_BigInteger(); } $x = $this->copy(); $y = $y->copy(); $x_sign = $x->is_negative; $y_sign = $y->is_negative; $x->is_negative = $y->is_negative = false; $diff = $x->compare($y); if ( !$diff ) { $temp = new Math_BigInteger(); $temp->value = array(1); $temp->is_negative = $x_sign != $y_sign; return array($this->_normalize($temp), $this->_normalize(new Math_BigInteger())); } if ( $diff < 0 ) { // if $x is negative, "add" $y. if ( $x_sign ) { $x = $y->subtract($x); } return array($this->_normalize(new Math_BigInteger()), $this->_normalize($x)); } // normalize $x and $y as described in HAC 14.23 / 14.24 $msb = $y->value[count($y->value) - 1]; for ($shift = 0; !($msb & 0x2000000); ++$shift) { $msb <<= 1; } $x->_lshift($shift); $y->_lshift($shift); $y_value = &$y->value; $x_max = count($x->value) - 1; $y_max = count($y->value) - 1; $quotient = new Math_BigInteger(); $quotient_value = &$quotient->value; $quotient_value = $this->_array_repeat(0, $x_max - $y_max + 1); static $temp, $lhs, $rhs; if (!isset($temp)) { $temp = new Math_BigInteger(); $lhs = new Math_BigInteger(); $rhs = new Math_BigInteger(); } $temp_value = &$temp->value; $rhs_value = &$rhs->value; // $temp = $y << ($x_max - $y_max-1) in base 2**26 $temp_value = array_merge($this->_array_repeat(0, $x_max - $y_max), $y_value); while ( $x->compare($temp) >= 0 ) { // calculate the "common residue" ++$quotient_value[$x_max - $y_max]; $x = $x->subtract($temp); $x_max = count($x->value) - 1; } for ($i = $x_max; $i >= $y_max + 1; --$i) { $x_value = &$x->value; $x_window = array( isset($x_value[$i]) ? $x_value[$i] : 0, isset($x_value[$i - 1]) ? $x_value[$i - 1] : 0, isset($x_value[$i - 2]) ? $x_value[$i - 2] : 0 ); $y_window = array( $y_value[$y_max], ( $y_max > 0 ) ? $y_value[$y_max - 1] : 0 ); $q_index = $i - $y_max - 1; if ($x_window[0] == $y_window[0]) { $quotient_value[$q_index] = 0x3FFFFFF; } else { $quotient_value[$q_index] = (int) ( ($x_window[0] * 0x4000000 + $x_window[1]) / $y_window[0] ); } $temp_value = array($y_window[1], $y_window[0]); $lhs->value = array($quotient_value[$q_index]); $lhs = $lhs->multiply($temp); $rhs_value = array($x_window[2], $x_window[1], $x_window[0]); while ( $lhs->compare($rhs) > 0 ) { --$quotient_value[$q_index]; $lhs->value = array($quotient_value[$q_index]); $lhs = $lhs->multiply($temp); } $adjust = $this->_array_repeat(0, $q_index); $temp_value = array($quotient_value[$q_index]); $temp = $temp->multiply($y); $temp_value = &$temp->value; $temp_value = array_merge($adjust, $temp_value); $x = $x->subtract($temp); if ($x->compare($zero) < 0) { $temp_value = array_merge($adjust, $y_value); $x = $x->add($temp); --$quotient_value[$q_index]; } $x_max = count($x_value) - 1; } // unnormalize the remainder $x->_rshift($shift); $quotient->is_negative = $x_sign != $y_sign; // calculate the "common residue", if appropriate if ( $x_sign ) { $y->_rshift($shift); $x = $y->subtract($x); } return array($this->_normalize($quotient), $this->_normalize($x)); } /** * Divides a BigInteger by a regular integer * * abc / x = a00 / x + b0 / x + c / x * * @param Array $dividend * @param Array $divisor * @return Array * @access private */ function _divide_digit($dividend, $divisor) { $carry = 0; $result = array(); for ($i = count($dividend) - 1; $i >= 0; --$i) { $temp = 0x4000000 * $carry + (!empty($dividend[$i]) ? $dividend[$i] : 0); $result[$i] = (int) ($temp / $divisor); $carry = (int) ($temp - $divisor * $result[$i]); } return array($result, $carry); } /** * Performs modular exponentiation. * * Here's an example: * <code> * <?php * include('Math/BigInteger.php'); * * $a = new Math_BigInteger('10'); * $b = new Math_BigInteger('20'); * $c = new Math_BigInteger('30'); * * $c = $a->modPow($b, $c); * * echo $c->toString(); // outputs 10 * ?> * </code> * * @param Math_BigInteger $e * @param Math_BigInteger $n * @return Math_BigInteger * @access public * @internal The most naive approach to modular exponentiation has very unreasonable requirements, and * and although the approach involving repeated squaring does vastly better, it, too, is impractical * for our purposes. The reason being that division - by far the most complicated and time-consuming * of the basic operations (eg. +,-,*,/) - occurs multiple times within it. * * Modular reductions resolve this issue. Although an individual modular reduction takes more time * then an individual division, when performed in succession (with the same modulo), they're a lot faster. * * The two most commonly used modular reductions are Barrett and Montgomery reduction. Montgomery reduction, * although faster, only works when the gcd of the modulo and of the base being used is 1. In RSA, when the * base is a power of two, the modulo - a product of two primes - is always going to have a gcd of 1 (because * the product of two odd numbers is odd), but what about when RSA isn't used? * * In contrast, Barrett reduction has no such constraint. As such, some bigint implementations perform a * Barrett reduction after every operation in the modpow function. Others perform Barrett reductions when the * modulo is even and Montgomery reductions when the modulo is odd. BigInteger.java's modPow method, however, * uses a trick involving the Chinese Remainder Theorem to factor the even modulo into two numbers - one odd and * the other, a power of two - and recombine them, later. This is the method that this modPow function uses. * {@link http://islab.oregonstate.edu/papers/j34monex.pdf Montgomery Reduction with Even Modulus} elaborates. */ function modPow($e, $n) { $n = $this->bitmask !== false && $this->bitmask->compare($n) < 0 ? $this->bitmask : $n->abs(); if ($e->compare(new Math_BigInteger()) < 0) { $e = $e->abs(); $temp = $this->modInverse($n); if ($temp === false) { return false; } return $this->_normalize($temp->modPow($e, $n)); } switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: $temp = new Math_BigInteger(); $temp->value = gmp_powm($this->value, $e->value, $n->value); return $this->_normalize($temp); case MATH_BIGINTEGER_MODE_BCMATH: $temp = new Math_BigInteger(); $temp->value = bcpowmod($this->value, $e->value, $n->value, 0); return $this->_normalize($temp); } if ( empty($e->value) ) { $temp = new Math_BigInteger(); $temp->value = array(1); return $this->_normalize($temp); } if ( $e->value == array(1) ) { list(, $temp) = $this->divide($n); return $this->_normalize($temp); } if ( $e->value == array(2) ) { $temp = new Math_BigInteger(); $temp->value = $this->_square($this->value); list(, $temp) = $temp->divide($n); return $this->_normalize($temp); } return $this->_normalize($this->_slidingWindow($e, $n, MATH_BIGINTEGER_BARRETT)); // is the modulo odd? if ( $n->value[0] & 1 ) { return $this->_normalize($this->_slidingWindow($e, $n, MATH_BIGINTEGER_MONTGOMERY)); } // if it's not, it's even // find the lowest set bit (eg. the max pow of 2 that divides $n) for ($i = 0; $i < count($n->value); ++$i) { if ( $n->value[$i] ) { $temp = decbin($n->value[$i]); $j = strlen($temp) - strrpos($temp, '1') - 1; $j+= 26 * $i; break; } } // at this point, 2^$j * $n/(2^$j) == $n $mod1 = $n->copy(); $mod1->_rshift($j); $mod2 = new Math_BigInteger(); $mod2->value = array(1); $mod2->_lshift($j); $part1 = ( $mod1->value != array(1) ) ? $this->_slidingWindow($e, $mod1, MATH_BIGINTEGER_MONTGOMERY) : new Math_BigInteger(); $part2 = $this->_slidingWindow($e, $mod2, MATH_BIGINTEGER_POWEROF2); $y1 = $mod2->modInverse($mod1); $y2 = $mod1->modInverse($mod2); $result = $part1->multiply($mod2); $result = $result->multiply($y1); $temp = $part2->multiply($mod1); $temp = $temp->multiply($y2); $result = $result->add($temp); list(, $result) = $result->divide($n); return $this->_normalize($result); } /** * Performs modular exponentiation. * * Alias for Math_BigInteger::modPow() * * @param Math_BigInteger $e * @param Math_BigInteger $n * @return Math_BigInteger * @access public */ function powMod($e, $n) { return $this->modPow($e, $n); } /** * Sliding Window k-ary Modular Exponentiation * * Based on {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=27 HAC 14.85} / * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=210 MPM 7.7}. In a departure from those algorithims, * however, this function performs a modular reduction after every multiplication and squaring operation. * As such, this function has the same preconditions that the reductions being used do. * * @param Math_BigInteger $e * @param Math_BigInteger $n * @param Integer $mode * @return Math_BigInteger * @access private */ function _slidingWindow($e, $n, $mode) { static $window_ranges = array(7, 25, 81, 241, 673, 1793); // from BigInteger.java's oddModPow function //static $window_ranges = array(0, 7, 36, 140, 450, 1303, 3529); // from MPM 7.3.1 $e_value = $e->value; $e_length = count($e_value) - 1; $e_bits = decbin($e_value[$e_length]); for ($i = $e_length - 1; $i >= 0; --$i) { $e_bits.= str_pad(decbin($e_value[$i]), 26, '0', STR_PAD_LEFT); } $e_length = strlen($e_bits); // calculate the appropriate window size. // $window_size == 3 if $window_ranges is between 25 and 81, for example. for ($i = 0, $window_size = 1; $e_length > $window_ranges[$i] && $i < count($window_ranges); ++$window_size, ++$i); $n_value = $n->value; // precompute $this^0 through $this^$window_size $powers = array(); $powers[1] = $this->_prepareReduce($this->value, $n_value, $mode); $powers[2] = $this->_squareReduce($powers[1], $n_value, $mode); // we do every other number since substr($e_bits, $i, $j+1) (see below) is supposed to end // in a 1. ie. it's supposed to be odd. $temp = 1 << ($window_size - 1); for ($i = 1; $i < $temp; ++$i) { $i2 = $i << 1; $powers[$i2 + 1] = $this->_multiplyReduce($powers[$i2 - 1], $powers[2], $n_value, $mode); } $result = array(1); $result = $this->_prepareReduce($result, $n_value, $mode); for ($i = 0; $i < $e_length; ) { if ( !$e_bits[$i] ) { $result = $this->_squareReduce($result, $n_value, $mode); ++$i; } else { for ($j = $window_size - 1; $j > 0; --$j) { if ( !empty($e_bits[$i + $j]) ) { break; } } for ($k = 0; $k <= $j; ++$k) {// eg. the length of substr($e_bits, $i, $j+1) $result = $this->_squareReduce($result, $n_value, $mode); } $result = $this->_multiplyReduce($result, $powers[bindec(substr($e_bits, $i, $j + 1))], $n_value, $mode); $i+=$j + 1; } } $temp = new Math_BigInteger(); $temp->value = $this->_reduce($result, $n_value, $mode); return $temp; } /** * Modular reduction * * For most $modes this will return the remainder. * * @see _slidingWindow() * @access private * @param Array $x * @param Array $n * @param Integer $mode * @return Array */ function _reduce($x, $n, $mode) { switch ($mode) { case MATH_BIGINTEGER_MONTGOMERY: return $this->_montgomery($x, $n); case MATH_BIGINTEGER_BARRETT: return $this->_barrett($x, $n); case MATH_BIGINTEGER_POWEROF2: $lhs = new Math_BigInteger(); $lhs->value = $x; $rhs = new Math_BigInteger(); $rhs->value = $n; return $x->_mod2($n); case MATH_BIGINTEGER_CLASSIC: $lhs = new Math_BigInteger(); $lhs->value = $x; $rhs = new Math_BigInteger(); $rhs->value = $n; list(, $temp) = $lhs->divide($rhs); return $temp->value; case MATH_BIGINTEGER_NONE: return $x; default: // an invalid $mode was provided } } /** * Modular reduction preperation * * @see _slidingWindow() * @access private * @param Array $x * @param Array $n * @param Integer $mode * @return Array */ function _prepareReduce($x, $n, $mode) { if ($mode == MATH_BIGINTEGER_MONTGOMERY) { return $this->_prepMontgomery($x, $n); } return $this->_reduce($x, $n, $mode); } /** * Modular multiply * * @see _slidingWindow() * @access private * @param Array $x * @param Array $y * @param Array $n * @param Integer $mode * @return Array */ function _multiplyReduce($x, $y, $n, $mode) { if ($mode == MATH_BIGINTEGER_MONTGOMERY) { return $this->_montgomeryMultiply($x, $y, $n); } $temp = $this->_multiply($x, false, $y, false); return $this->_reduce($temp[MATH_BIGINTEGER_VALUE], $n, $mode); } /** * Modular square * * @see _slidingWindow() * @access private * @param Array $x * @param Array $n * @param Integer $mode * @return Array */ function _squareReduce($x, $n, $mode) { if ($mode == MATH_BIGINTEGER_MONTGOMERY) { return $this->_montgomeryMultiply($x, $x, $n); } return $this->_reduce($this->_square($x), $n, $mode); } /** * Modulos for Powers of Two * * Calculates $x%$n, where $n = 2**$e, for some $e. Since this is basically the same as doing $x & ($n-1), * we'll just use this function as a wrapper for doing that. * * @see _slidingWindow() * @access private * @param Math_BigInteger * @return Math_BigInteger */ function _mod2($n) { $temp = new Math_BigInteger(); $temp->value = array(1); return $this->bitwise_and($n->subtract($temp)); } /** * Barrett Modular Reduction * * See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=14 HAC 14.3.3} / * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=165 MPM 6.2.5} for more information. Modified slightly, * so as not to require negative numbers (initially, this script didn't support negative numbers). * * Employs "folding", as described at * {@link http://www.cosic.esat.kuleuven.be/publications/thesis-149.pdf#page=66 thesis-149.pdf#page=66}. To quote from * it, "the idea [behind folding] is to find a value x' such that x (mod m) = x' (mod m), with x' being smaller than x." * * Unfortunately, the "Barrett Reduction with Folding" algorithm described in thesis-149.pdf is not, as written, all that * usable on account of (1) its not using reasonable radix points as discussed in * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=162 MPM 6.2.2} and (2) the fact that, even with reasonable * radix points, it only works when there are an even number of digits in the denominator. The reason for (2) is that * (x >> 1) + (x >> 1) != x / 2 + x / 2. If x is even, they're the same, but if x is odd, they're not. See the in-line * comments for details. * * @see _slidingWindow() * @access private * @param Array $n * @param Array $m * @return Array */ function _barrett($n, $m) { static $cache = array( MATH_BIGINTEGER_VARIABLE => array(), MATH_BIGINTEGER_DATA => array() ); $m_length = count($m); // if ($this->_compare($n, $this->_square($m)) >= 0) { if (count($n) > 2 * $m_length) { $lhs = new Math_BigInteger(); $rhs = new Math_BigInteger(); $lhs->value = $n; $rhs->value = $m; list(, $temp) = $lhs->divide($rhs); return $temp->value; } // if (m.length >> 1) + 2 <= m.length then m is too small and n can't be reduced if ($m_length < 5) { return $this->_regularBarrett($n, $m); } // n = 2 * m.length if ( ($key = array_search($m, $cache[MATH_BIGINTEGER_VARIABLE])) === false ) { $key = count($cache[MATH_BIGINTEGER_VARIABLE]); $cache[MATH_BIGINTEGER_VARIABLE][] = $m; $lhs = new Math_BigInteger(); $lhs_value = &$lhs->value; $lhs_value = $this->_array_repeat(0, $m_length + ($m_length >> 1)); $lhs_value[] = 1; $rhs = new Math_BigInteger(); $rhs->value = $m; list($u, $m1) = $lhs->divide($rhs); $u = $u->value; $m1 = $m1->value; $cache[MATH_BIGINTEGER_DATA][] = array( 'u' => $u, // m.length >> 1 (technically (m.length >> 1) + 1) 'm1'=> $m1 // m.length ); } else { extract($cache[MATH_BIGINTEGER_DATA][$key]); } $cutoff = $m_length + ($m_length >> 1); $lsd = array_slice($n, 0, $cutoff); // m.length + (m.length >> 1) $msd = array_slice($n, $cutoff); // m.length >> 1 $lsd = $this->_trim($lsd); $temp = $this->_multiply($msd, false, $m1, false); $n = $this->_add($lsd, false, $temp[MATH_BIGINTEGER_VALUE], false); // m.length + (m.length >> 1) + 1 if ($m_length & 1) { return $this->_regularBarrett($n[MATH_BIGINTEGER_VALUE], $m); } // (m.length + (m.length >> 1) + 1) - (m.length - 1) == (m.length >> 1) + 2 $temp = array_slice($n[MATH_BIGINTEGER_VALUE], $m_length - 1); // if even: ((m.length >> 1) + 2) + (m.length >> 1) == m.length + 2 // if odd: ((m.length >> 1) + 2) + (m.length >> 1) == (m.length - 1) + 2 == m.length + 1 $temp = $this->_multiply($temp, false, $u, false); // if even: (m.length + 2) - ((m.length >> 1) + 1) = m.length - (m.length >> 1) + 1 // if odd: (m.length + 1) - ((m.length >> 1) + 1) = m.length - (m.length >> 1) $temp = array_slice($temp[MATH_BIGINTEGER_VALUE], ($m_length >> 1) + 1); // if even: (m.length - (m.length >> 1) + 1) + m.length = 2 * m.length - (m.length >> 1) + 1 // if odd: (m.length - (m.length >> 1)) + m.length = 2 * m.length - (m.length >> 1) $temp = $this->_multiply($temp, false, $m, false); // at this point, if m had an odd number of digits, we'd be subtracting a 2 * m.length - (m.length >> 1) digit // number from a m.length + (m.length >> 1) + 1 digit number. ie. there'd be an extra digit and the while loop // following this comment would loop a lot (hence our calling _regularBarrett() in that situation). $result = $this->_subtract($n[MATH_BIGINTEGER_VALUE], false, $temp[MATH_BIGINTEGER_VALUE], false); while ($this->_compare($result[MATH_BIGINTEGER_VALUE], $result[MATH_BIGINTEGER_SIGN], $m, false) >= 0) { $result = $this->_subtract($result[MATH_BIGINTEGER_VALUE], $result[MATH_BIGINTEGER_SIGN], $m, false); } return $result[MATH_BIGINTEGER_VALUE]; } /** * (Regular) Barrett Modular Reduction * * For numbers with more than four digits Math_BigInteger::_barrett() is faster. The difference between that and this * is that this function does not fold the denominator into a smaller form. * * @see _slidingWindow() * @access private * @param Array $x * @param Array $n * @return Array */ function _regularBarrett($x, $n) { static $cache = array( MATH_BIGINTEGER_VARIABLE => array(), MATH_BIGINTEGER_DATA => array() ); $n_length = count($n); if (count($x) > 2 * $n_length) { $lhs = new Math_BigInteger(); $rhs = new Math_BigInteger(); $lhs->value = $x; $rhs->value = $n; list(, $temp) = $lhs->divide($rhs); return $temp->value; } if ( ($key = array_search($n, $cache[MATH_BIGINTEGER_VARIABLE])) === false ) { $key = count($cache[MATH_BIGINTEGER_VARIABLE]); $cache[MATH_BIGINTEGER_VARIABLE][] = $n; $lhs = new Math_BigInteger(); $lhs_value = &$lhs->value; $lhs_value = $this->_array_repeat(0, 2 * $n_length); $lhs_value[] = 1; $rhs = new Math_BigInteger(); $rhs->value = $n; list($temp, ) = $lhs->divide($rhs); // m.length $cache[MATH_BIGINTEGER_DATA][] = $temp->value; } // 2 * m.length - (m.length - 1) = m.length + 1 $temp = array_slice($x, $n_length - 1); // (m.length + 1) + m.length = 2 * m.length + 1 $temp = $this->_multiply($temp, false, $cache[MATH_BIGINTEGER_DATA][$key], false); // (2 * m.length + 1) - (m.length - 1) = m.length + 2 $temp = array_slice($temp[MATH_BIGINTEGER_VALUE], $n_length + 1); // m.length + 1 $result = array_slice($x, 0, $n_length + 1); // m.length + 1 $temp = $this->_multiplyLower($temp, false, $n, false, $n_length + 1); // $temp == array_slice($temp->_multiply($temp, false, $n, false)->value, 0, $n_length + 1) if ($this->_compare($result, false, $temp[MATH_BIGINTEGER_VALUE], $temp[MATH_BIGINTEGER_SIGN]) < 0) { $corrector_value = $this->_array_repeat(0, $n_length + 1); $corrector_value[] = 1; $result = $this->_add($result, false, $corrector, false); $result = $result[MATH_BIGINTEGER_VALUE]; } // at this point, we're subtracting a number with m.length + 1 digits from another number with m.length + 1 digits $result = $this->_subtract($result, false, $temp[MATH_BIGINTEGER_VALUE], $temp[MATH_BIGINTEGER_SIGN]); while ($this->_compare($result[MATH_BIGINTEGER_VALUE], $result[MATH_BIGINTEGER_SIGN], $n, false) > 0) { $result = $this->_subtract($result[MATH_BIGINTEGER_VALUE], $result[MATH_BIGINTEGER_SIGN], $n, false); } return $result[MATH_BIGINTEGER_VALUE]; } /** * Performs long multiplication up to $stop digits * * If you're going to be doing array_slice($product->value, 0, $stop), some cycles can be saved. * * @see _regularBarrett() * @param Array $x_value * @param Boolean $x_negative * @param Array $y_value * @param Boolean $y_negative * @return Array * @access private */ function _multiplyLower($x_value, $x_negative, $y_value, $y_negative, $stop) { $x_length = count($x_value); $y_length = count($y_value); if ( !$x_length || !$y_length ) { // a 0 is being multiplied return array( MATH_BIGINTEGER_VALUE => array(), MATH_BIGINTEGER_SIGN => false ); } if ( $x_length < $y_length ) { $temp = $x_value; $x_value = $y_value; $y_value = $temp; $x_length = count($x_value); $y_length = count($y_value); } $product_value = $this->_array_repeat(0, $x_length + $y_length); // the following for loop could be removed if the for loop following it // (the one with nested for loops) initially set $i to 0, but // doing so would also make the result in one set of unnecessary adds, // since on the outermost loops first pass, $product->value[$k] is going // to always be 0 $carry = 0; for ($j = 0; $j < $x_length; ++$j) { // ie. $i = 0, $k = $i $temp = $x_value[$j] * $y_value[0] + $carry; // $product_value[$k] == 0 $carry = (int) ($temp / 0x4000000); $product_value[$j] = (int) ($temp - 0x4000000 * $carry); } if ($j < $stop) { $product_value[$j] = $carry; } // the above for loop is what the previous comment was talking about. the // following for loop is the "one with nested for loops" for ($i = 1; $i < $y_length; ++$i) { $carry = 0; for ($j = 0, $k = $i; $j < $x_length && $k < $stop; ++$j, ++$k) { $temp = $product_value[$k] + $x_value[$j] * $y_value[$i] + $carry; $carry = (int) ($temp / 0x4000000); $product_value[$k] = (int) ($temp - 0x4000000 * $carry); } if ($k < $stop) { $product_value[$k] = $carry; } } return array( MATH_BIGINTEGER_VALUE => $this->_trim($product_value), MATH_BIGINTEGER_SIGN => $x_negative != $y_negative ); } /** * Montgomery Modular Reduction * * ($x->_prepMontgomery($n))->_montgomery($n) yields $x % $n. * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=170 MPM 6.3} provides insights on how this can be * improved upon (basically, by using the comba method). gcd($n, 2) must be equal to one for this function * to work correctly. * * @see _prepMontgomery() * @see _slidingWindow() * @access private * @param Array $x * @param Array $n * @return Array */ function _montgomery($x, $n) { static $cache = array( MATH_BIGINTEGER_VARIABLE => array(), MATH_BIGINTEGER_DATA => array() ); if ( ($key = array_search($n, $cache[MATH_BIGINTEGER_VARIABLE])) === false ) { $key = count($cache[MATH_BIGINTEGER_VARIABLE]); $cache[MATH_BIGINTEGER_VARIABLE][] = $x; $cache[MATH_BIGINTEGER_DATA][] = $this->_modInverse67108864($n); } $k = count($n); $result = array(MATH_BIGINTEGER_VALUE => $x); for ($i = 0; $i < $k; ++$i) { $temp = $result[MATH_BIGINTEGER_VALUE][$i] * $cache[MATH_BIGINTEGER_DATA][$key]; $temp = (int) ($temp - 0x4000000 * ((int) ($temp / 0x4000000))); $temp = $this->_regularMultiply(array($temp), $n); $temp = array_merge($this->_array_repeat(0, $i), $temp); $result = $this->_add($result[MATH_BIGINTEGER_VALUE], false, $temp, false); } $result[MATH_BIGINTEGER_VALUE] = array_slice($result[MATH_BIGINTEGER_VALUE], $k); if ($this->_compare($result, false, $n, false) >= 0) { $result = $this->_subtract($result[MATH_BIGINTEGER_VALUE], false, $n, false); } return $result[MATH_BIGINTEGER_VALUE]; } /** * Montgomery Multiply * * Interleaves the montgomery reduction and long multiplication algorithms together as described in * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=13 HAC 14.36} * * @see _prepMontgomery() * @see _montgomery() * @access private * @param Array $x * @param Array $y * @param Array $m * @return Array */ function _montgomeryMultiply($x, $y, $m) { $temp = $this->_multiply($x, false, $y, false); return $this->_montgomery($temp[MATH_BIGINTEGER_VALUE], $m); static $cache = array( MATH_BIGINTEGER_VARIABLE => array(), MATH_BIGINTEGER_DATA => array() ); if ( ($key = array_search($m, $cache[MATH_BIGINTEGER_VARIABLE])) === false ) { $key = count($cache[MATH_BIGINTEGER_VARIABLE]); $cache[MATH_BIGINTEGER_VARIABLE][] = $m; $cache[MATH_BIGINTEGER_DATA][] = $this->_modInverse67108864($m); } $n = max(count($x), count($y), count($m)); $x = array_pad($x, $n, 0); $y = array_pad($y, $n, 0); $m = array_pad($m, $n, 0); $a = array(MATH_BIGINTEGER_VALUE => $this->_array_repeat(0, $n + 1)); for ($i = 0; $i < $n; ++$i) { $temp = $a[MATH_BIGINTEGER_VALUE][0] + $x[$i] * $y[0]; $temp = (int) ($temp - 0x4000000 * ((int) ($temp / 0x4000000))); $temp = $temp * $cache[MATH_BIGINTEGER_DATA][$key]; $temp = (int) ($temp - 0x4000000 * ((int) ($temp / 0x4000000))); $temp = $this->_add($this->_regularMultiply(array($x[$i]), $y), false, $this->_regularMultiply(array($temp), $m), false); $a = $this->_add($a[MATH_BIGINTEGER_VALUE], false, $temp[MATH_BIGINTEGER_VALUE], false); $a[MATH_BIGINTEGER_VALUE] = array_slice($a[MATH_BIGINTEGER_VALUE], 1); } if ($this->_compare($a[MATH_BIGINTEGER_VALUE], false, $m, false) >= 0) { $a = $this->_subtract($a[MATH_BIGINTEGER_VALUE], false, $m, false); } return $a[MATH_BIGINTEGER_VALUE]; } /** * Prepare a number for use in Montgomery Modular Reductions * * @see _montgomery() * @see _slidingWindow() * @access private * @param Array $x * @param Array $n * @return Array */ function _prepMontgomery($x, $n) { $lhs = new Math_BigInteger(); $lhs->value = array_merge($this->_array_repeat(0, count($n)), $x); $rhs = new Math_BigInteger(); $rhs->value = $n; list(, $temp) = $lhs->divide($rhs); return $temp->value; } /** * Modular Inverse of a number mod 2**26 (eg. 67108864) * * Based off of the bnpInvDigit function implemented and justified in the following URL: * * {@link http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn.js} * * The following URL provides more info: * * {@link http://groups.google.com/group/sci.crypt/msg/7a137205c1be7d85} * * As for why we do all the bitmasking... strange things can happen when converting from floats to ints. For * instance, on some computers, var_dump((int) -4294967297) yields int(-1) and on others, it yields * int(-2147483648). To avoid problems stemming from this, we use bitmasks to guarantee that ints aren't * auto-converted to floats. The outermost bitmask is present because without it, there's no guarantee that * the "residue" returned would be the so-called "common residue". We use fmod, in the last step, because the * maximum possible $x is 26 bits and the maximum $result is 16 bits. Thus, we have to be able to handle up to * 40 bits, which only 64-bit floating points will support. * * Thanks to Pedro Gimeno Fortea for input! * * @see _montgomery() * @access private * @param Array $x * @return Integer */ function _modInverse67108864($x) // 2**26 == 67108864 { $x = -$x[0]; $result = $x & 0x3; // x**-1 mod 2**2 $result = ($result * (2 - $x * $result)) & 0xF; // x**-1 mod 2**4 $result = ($result * (2 - ($x & 0xFF) * $result)) & 0xFF; // x**-1 mod 2**8 $result = ($result * ((2 - ($x & 0xFFFF) * $result) & 0xFFFF)) & 0xFFFF; // x**-1 mod 2**16 $result = fmod($result * (2 - fmod($x * $result, 0x4000000)), 0x4000000); // x**-1 mod 2**26 return $result & 0x3FFFFFF; } /** * Calculates modular inverses. * * Say you have (30 mod 17 * x mod 17) mod 17 == 1. x can be found using modular inverses. * * Here's an example: * <code> * <?php * include('Math/BigInteger.php'); * * $a = new Math_BigInteger(30); * $b = new Math_BigInteger(17); * * $c = $a->modInverse($b); * echo $c->toString(); // outputs 4 * * echo "\r\n"; * * $d = $a->multiply($c); * list(, $d) = $d->divide($b); * echo $d; // outputs 1 (as per the definition of modular inverse) * ?> * </code> * * @param Math_BigInteger $n * @return mixed false, if no modular inverse exists, Math_BigInteger, otherwise. * @access public * @internal See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=21 HAC 14.64} for more information. */ function modInverse($n) { switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: $temp = new Math_BigInteger(); $temp->value = gmp_invert($this->value, $n->value); return ( $temp->value === false ) ? false : $this->_normalize($temp); } static $zero, $one; if (!isset($zero)) { $zero = new Math_BigInteger(); $one = new Math_BigInteger(1); } // $x mod $n == $x mod -$n. $n = $n->abs(); if ($this->compare($zero) < 0) { $temp = $this->abs(); $temp = $temp->modInverse($n); return $negated === false ? false : $this->_normalize($n->subtract($temp)); } extract($this->extendedGCD($n)); if (!$gcd->equals($one)) { return false; } $x = $x->compare($zero) < 0 ? $x->add($n) : $x; return $this->compare($zero) < 0 ? $this->_normalize($n->subtract($x)) : $this->_normalize($x); } /** * Calculates the greatest common divisor and B�zout's identity. * * Say you have 693 and 609. The GCD is 21. B�zout's identity states that there exist integers x and y such that * 693*x + 609*y == 21. In point of fact, there are actually an infinite number of x and y combinations and which * combination is returned is dependant upon which mode is in use. See * {@link http://en.wikipedia.org/wiki/B%C3%A9zout%27s_identity B�zout's identity - Wikipedia} for more information. * * Here's an example: * <code> * <?php * include('Math/BigInteger.php'); * * $a = new Math_BigInteger(693); * $b = new Math_BigInteger(609); * * extract($a->extendedGCD($b)); * * echo $gcd->toString() . "\r\n"; // outputs 21 * echo $a->toString() * $x->toString() + $b->toString() * $y->toString(); // outputs 21 * ?> * </code> * * @param Math_BigInteger $n * @return Math_BigInteger * @access public * @internal Calculates the GCD using the binary xGCD algorithim described in * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=19 HAC 14.61}. As the text above 14.61 notes, * the more traditional algorithim requires "relatively costly multiple-precision divisions". */ function extendedGCD($n) { switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: extract(gmp_gcdext($this->value, $n->value)); return array( 'gcd' => $this->_normalize(new Math_BigInteger($g)), 'x' => $this->_normalize(new Math_BigInteger($s)), 'y' => $this->_normalize(new Math_BigInteger($t)) ); case MATH_BIGINTEGER_MODE_BCMATH: // it might be faster to use the binary xGCD algorithim here, as well, but (1) that algorithim works // best when the base is a power of 2 and (2) i don't think it'd make much difference, anyway. as is, // the basic extended euclidean algorithim is what we're using. $u = $this->value; $v = $n->value; $a = '1'; $b = '0'; $c = '0'; $d = '1'; while (bccomp($v, '0', 0) != 0) { $q = bcdiv($u, $v, 0); $temp = $u; $u = $v; $v = bcsub($temp, bcmul($v, $q, 0), 0); $temp = $a; $a = $c; $c = bcsub($temp, bcmul($a, $q, 0), 0); $temp = $b; $b = $d; $d = bcsub($temp, bcmul($b, $q, 0), 0); } return array( 'gcd' => $this->_normalize(new Math_BigInteger($u)), 'x' => $this->_normalize(new Math_BigInteger($a)), 'y' => $this->_normalize(new Math_BigInteger($b)) ); } $y = $n->copy(); $x = $this->copy(); $g = new Math_BigInteger(); $g->value = array(1); while ( !(($x->value[0] & 1)|| ($y->value[0] & 1)) ) { $x->_rshift(1); $y->_rshift(1); $g->_lshift(1); } $u = $x->copy(); $v = $y->copy(); $a = new Math_BigInteger(); $b = new Math_BigInteger(); $c = new Math_BigInteger(); $d = new Math_BigInteger(); $a->value = $d->value = $g->value = array(1); $b->value = $c->value = array(); while ( !empty($u->value) ) { while ( !($u->value[0] & 1) ) { $u->_rshift(1); if ( (!empty($a->value) && ($a->value[0] & 1)) || (!empty($b->value) && ($b->value[0] & 1)) ) { $a = $a->add($y); $b = $b->subtract($x); } $a->_rshift(1); $b->_rshift(1); } while ( !($v->value[0] & 1) ) { $v->_rshift(1); if ( (!empty($d->value) && ($d->value[0] & 1)) || (!empty($c->value) && ($c->value[0] & 1)) ) { $c = $c->add($y); $d = $d->subtract($x); } $c->_rshift(1); $d->_rshift(1); } if ($u->compare($v) >= 0) { $u = $u->subtract($v); $a = $a->subtract($c); $b = $b->subtract($d); } else { $v = $v->subtract($u); $c = $c->subtract($a); $d = $d->subtract($b); } } return array( 'gcd' => $this->_normalize($g->multiply($v)), 'x' => $this->_normalize($c), 'y' => $this->_normalize($d) ); } /** * Calculates the greatest common divisor * * Say you have 693 and 609. The GCD is 21. * * Here's an example: * <code> * <?php * include('Math/BigInteger.php'); * * $a = new Math_BigInteger(693); * $b = new Math_BigInteger(609); * * $gcd = a->extendedGCD($b); * * echo $gcd->toString() . "\r\n"; // outputs 21 * ?> * </code> * * @param Math_BigInteger $n * @return Math_BigInteger * @access public */ function gcd($n) { extract($this->extendedGCD($n)); return $gcd; } /** * Absolute value. * * @return Math_BigInteger * @access public */ function abs() { $temp = new Math_BigInteger(); switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: $temp->value = gmp_abs($this->value); break; case MATH_BIGINTEGER_MODE_BCMATH: $temp->value = (bccomp($this->value, '0', 0) < 0) ? substr($this->value, 1) : $this->value; break; default: $temp->value = $this->value; } return $temp; } /** * Compares two numbers. * * Although one might think !$x->compare($y) means $x != $y, it, in fact, means the opposite. The reason for this is * demonstrated thusly: * * $x > $y: $x->compare($y) > 0 * $x < $y: $x->compare($y) < 0 * $x == $y: $x->compare($y) == 0 * * Note how the same comparison operator is used. If you want to test for equality, use $x->equals($y). * * @param Math_BigInteger $x * @return Integer < 0 if $this is less than $x; > 0 if $this is greater than $x, and 0 if they are equal. * @access public * @see equals() * @internal Could return $this->subtract($x), but that's not as fast as what we do do. */ function compare($y) { switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: return gmp_cmp($this->value, $y->value); case MATH_BIGINTEGER_MODE_BCMATH: return bccomp($this->value, $y->value, 0); } return $this->_compare($this->value, $this->is_negative, $y->value, $y->is_negative); } /** * Compares two numbers. * * @param Array $x_value * @param Boolean $x_negative * @param Array $y_value * @param Boolean $y_negative * @return Integer * @see compare() * @access private */ function _compare($x_value, $x_negative, $y_value, $y_negative) { if ( $x_negative != $y_negative ) { return ( !$x_negative && $y_negative ) ? 1 : -1; } $result = $x_negative ? -1 : 1; if ( count($x_value) != count($y_value) ) { return ( count($x_value) > count($y_value) ) ? $result : -$result; } $size = max(count($x_value), count($y_value)); $x_value = array_pad($x_value, $size, 0); $y_value = array_pad($y_value, $size, 0); for ($i = count($x_value) - 1; $i >= 0; --$i) { if ($x_value[$i] != $y_value[$i]) { return ( $x_value[$i] > $y_value[$i] ) ? $result : -$result; } } return 0; } /** * Tests the equality of two numbers. * * If you need to see if one number is greater than or less than another number, use Math_BigInteger::compare() * * @param Math_BigInteger $x * @return Boolean * @access public * @see compare() */ function equals($x) { switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: return gmp_cmp($this->value, $x->value) == 0; default: return $this->value === $x->value && $this->is_negative == $x->is_negative; } } /** * Set Precision * * Some bitwise operations give different results depending on the precision being used. Examples include left * shift, not, and rotates. * * @param Math_BigInteger $x * @access public * @return Math_BigInteger */ function setPrecision($bits) { $this->precision = $bits; if ( MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_BCMATH ) { $this->bitmask = new Math_BigInteger(chr((1 << ($bits & 0x7)) - 1) . str_repeat(chr(0xFF), $bits >> 3), 256); } else { $this->bitmask = new Math_BigInteger(bcpow('2', $bits, 0)); } $temp = $this->_normalize($this); $this->value = $temp->value; } /** * Logical And * * @param Math_BigInteger $x * @access public * @internal Implemented per a request by Lluis Pamies i Juarez <lluis _a_ pamies.cat> * @return Math_BigInteger */ function bitwise_and($x) { switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: $temp = new Math_BigInteger(); $temp->value = gmp_and($this->value, $x->value); return $this->_normalize($temp); case MATH_BIGINTEGER_MODE_BCMATH: $left = $this->toBytes(); $right = $x->toBytes(); $length = max(strlen($left), strlen($right)); $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); return $this->_normalize(new Math_BigInteger($left & $right, 256)); } $result = $this->copy(); $length = min(count($x->value), count($this->value)); $result->value = array_slice($result->value, 0, $length); for ($i = 0; $i < $length; ++$i) { $result->value[$i] = $result->value[$i] & $x->value[$i]; } return $this->_normalize($result); } /** * Logical Or * * @param Math_BigInteger $x * @access public * @internal Implemented per a request by Lluis Pamies i Juarez <lluis _a_ pamies.cat> * @return Math_BigInteger */ function bitwise_or($x) { switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: $temp = new Math_BigInteger(); $temp->value = gmp_or($this->value, $x->value); return $this->_normalize($temp); case MATH_BIGINTEGER_MODE_BCMATH: $left = $this->toBytes(); $right = $x->toBytes(); $length = max(strlen($left), strlen($right)); $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); return $this->_normalize(new Math_BigInteger($left | $right, 256)); } $length = max(count($this->value), count($x->value)); $result = $this->copy(); $result->value = array_pad($result->value, 0, $length); $x->value = array_pad($x->value, 0, $length); for ($i = 0; $i < $length; ++$i) { $result->value[$i] = $this->value[$i] | $x->value[$i]; } return $this->_normalize($result); } /** * Logical Exclusive-Or * * @param Math_BigInteger $x * @access public * @internal Implemented per a request by Lluis Pamies i Juarez <lluis _a_ pamies.cat> * @return Math_BigInteger */ function bitwise_xor($x) { switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: $temp = new Math_BigInteger(); $temp->value = gmp_xor($this->value, $x->value); return $this->_normalize($temp); case MATH_BIGINTEGER_MODE_BCMATH: $left = $this->toBytes(); $right = $x->toBytes(); $length = max(strlen($left), strlen($right)); $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); return $this->_normalize(new Math_BigInteger($left ^ $right, 256)); } $length = max(count($this->value), count($x->value)); $result = $this->copy(); $result->value = array_pad($result->value, 0, $length); $x->value = array_pad($x->value, 0, $length); for ($i = 0; $i < $length; ++$i) { $result->value[$i] = $this->value[$i] ^ $x->value[$i]; } return $this->_normalize($result); } /** * Logical Not * * @access public * @internal Implemented per a request by Lluis Pamies i Juarez <lluis _a_ pamies.cat> * @return Math_BigInteger */ function bitwise_not() { // calculuate "not" without regard to $this->precision // (will always result in a smaller number. ie. ~1 isn't 1111 1110 - it's 0) $temp = $this->toBytes(); $pre_msb = decbin(ord($temp[0])); $temp = ~$temp; $msb = decbin(ord($temp[0])); if (strlen($msb) == 8) { $msb = substr($msb, strpos($msb, '0')); } $temp[0] = chr(bindec($msb)); // see if we need to add extra leading 1's $current_bits = strlen($pre_msb) + 8 * strlen($temp) - 8; $new_bits = $this->precision - $current_bits; if ($new_bits <= 0) { return $this->_normalize(new Math_BigInteger($temp, 256)); } // generate as many leading 1's as we need to. $leading_ones = chr((1 << ($new_bits & 0x7)) - 1) . str_repeat(chr(0xFF), $new_bits >> 3); $this->_base256_lshift($leading_ones, $current_bits); $temp = str_pad($temp, ceil($this->bits / 8), chr(0), STR_PAD_LEFT); return $this->_normalize(new Math_BigInteger($leading_ones | $temp, 256)); } /** * Logical Right Shift * * Shifts BigInteger's by $shift bits, effectively dividing by 2**$shift. * * @param Integer $shift * @return Math_BigInteger * @access public * @internal The only version that yields any speed increases is the internal version. */ function bitwise_rightShift($shift) { $temp = new Math_BigInteger(); switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: static $two; if (!isset($two)) { $two = gmp_init('2'); } $temp->value = gmp_div_q($this->value, gmp_pow($two, $shift)); break; case MATH_BIGINTEGER_MODE_BCMATH: $temp->value = bcdiv($this->value, bcpow('2', $shift, 0), 0); break; default: // could just replace _lshift with this, but then all _lshift() calls would need to be rewritten // and I don't want to do that... $temp->value = $this->value; $temp->_rshift($shift); } return $this->_normalize($temp); } /** * Logical Left Shift * * Shifts BigInteger's by $shift bits, effectively multiplying by 2**$shift. * * @param Integer $shift * @return Math_BigInteger * @access public * @internal The only version that yields any speed increases is the internal version. */ function bitwise_leftShift($shift) { $temp = new Math_BigInteger(); switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: static $two; if (!isset($two)) { $two = gmp_init('2'); } $temp->value = gmp_mul($this->value, gmp_pow($two, $shift)); break; case MATH_BIGINTEGER_MODE_BCMATH: $temp->value = bcmul($this->value, bcpow('2', $shift, 0), 0); break; default: // could just replace _rshift with this, but then all _lshift() calls would need to be rewritten // and I don't want to do that... $temp->value = $this->value; $temp->_lshift($shift); } return $this->_normalize($temp); } /** * Logical Left Rotate * * Instead of the top x bits being dropped they're appended to the shifted bit string. * * @param Integer $shift * @return Math_BigInteger * @access public */ function bitwise_leftRotate($shift) { $bits = $this->toBytes(); if ($this->precision > 0) { $precision = $this->precision; if ( MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_BCMATH ) { $mask = $this->bitmask->subtract(new Math_BigInteger(1)); $mask = $mask->toBytes(); } else { $mask = $this->bitmask->toBytes(); } } else { $temp = ord($bits[0]); for ($i = 0; $temp >> $i; ++$i); $precision = 8 * strlen($bits) - 8 + $i; $mask = chr((1 << ($precision & 0x7)) - 1) . str_repeat(chr(0xFF), $precision >> 3); } if ($shift < 0) { $shift+= $precision; } $shift%= $precision; if (!$shift) { return $this->copy(); } $left = $this->bitwise_leftShift($shift); $left = $left->bitwise_and(new Math_BigInteger($mask, 256)); $right = $this->bitwise_rightShift($precision - $shift); $result = MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_BCMATH ? $left->bitwise_or($right) : $left->add($right); return $this->_normalize($result); } /** * Logical Right Rotate * * Instead of the bottom x bits being dropped they're prepended to the shifted bit string. * * @param Integer $shift * @return Math_BigInteger * @access public */ function bitwise_rightRotate($shift) { return $this->bitwise_leftRotate(-$shift); } /** * Set random number generator function * * $generator should be the name of a random generating function whose first parameter is the minimum * value and whose second parameter is the maximum value. If this function needs to be seeded, it should * be seeded prior to calling Math_BigInteger::random() or Math_BigInteger::randomPrime() * * If the random generating function is not explicitly set, it'll be assumed to be mt_rand(). * * @see random() * @see randomPrime() * @param optional String $generator * @access public */ function setRandomGenerator($generator) { $this->generator = $generator; } /** * Generate a random number * * @param optional Integer $min * @param optional Integer $max * @return Math_BigInteger * @access public */ function random($min = false, $max = false) { if ($min === false) { $min = new Math_BigInteger(0); } if ($max === false) { $max = new Math_BigInteger(0x7FFFFFFF); } $compare = $max->compare($min); if (!$compare) { return $this->_normalize($min); } else if ($compare < 0) { // if $min is bigger then $max, swap $min and $max $temp = $max; $max = $min; $min = $temp; } $generator = $this->generator; $max = $max->subtract($min); $max = ltrim($max->toBytes(), chr(0)); $size = strlen($max) - 1; $random = ''; $bytes = $size & 1; for ($i = 0; $i < $bytes; ++$i) { $random.= chr($generator(0, 255)); } $blocks = $size >> 1; for ($i = 0; $i < $blocks; ++$i) { // mt_rand(-2147483648, 0x7FFFFFFF) always produces -2147483648 on some systems $random.= pack('n', $generator(0, 0xFFFF)); } $temp = new Math_BigInteger($random, 256); if ($temp->compare(new Math_BigInteger(substr($max, 1), 256)) > 0) { $random = chr($generator(0, ord($max[0]) - 1)) . $random; } else { $random = chr($generator(0, ord($max[0]) )) . $random; } $random = new Math_BigInteger($random, 256); return $this->_normalize($random->add($min)); } /** * Generate a random prime number. * * If there's not a prime within the given range, false will be returned. If more than $timeout seconds have elapsed, * give up and return false. * * @param optional Integer $min * @param optional Integer $max * @param optional Integer $timeout * @return Math_BigInteger * @access public * @internal See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap4.pdf#page=15 HAC 4.44}. */ function randomPrime($min = false, $max = false, $timeout = false) { $compare = $max->compare($min); if (!$compare) { return $min; } else if ($compare < 0) { // if $min is bigger then $max, swap $min and $max $temp = $max; $max = $min; $min = $temp; } // gmp_nextprime() requires PHP 5 >= 5.2.0 per <http://php.net/gmp-nextprime>. if ( MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_GMP && function_exists('gmp_nextprime') ) { // we don't rely on Math_BigInteger::random()'s min / max when gmp_nextprime() is being used since this function // does its own checks on $max / $min when gmp_nextprime() is used. When gmp_nextprime() is not used, however, // the same $max / $min checks are not performed. if ($min === false) { $min = new Math_BigInteger(0); } if ($max === false) { $max = new Math_BigInteger(0x7FFFFFFF); } $x = $this->random($min, $max); $x->value = gmp_nextprime($x->value); if ($x->compare($max) <= 0) { return $x; } $x->value = gmp_nextprime($min->value); if ($x->compare($max) <= 0) { return $x; } return false; } static $one, $two; if (!isset($one)) { $one = new Math_BigInteger(1); $two = new Math_BigInteger(2); } $start = time(); $x = $this->random($min, $max); if ($x->equals($two)) { return $x; } $x->_make_odd(); if ($x->compare($max) > 0) { // if $x > $max then $max is even and if $min == $max then no prime number exists between the specified range if ($min->equals($max)) { return false; } $x = $min->copy(); $x->_make_odd(); } $initial_x = $x->copy(); while (true) { if ($timeout !== false && time() - $start > $timeout) { return false; } if ($x->isPrime()) { return $x; } $x = $x->add($two); if ($x->compare($max) > 0) { $x = $min->copy(); if ($x->equals($two)) { return $x; } $x->_make_odd(); } if ($x->equals($initial_x)) { return false; } } } /** * Make the current number odd * * If the current number is odd it'll be unchanged. If it's even, one will be added to it. * * @see randomPrime() * @access private */ function _make_odd() { switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: gmp_setbit($this->value, 0); break; case MATH_BIGINTEGER_MODE_BCMATH: if ($this->value[strlen($this->value) - 1] % 2 == 0) { $this->value = bcadd($this->value, '1'); } break; default: $this->value[0] |= 1; } } /** * Checks a numer to see if it's prime * * Assuming the $t parameter is not set, this function has an error rate of 2**-80. The main motivation for the * $t parameter is distributability. Math_BigInteger::randomPrime() can be distributed accross multiple pageloads * on a website instead of just one. * * @param optional Integer $t * @return Boolean * @access public * @internal Uses the * {@link http://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test Miller-Rabin primality test}. See * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap4.pdf#page=8 HAC 4.24}. */ function isPrime($t = false) { $length = strlen($this->toBytes()); if (!$t) { // see HAC 4.49 "Note (controlling the error probability)" if ($length >= 163) { $t = 2; } // floor(1300 / 8) else if ($length >= 106) { $t = 3; } // floor( 850 / 8) else if ($length >= 81 ) { $t = 4; } // floor( 650 / 8) else if ($length >= 68 ) { $t = 5; } // floor( 550 / 8) else if ($length >= 56 ) { $t = 6; } // floor( 450 / 8) else if ($length >= 50 ) { $t = 7; } // floor( 400 / 8) else if ($length >= 43 ) { $t = 8; } // floor( 350 / 8) else if ($length >= 37 ) { $t = 9; } // floor( 300 / 8) else if ($length >= 31 ) { $t = 12; } // floor( 250 / 8) else if ($length >= 25 ) { $t = 15; } // floor( 200 / 8) else if ($length >= 18 ) { $t = 18; } // floor( 150 / 8) else { $t = 27; } } // ie. gmp_testbit($this, 0) // ie. isEven() or !isOdd() switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: return gmp_prob_prime($this->value, $t) != 0; case MATH_BIGINTEGER_MODE_BCMATH: if ($this->value === '2') { return true; } if ($this->value[strlen($this->value) - 1] % 2 == 0) { return false; } break; default: if ($this->value == array(2)) { return true; } if (~$this->value[0] & 1) { return false; } } static $primes, $zero, $one, $two; if (!isset($primes)) { $primes = array( 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997 ); if ( MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_INTERNAL ) { for ($i = 0; $i < count($primes); ++$i) { $primes[$i] = new Math_BigInteger($primes[$i]); } } $zero = new Math_BigInteger(); $one = new Math_BigInteger(1); $two = new Math_BigInteger(2); } if ($this->equals($one)) { return false; } // see HAC 4.4.1 "Random search for probable primes" if ( MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_INTERNAL ) { foreach ($primes as $prime) { list(, $r) = $this->divide($prime); if ($r->equals($zero)) { return $this->equals($prime); } } } else { $value = $this->value; foreach ($primes as $prime) { list(, $r) = $this->_divide_digit($value, $prime); if (!$r) { return count($value) == 1 && $value[0] == $prime; } } } $n = $this->copy(); $n_1 = $n->subtract($one); $n_2 = $n->subtract($two); $r = $n_1->copy(); $r_value = $r->value; // ie. $s = gmp_scan1($n, 0) and $r = gmp_div_q($n, gmp_pow(gmp_init('2'), $s)); if ( MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_BCMATH ) { $s = 0; // if $n was 1, $r would be 0 and this would be an infinite loop, hence our $this->equals($one) check earlier while ($r->value[strlen($r->value) - 1] % 2 == 0) { $r->value = bcdiv($r->value, '2', 0); ++$s; } } else { for ($i = 0, $r_length = count($r_value); $i < $r_length; ++$i) { $temp = ~$r_value[$i] & 0xFFFFFF; for ($j = 1; ($temp >> $j) & 1; ++$j); if ($j != 25) { break; } } $s = 26 * $i + $j - 1; $r->_rshift($s); } for ($i = 0; $i < $t; ++$i) { $a = $this->random($two, $n_2); $y = $a->modPow($r, $n); if (!$y->equals($one) && !$y->equals($n_1)) { for ($j = 1; $j < $s && !$y->equals($n_1); ++$j) { $y = $y->modPow($two, $n); if ($y->equals($one)) { return false; } } if (!$y->equals($n_1)) { return false; } } } return true; } /** * Logical Left Shift * * Shifts BigInteger's by $shift bits. * * @param Integer $shift * @access private */ function _lshift($shift) { if ( $shift == 0 ) { return; } $num_digits = (int) ($shift / 26); $shift %= 26; $shift = 1 << $shift; $carry = 0; for ($i = 0; $i < count($this->value); ++$i) { $temp = $this->value[$i] * $shift + $carry; $carry = (int) ($temp / 0x4000000); $this->value[$i] = (int) ($temp - $carry * 0x4000000); } if ( $carry ) { $this->value[] = $carry; } while ($num_digits--) { array_unshift($this->value, 0); } } /** * Logical Right Shift * * Shifts BigInteger's by $shift bits. * * @param Integer $shift * @access private */ function _rshift($shift) { if ($shift == 0) { return; } $num_digits = (int) ($shift / 26); $shift %= 26; $carry_shift = 26 - $shift; $carry_mask = (1 << $shift) - 1; if ( $num_digits ) { $this->value = array_slice($this->value, $num_digits); } $carry = 0; for ($i = count($this->value) - 1; $i >= 0; --$i) { $temp = $this->value[$i] >> $shift | $carry; $carry = ($this->value[$i] & $carry_mask) << $carry_shift; $this->value[$i] = $temp; } $this->value = $this->_trim($this->value); } /** * Normalize * * Removes leading zeros and truncates (if necessary) to maintain the appropriate precision * * @param Math_BigInteger * @return Math_BigInteger * @see _trim() * @access private */ function _normalize($result) { $result->precision = $this->precision; $result->bitmask = $this->bitmask; switch ( MATH_BIGINTEGER_MODE ) { case MATH_BIGINTEGER_MODE_GMP: if (!empty($result->bitmask->value)) { $result->value = gmp_and($result->value, $result->bitmask->value); } return $result; case MATH_BIGINTEGER_MODE_BCMATH: if (!empty($result->bitmask->value)) { $result->value = bcmod($result->value, $result->bitmask->value); } return $result; } $value = &$result->value; if ( !count($value) ) { return $result; } $value = $this->_trim($value); if (!empty($result->bitmask->value)) { $length = min(count($value), count($this->bitmask->value)); $value = array_slice($value, 0, $length); for ($i = 0; $i < $length; ++$i) { $value[$i] = $value[$i] & $this->bitmask->value[$i]; } } return $result; } /** * Trim * * Removes leading zeros * * @return Math_BigInteger * @access private */ function _trim($value) { for ($i = count($value) - 1; $i >= 0; --$i) { if ( !empty($value[$i]) ) { break; } unset($value[$i]); } return $value; } /** * Array Repeat * * @param $input Array * @param $multiplier mixed * @return Array * @access private */ function _array_repeat($input, $multiplier) { return ($multiplier) ? array_fill(0, $multiplier, $input) : array(); } /** * Logical Left Shift * * Shifts binary strings $shift bits, essentially multiplying by 2**$shift. * * @param $x String * @param $shift Integer * @return String * @access private */ function _base256_lshift(&$x, $shift) { if ($shift == 0) { return; } $num_bytes = $shift >> 3; // eg. floor($shift/8) $shift &= 7; // eg. $shift % 8 $carry = 0; for ($i = strlen($x) - 1; $i >= 0; --$i) { $temp = ord($x[$i]) << $shift | $carry; $x[$i] = chr($temp); $carry = $temp >> 8; } $carry = ($carry != 0) ? chr($carry) : ''; $x = $carry . $x . str_repeat(chr(0), $num_bytes); } /** * Logical Right Shift * * Shifts binary strings $shift bits, essentially dividing by 2**$shift and returning the remainder. * * @param $x String * @param $shift Integer * @return String * @access private */ function _base256_rshift(&$x, $shift) { if ($shift == 0) { $x = ltrim($x, chr(0)); return ''; } $num_bytes = $shift >> 3; // eg. floor($shift/8) $shift &= 7; // eg. $shift % 8 $remainder = ''; if ($num_bytes) { $start = $num_bytes > strlen($x) ? -strlen($x) : -$num_bytes; $remainder = substr($x, $start); $x = substr($x, 0, -$num_bytes); } $carry = 0; $carry_shift = 8 - $shift; for ($i = 0; $i < strlen($x); ++$i) { $temp = (ord($x[$i]) >> $shift) | $carry; $carry = (ord($x[$i]) << $carry_shift) & 0xFF; $x[$i] = chr($temp); } $x = ltrim($x, chr(0)); $remainder = chr($carry >> $carry_shift) . $remainder; return ltrim($remainder, chr(0)); } // one quirk about how the following functions are implemented is that PHP defines N to be an unsigned long // at 32-bits, while java's longs are 64-bits. /** * Converts 32-bit integers to bytes. * * @param Integer $x * @return String * @access private */ function _int2bytes($x) { return ltrim(pack('N', $x), chr(0)); } /** * Converts bytes to 32-bit integers * * @param String $x * @return Integer * @access private */ function _bytes2int($x) { $temp = unpack('Nint', str_pad($x, 4, chr(0), STR_PAD_LEFT)); return $temp['int']; } } IPv6/IPv6.php 0000644 00000012756 15104256626 0006647 0 ustar 00 <?php /* Copyright (c) 2011, Sam Clarke <www.samclarke.com> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ // Include only if necessary if(!class_exists('Math_BigInteger')){ require_once 'BigInteger.php'; } /** * Converts human readable representation to a 128 bit int * which can be stored in MySQL using DECIMAL(39,0). * * Requires PHP to be compiled with IPv6 support. * This could be made to work without IPv6 support but * I don't think there would be much use for it if PHP * doesn't support IPv6. * * @param string $ip IPv4 or IPv6 address to convert * @return string 128 bit string that can be used with DECIMNAL(39,0) or false */ if(!function_exists('inet_ptoi')) { function inet_ptoi($ip) { // make sure it is an ip if (filter_var($ip, FILTER_VALIDATE_IP) === false) return false; $parts = unpack('N*', inet_pton($ip)); // fix IPv4 if (strpos($ip, '.') !== false) $parts = array(1=>0, 2=>0, 3=>0, 4=>$parts[1]); foreach ($parts as &$part) { // convert any unsigned ints to signed from unpack. // this should be OK as it will be a PHP float not an int if ($part < 0) $part += 4294967296; } // use BCMath if the extension exists if (function_exists('bcadd')) { $decimal = $parts[4]; $decimal = bcadd($decimal, bcmul($parts[3], '4294967296')); $decimal = bcadd($decimal, bcmul($parts[2], '18446744073709551616')); $decimal = bcadd($decimal, bcmul($parts[1], '79228162514264337593543950336')); } // otherwise use the pure PHP BigInteger else { $decimal = new Math_BigInteger($parts[4]); $part3 = new Math_BigInteger($parts[3]); $part2 = new Math_BigInteger($parts[2]); $part1 = new Math_BigInteger($parts[1]); $decimal = $decimal->add($part3->multiply(new Math_BigInteger('4294967296'))); $decimal = $decimal->add($part2->multiply(new Math_BigInteger('18446744073709551616'))); $decimal = $decimal->add($part1->multiply(new Math_BigInteger('79228162514264337593543950336'))); $decimal = $decimal->toString(); } return $decimal; } } /** * Converts a 128 bit int to a human readable representation. * * Requires PHP to be compiled with IPv6 support. * This could be made to work without IPv6 support but * I don't think there would be much use for it if PHP * doesn't support IPv6. * * @param string $decimal 128 bit int * @return string IPv4 or IPv6 */ if(!function_exists('inet_itop')) { function inet_itop($decimal) { $parts = array(); // use BCMath if the extension exists if (function_exists('bcadd')) { $parts[1] = bcdiv($decimal, '79228162514264337593543950336', 0); $decimal = bcsub($decimal, bcmul($parts[1], '79228162514264337593543950336')); $parts[2] = bcdiv($decimal, '18446744073709551616', 0); $decimal = bcsub($decimal, bcmul($parts[2], '18446744073709551616')); $parts[3] = bcdiv($decimal, '4294967296', 0); $decimal = bcsub($decimal, bcmul($parts[3], '4294967296')); $parts[4] = $decimal; } // otherwise use the pure PHP BigInteger else { $decimal = new Math_BigInteger($decimal); list($parts[1],) = $decimal->divide(new Math_BigInteger('79228162514264337593543950336')); $decimal = $decimal->subtract($parts[1]->multiply(new Math_BigInteger('79228162514264337593543950336'))); list($parts[2],) = $decimal->divide(new Math_BigInteger('18446744073709551616')); $decimal = $decimal->subtract($parts[2]->multiply(new Math_BigInteger('18446744073709551616'))); list($parts[3],) = $decimal->divide(new Math_BigInteger('4294967296')); $decimal = $decimal->subtract($parts[3]->multiply(new Math_BigInteger('4294967296'))); $parts[4] = $decimal; $parts[1] = $parts[1]->toString(); $parts[2] = $parts[2]->toString(); $parts[3] = $parts[3]->toString(); $parts[4] = $parts[4]->toString(); } foreach ($parts as &$part) { // convert any signed ints to unsigned for pack // this should be fine as it will be treated as a float if ($part > 2147483647) $part -= 4294967296; } $ip = inet_ntop(pack('N4', $parts[1], $parts[2], $parts[3], $parts[4])); // fix IPv4 by removing :: from the beginning if (strpos($ip, '.') !== false) return substr($ip, 2); return $ip; } } // Example // // echo inet_ptoi('::FFFF:FFFF:FFFF:FFFF') . "\n"; // echo inet_ptoi('::FFFF:FFFF') . "\n"; // echo inet_ptoi('255.255.255.255') . "\n"; // echo inet_itop(inet_ptoi('::FFFF:FFFF:FFFF:FFFF')) . "\n"; // echo inet_itop(inet_ptoi('::FFFF:FFFF')) . "\n"; /* Output: 18446744073709551615 4294967295 4294967295 ::ffff:ffff:ffff:ffff 255.255.255.255 */ pquery/IQuery.php 0000644 00000012430 15104270523 0010017 0 ustar 00 <?php namespace pagelayerQuery; interface IQuery extends \Countable { /// Methods /// /** * Adds the specified class(es) to each of the set of matched elements. * @param string $classname The name of the class to add. You can add multiple classes by separating them with spaces. * @return IQuery */ function addClass($classname); /** * Insert content, specified by the parameter, after each element in the set of matched elements. * @param string $content The content to add. * @return IQuery */ function after($content); /** * Insert content, specified by the parameter, to the end of each element in the set of matched elements. * @param string $content The content to append. * @return IQuery */ function append($content); /** * Get the value of an attribute for the first element in the set of matched elements or set one * or more attributes for every matched element. * @param string $name The name of the attribute. * @param null|string $value The value to set or null to get the current attribute value. * @return string|IQuery */ function attr($name, $value = null); /** * Insert content, specified by the parameter, before each element in the set of matched elements. * @param string $content The content to add. * @return IQuery */ function before($content); /** * Remove all child nodes of the set of matched elements from the DOM. * @return IQuery; */ function clear(); /** * Get the value of a style property for the first element in the set of matched elements or * set one or more CSS properties for every matched element. */ // function css($name, $value = null); /** * Determine whether any of the matched elements are assigned the given class. * @param string $classname The name of the class to check. */ function hasClass($classname); /** * Get the HTML contents of the first element in the set of matched elements * or set the HTML contents of every matched element. * @param string|null $value The value to set. */ function html($value = null); /** * Insert content, specified by the parameter, to the beginning of each element in the set of matched elements. * @param string $content The content to add. */ function prepend($content); /** * Get the value of a property for the first element in the set of matched elements * or set one or more properties for every matched element. * @param string $name The name of the property. * The currently supported properties are `tagname`, `selected`, and `checked`. * @param null|string $value The value to set or null to get the current property value. */ function prop($name, $value = null); /** * Remove the set of matched elements from the DOM. * @param null|string $selector A css query to filter the set of removed nodes. */ function remove($selector = null); /** * Remove an attribute from each element in the set of matched elements. * @param string $name The name of the attribute to remove. */ function removeAttr($name); /** * Remove a single class, multiple classes, or all classes from each element in the set of matched elements. * @param string $classname The name of the class to remove. */ function removeClass($classname); /** * Replace each element in the set of matched elements with the provided new content and return the set of elements that was removed. * @param string $content The content that will replace the nodes. */ function replaceWith($content); /** * Returns the name of the element. * @param null|string $tagName A new tag name or null to return the current tag name. */ function tagName($value = null); /** * Get the combined text contents of each element in the set of matched elements, including their descendants, or set the text contents of the matched elements. * @param null|string $value A string to set the text or null to return the current text. */ function text($value = null); /** * Add or remove one or more classes from each element in the set of matched elements, * depending on either the class’s presence or the value of the switch argument. * @param string $classname * @param bool|null */ function toggleClass($classname, $switch = null); /** * Remove the parents of the set of matched elements from the DOM, leaving the matched elements in their place. */ function unwrap(); /** * Get the current value of the first element in the set of matched elements or set the value of every matched element. * @param string|null $value The new value of the element or null to return the current value. */ function val($value = null); /** * Wrap an HTML structure around each element in the set of matched elements. * @param string A tag name or html string specifying the structure to wrap around the matched elements. */ function wrap($wrapping_element); /** * Wrap an HTML structure around the content of each element in the set of matched elements. * @param string A tag name or html string specifying the structure to wrap around the content of the matched elements. */ function wrapInner($wrapping_element); } pquery/gan_node_html.php 0000644 00000224041 15104270523 0011402 0 ustar 00 <?php /** * @author Niels A.D. * @author Todd Burry <todd@vanillaforums.com> * @copyright 2010 Niels A.D., 2014 Todd Burry * @license http://opensource.org/licenses/LGPL-2.1 LGPL-2.1 * @package pQuery */ namespace pagelayerQuery; /** * Holds (x)html/xml tag information like tag name, attributes, * parent, children, self close, etc. * */ class DomNode implements IQuery { /** * Element Node, used for regular elements */ const NODE_ELEMENT = 0; /** * Text Node */ const NODE_TEXT = 1; /** * Comment Node */ const NODE_COMMENT = 2; /** * Conditional Node (<![if]> <![endif]) */ const NODE_CONDITIONAL = 3; /** * CDATA Node (<![CDATA[]]> */ const NODE_CDATA = 4; /** * Doctype Node */ const NODE_DOCTYPE = 5; /** * XML Node, used for tags that start with ?, like <?xml and <?php */ const NODE_XML = 6; /** * ASP Node */ const NODE_ASP = 7; #php4 Compatibility with PHP4, this gets changed to a regular var in release tool #static $NODE_TYPE = self::NODE_ELEMENT; #php4e #php5 /** * Node type of class */ const NODE_TYPE = self::NODE_ELEMENT; #php5e /** * Name of the selector class * @var string * @see select() */ var $selectClass = 'pagelayerQuery\\HtmlSelector'; /** * Name of the parser class * @var string * @see setOuterText() * @see setInnerText() */ var $parserClass = 'pagelayerQuery\\Html5Parser'; /** * Name of the class used for {@link addChild()} * @var string */ var $childClass = __CLASS__; /** * Name of the class used for {@link addText()} * @var string */ var $childClass_Text = 'pagelayerQuery\\TextNode'; /** * Name of the class used for {@link addComment()} * @var string */ var $childClass_Comment = 'pagelayerQuery\\CommentNode'; /** * Name of the class used for {@link addContional()} * @var string */ var $childClass_Conditional = 'pagelayerQuery\\ConditionalTagNode'; /** * Name of the class used for {@link addCDATA()} * @var string */ var $childClass_CDATA = 'pagelayerQuery\\CdataNode'; /** * Name of the class used for {@link addDoctype()} * @var string */ var $childClass_Doctype = 'pagelayerQuery\\DoctypeNode'; /** * Name of the class used for {@link addXML()} * @var string */ var $childClass_XML = 'pagelayerQuery\\XmlNode'; /** * Name of the class used for {@link addASP()} * @var string */ var $childClass_ASP = 'pagelayerQuery\\AspEmbeddedNode'; /** * Parent node, null if none * @var DomNode * @see changeParent() */ var $parent = null; /** * Attributes of node * @var array * @internal array('attribute' => 'value') * @internal Public for faster access! * @see getAttribute() * @see setAttribute() * @access private */ var $attributes = array(); /** * Namespace info for attributes * @var array * @internal array('tag' => array(array('ns', 'tag', 'ns:tag', index))) * @internal Public for easy outside modifications! * @see findAttribute() * @access private */ var $attributes_ns = null; /** * Array of child nodes * @var array * @internal Public for faster access! * @see childCount() * @see getChild() * @see addChild() * @see deleteChild() * @access private */ var $children = array(); /** * Full tag name (including namespace) * @var string * @see getTagName() * @see getNamespace() */ var $tag = ''; /** * Namespace info for tag * @var array * @internal array('namespace', 'tag') * @internal Public for easy outside modifications! * @access private */ var $tag_ns = null; /** * Is node a self closing node? No closing tag if true. * @var bool */ var $self_close = false; /** * If self close, then this will be used to close the tag * @var string * @see $self_close */ var $self_close_str = ' /'; /** * Use short tags for attributes? If true, then attributes * with values equal to the attribute name will not output * the value, e.g. selected="selected" will be selected. * @var bool */ var $attribute_shorttag = false; /** * Function map used for the selector filter * @var array * @internal array('root' => 'filter_root') will cause the * selector to call $this->filter_root at :root * @access private */ var $filter_map = array( 'root' => 'filter_root', 'nth-child' => 'filter_nchild', 'eq' => 'filter_nchild', //jquery (naming) compatibility 'gt' => 'filter_gt', 'lt' => 'filter_lt', 'nth-last-child' => 'filter_nlastchild', 'nth-of-type' => 'filter_ntype', 'nth-last-of-type' => 'filter_nlastype', 'odd' => 'filter_odd', 'even' => 'filter_even', 'every' => 'filter_every', 'first-child' => 'filter_first', 'last-child' => 'filter_last', 'first-of-type' => 'filter_firsttype', 'last-of-type' => 'filter_lasttype', 'only-child' => 'filter_onlychild', 'only-of-type' => 'filter_onlytype', 'empty' => 'filter_empty', 'not-empty' => 'filter_notempty', 'has-text' => 'filter_hastext', 'no-text' => 'filter_notext', 'lang' => 'filter_lang', 'contains' => 'filter_contains', 'has' => 'filter_has', 'not' => 'filter_not', 'element' => 'filter_element', 'text' => 'filter_text', 'comment' => 'filter_comment', 'checked' => 'filter_checked', 'selected' => 'filter_selected', ); /** * Class constructor * @param string|array $tag Name of the tag, or array with taginfo (array( * 'tag_name' => 'tag', * 'self_close' => false, * 'attributes' => array('attribute' => 'value'))) * @param DomNode $parent Parent of node, null if none */ function __construct($tag, $parent) { $this->parent = $parent; if (is_string($tag)) { $this->tag = $tag; } else { $this->tag = $tag['tag_name']; $this->self_close = $tag['self_close']; $this->attributes = $tag['attributes']; } } #php4 PHP4 class constructor compatibility #function DomNode($tag, $parent) {return $this->__construct($tag, $parent);} #php4e /** * Class destructor * @access private */ function __destruct() { $this->delete(); } /** * Class toString, outputs {@link $tag} * @return string * @access private */ function __toString() { return (($this->tag === '~root~') ? $this->toString(true, true, 1) : $this->tag); } /** * Class magic get method, outputs {@link getAttribute()} * @return string * @access private */ function __get($attribute) { return $this->getAttribute($attribute); } /** * Class magic set method, performs {@link setAttribute()} * @access private */ function __set($attribute, $value) { $this->setAttribute($attribute, $value); } /** * Class magic isset method, returns {@link hasAttribute()} * @return bool * @access private */ function __isset($attribute) { return $this->hasAttribute($attribute); } /** * Class magic unset method, performs {@link deleteAttribute()} * @access private */ function __unset($attribute) { return $this->deleteAttribute($attribute); } /** * Class magic invoke method, performs {@link query()}. * @param string $query The css query to run on the nodes. * @return \pQuery */ function __invoke($query = '*') { return $this->query($query); } /** * Returns place in document * @return string */ function dumpLocation() { return (($this->parent) ? (($p = $this->parent->dumpLocation()) ? $p.' > ' : '').$this->tag.'('.$this->typeIndex().')' : ''); } /** * Returns all the attributes and their values * @return string * @access private */ protected function toString_attributes() { $s = ''; foreach($this->attributes as $a => $v) { $s .= ' '.$a; if ((!$this->attribute_shorttag) || ($v !== $a)) { $quote = '"';//(strpos($v, '"') === false) ? '"' : "'"; $v = str_replace('"', '"', $v); $s .= '='.$quote.$v.$quote; } } return $s; } /** * Returns the content of the node (child tags and text) * @param bool $attributes Print attributes of child tags * @param bool|int $recursive How many sublevels of childtags to print. True for all. * @param bool $content_only Only print text, false will print tags too. * @return string * @access private */ protected function toString_content($attributes = true, $recursive = true, $content_only = false) { $s = ''; foreach($this->children as $c) { $s .= $c->toString($attributes, $recursive, $content_only); } return $s; } /** * Returns the node as string * @param bool $attributes Print attributes (of child tags) * @param bool|int $recursive How many sub-levels of child tags to print. True for all. * @param bool|int $content_only Only print text, false will print tags too. * @return string */ function toString($attributes = true, $recursive = true, $content_only = false) { if ($content_only) { if (is_int($content_only)) { --$content_only; } return $this->toString_content($attributes, $recursive, $content_only); } $s = '<'.$this->tag; if ($attributes) { $s .= $this->toString_attributes(); } if ($this->self_close) { $s .= $this->self_close_str.'>'; } else { $s .= '>'; if($recursive) { $s .= $this->toString_content($attributes); } $s .= '</'.$this->tag.'>'; } return $s; } /** * Similar to JavaScript outerText, will return full (html formatted) node * @return string */ function getOuterText() { return $this->toString(); } /** * Similar to JavaScript outerText, will replace node (and child nodes) with new text * @param string $text * @param HtmlParserBase $parser Null to auto create instance * @return bool|array True on succeed, array with errors on failure */ function setOuterText($text, $parser = null) { if (trim($text)) { $index = $this->index(); if ($parser === null) { $parser = new $this->parserClass(); } $parser->setDoc($text); $parser->parse_all(); $parser->root->moveChildren($this->parent, $index); } $this->delete(); return (($parser && $parser->errors) ? $parser->errors : true); } /** * Return html code of node * @internal jquery (naming) compatibility * @param string|null $value The value to set or null to get the value. * @see toString() * @return string */ function html($value = null) { if ($value !== null) { $this->setInnerText($value); } return $this->getInnerText(); } /** * Similar to JavaScript innerText, will return (html formatted) content * @return string */ function getInnerText() { return $this->toString(true, true, 1); } /** * Similar to JavaScript innerText, will replace child nodes with new text * @param string $text * @param HtmlParserBase $parser Null to auto create instance * @return bool|array True on succeed, array with errors on failure */ function setInnerText($text, $parser = null) { $this->clear(); if (trim($text)) { if ($parser === null) { $parser = new $this->parserClass(); } $parser->root =& $this; $parser->setDoc($text); $parser->parse_all(); } return (($parser && $parser->errors) ? $parser->errors : true); } /** * Similar to JavaScript plainText, will return text in node (and subnodes) * @return string */ function getPlainText() { return preg_replace('`\s+`', ' ', $this->toString(true, true, true)); } /** * Return plaintext taking document encoding into account * @return string */ function getPlainTextUTF8() { $txt = $this->toString(true, true, true); $enc = $this->getEncoding(); if ($enc !== false) { $txt = mb_convert_encoding($txt, 'UTF-8', $enc); } return preg_replace('`\s+`', ' ', $txt); } /** * Similar to JavaScript plainText, will replace child nodes with new text (literal) * @param string $text */ function setPlainText($text) { $this->clear(); if (trim($text)) { $this->addText($text); } } /** * Delete node from parent and clear node */ function delete() { if (($p = $this->parent) !== null) { $this->parent = null; $p->deleteChild($this); } else { $this->clear(); } } /** * Detach node from parent * @param bool $move_children_up Only detach current node and replace it with child nodes * @internal jquery (naming) compatibility * @see delete() */ function detach($move_children_up = false) { if (($p = $this->parent) !== null) { $index = $this->index(); $this->parent = null; if ($move_children_up) { $this->moveChildren($p, $index); } $p->deleteChild($this, true); } } /** * Deletes all child nodes from node */ function clear() { foreach($this->children as $c) { $c->parent = null; $c->delete(); } $this->children = array(); } /** * Get top parent * @return DomNode Root, null if node has no parent */ function getRoot() { $r = $this->parent; $n = ($r === null) ? null : $r->parent; while ($n !== null) { $r = $n; $n = $r->parent; } return $r; } /** * Change parent * @param null|DomNode $to New parent, null if none * @param false|int $index Add child to parent if not present at index, false to not add, negative to count from end, null to append */ #php4 #function changeParent($to, &$index) { #php4e #php5 function changeParent($to, &$index = null) { #php5e if ($this->parent !== null) { $this->parent->deleteChild($this, true); } $this->parent = $to; if ($index !== false) { $new_index = $this->index(); if (!(is_int($new_index) && ($new_index >= 0))) { $this->parent->addChild($this, $index); } } } /** * Find out if node has (a certain) parent * @param DomNode|string $tag Match against parent, string to match tag, object to fully match node, null to return if node has parent * @param bool $recursive * @return bool */ function hasParent($tag = null, $recursive = false) { if ($this->parent !== null) { if ($tag === null) { return true; } elseif (is_string($tag)) { return (($this->parent->tag === $tag) || ($recursive && $this->parent->hasParent($tag))); } elseif (is_object($tag)) { return (($this->parent === $tag) || ($recursive && $this->parent->hasParent($tag))); } } return false; } /** * Find out if node is parent of a certain tag * @param DomNode|string $tag Match against parent, string to match tag, object to fully match node * @param bool $recursive * @return bool * @see hasParent() */ function isParent($tag, $recursive = false) { return ($this->hasParent($tag, $recursive) === ($tag !== null)); } /** * Find out if node is text * @return bool */ function isText() { return false; } /** * Find out if node is comment * @return bool */ function isComment() { return false; } /** * Find out if node is text or comment node * @return bool */ function isTextOrComment() { return false; } /** * Move node to other node * @param DomNode $to New parent, null if none * @param int $new_index Add child to parent at index if not present, null to not add, negative to count from end * @internal Performs {@link changeParent()} */ #php4 #function move($to, &$new_index) { #php4e #php5 function move($to, &$new_index = -1) { #php5e $this->changeParent($to, $new_index); } /** * Move child nodes to other node * @param DomNode $to New parent, null if none * @param int $new_index Add child to new node at index if not present, null to not add, negative to count from end * @param int $start Index from child node where to start wrapping, 0 for first element * @param int $end Index from child node where to end wrapping, -1 for last element */ #php4 #function moveChildren($to, &$new_index, $start = 0, $end = -1) { #php4e #php5 function moveChildren($to, &$new_index = -1, $start = 0, $end = -1) { #php5e if ($end < 0) { $end += count($this->children); } for ($i = $start; $i <= $end; $i++) { $this->children[$start]->changeParent($to, $new_index); } } /** * Index of node in parent * @param bool $count_all True to count all tags, false to ignore text and comments * @return int -1 if not found */ function index($count_all = true) { if (!$this->parent) { return -1; } elseif ($count_all) { return $this->parent->findChild($this); } else{ $index = -1; //foreach($this->parent->children as &$c) { // if (!$c->isTextOrComment()) { // ++$index; // } // if ($c === $this) { // return $index; // } //} foreach(array_keys($this->parent->children) as $k) { if (!$this->parent->children[$k]->isTextOrComment()) { ++$index; } if ($this->parent->children[$k] === $this) { return $index; } } return -1; } } /** * Change index of node in parent * @param int $index New index */ function setIndex($index) { if ($this->parent) { if ($index > $this->index()) { --$index; } $this->delete(); $this->parent->addChild($this, $index); } } /** * Index of all similar nodes in parent * @return int -1 if not found */ function typeIndex() { if (!$this->parent) { return -1; } else { $index = -1; //foreach($this->parent->children as &$c) { // if (strcasecmp($this->tag, $c->tag) === 0) { // ++$index; // } // if ($c === $this) { // return $index; // } //} foreach(array_keys($this->parent->children) as $k) { if (strcasecmp($this->tag, $this->parent->children[$k]->tag) === 0) { ++$index; } if ($this->parent->children[$k] === $this) { return $index; } } return -1; } } /** * Calculate indent of node (number of parent tags - 1) * @return int */ function indent() { return (($this->parent) ? $this->parent->indent() + 1 : -1); } /** * Get sibling node * @param int $offset Offset from current node * @return DomNode Null if not found */ function getSibling($offset = 1) { $index = $this->index() + $offset; if (($index >= 0) && ($index < $this->parent->childCount())) { return $this->parent->getChild($index); } else { return null; } } /** * Get node next to current * @param bool $skip_text_comments * @return DomNode Null if not found * @see getSibling() * @see getPreviousSibling() */ function getNextSibling($skip_text_comments = true) { $offset = 1; while (($n = $this->getSibling($offset)) !== null) { if ($skip_text_comments && ($n->tag[0] === '~')) { ++$offset; } else { break; } } return $n; } /** * Get node previous to current * @param bool $skip_text_comments * @return DomNode Null if not found * @see getSibling() * @see getNextSibling() */ function getPreviousSibling($skip_text_comments = true) { $offset = -1; while (($n = $this->getSibling($offset)) !== null) { if ($skip_text_comments && ($n->tag[0] === '~')) { --$offset; } else { break; } } return $n; } /** * Get namespace of node * @return string * @see setNamespace() */ function getNamespace() { if ($this->tag_ns === null) { $a = explode(':', $this->tag, 2); if (empty($a[1])) { $this->tag_ns = array('', $a[0]); } else { $this->tag_ns = array($a[0], $a[1]); } } return $this->tag_ns[0]; } /** * Set namespace of node * @param string $ns * @see getNamespace() */ function setNamespace($ns) { if ($this->getNamespace() !== $ns) { $this->tag_ns[0] = $ns; $this->tag = $ns.':'.$this->tag_ns[1]; } } /** * Get tagname of node (without namespace) * @return string * @see setTag() */ function getTag() { if ($this->tag_ns === null) { $this->getNamespace(); } return $this->tag_ns[1]; } /** * Set tag (with or without namespace) * @param string $tag * @param bool $with_ns Does $tag include namespace? * @see getTag() */ function setTag($tag, $with_ns = false) { $with_ns = $with_ns || (strpos($tag, ':') !== false); if ($with_ns) { $this->tag = $tag; $this->tag_ns = null; } elseif ($this->getTag() !== $tag) { $this->tag_ns[1] = $tag; $this->tag = (($this->tag_ns[0]) ? $this->tag_ns[0].':' : '').$tag; } } /** * Try to determine the encoding of the current tag * @return string|bool False if encoding could not be found */ function getEncoding() { $root = $this->getRoot(); if ($root !== null) { if ($enc = $root->select('meta[charset]', 0, true, true)) { return $enc->getAttribute("charset"); } elseif ($enc = $root->select('"?xml"[encoding]', 0, true, true)) { return $enc->getAttribute("encoding"); } elseif ($enc = $root->select('meta[content*="charset="]', 0, true, true)) { $enc = $enc->getAttribute("content"); return substr($enc, strpos($enc, "charset=")+8); } } return false; } /** * Number of children in node * @param bool $ignore_text_comments Ignore text/comments with calculation * @return int */ function childCount($ignore_text_comments = false) { if (!$ignore_text_comments) { return count($this->children); } else{ $count = 0; //foreach($this->children as &$c) { // if (!$c->isTextOrComment()) { // ++$count; // } //} foreach(array_keys($this->children) as $k) { if (!$this->children[$k]->isTextOrComment()) { ++$count; } } return $count; } } /** * Find node in children * @param DomNode $child * @return int False if not found */ function findChild($child) { return array_search($child, $this->children, true); } /** * Checks if node has another node as child * @param DomNode $child * @return bool */ function hasChild($child) { return ((bool) findChild($child)); } /** * Get childnode * @param int|DomNode $child Index, negative to count from end * @param bool $ignore_text_comments Ignore text/comments with index calculation * @return DomNode */ function &getChild($child, $ignore_text_comments = false) { if (!is_int($child)) { $child = $this->findChild($child); } elseif ($child < 0) { $child += $this->childCount($ignore_text_comments); } if ($ignore_text_comments) { $count = 0; $last = null; //foreach($this->children as &$c) { // if (!$c->isTextOrComment()) { // if ($count++ === $child) { // return $c; // } // $last = $c; // } //} foreach(array_keys($this->children) as $k) { if (!$this->children[$k]->isTextOrComment()) { if ($count++ === $child) { return $this->children[$k]; } $last = $this->children[$k]; } } return (($child > $count) ? $last : null); } else { return $this->children[$child]; } } /** * Add child node * @param string|DomNode $tag Tag name or object * @param int $offset Position to insert node, negative to count from end, null to append * @return DomNode Added node */ #php4 #function &addChild($tag, &$offset) { #php4e #php5 function &addChild($tag, &$offset = null) { #php5e if (is_array($tag)) { $tag = new $this->childClass($tag, $this); } elseif (is_string($tag)) { $nodes = $this->createNodes($tag); $tag = array_shift($nodes); if ($tag && $tag->parent !== $this) { $index = false; $tag->changeParent($this, $index); } } elseif (is_object($tag) && $tag->parent !== $this) { $index = false; //Needs to be passed by ref $tag->changeParent($this, $index); } if (is_int($offset) && ($offset < count($this->children)) && ($offset !== -1)) { if ($offset < 0) { $offset += count($this->children); } array_splice($this->children, $offset++, 0, array(&$tag)); } else { $this->children[] =& $tag; } return $tag; } /** * First child node * @param bool $ignore_text_comments Ignore text/comments with index calculation * @return DomNode */ function &firstChild($ignore_text_comments = false) { return $this->getChild(0, $ignore_text_comments); } /** * Last child node * @param bool $ignore_text_comments Ignore text/comments with index calculation * @return DomNode */ function &lastChild($ignore_text_comments = false) { return $this->getChild(-1, $ignore_text_comments); } /** * Insert childnode * @param string|DomNode $tag Tagname or object * @param int $offset Position to insert node, negative to count from end, null to append * @return DomNode Added node * @see addChild(); */ function &insertChild($tag, $index) { return $this->addChild($tag, $index); } /** * Add text node * @param string $text * @param int $offset Position to insert node, negative to count from end, null to append * @return DomNode Added node * @see addChild(); */ #php4 #function &addText($text, &$offset) { #php4e #php5 function &addText($text, &$offset = null) { #php5e return $this->addChild(new $this->childClass_Text($this, $text), $offset); } /** * Add comment node * @param string $text * @param int $offset Position to insert node, negative to count from end, null to append * @return DomNode Added node * @see addChild(); */ #php4 #function &addComment($text, &$offset) { #php4e #php5 function &addComment($text, &$offset = null) { #php5e return $this->addChild(new $this->childClass_Comment($this, $text), $offset); } /** * Add conditional node * @param string $condition * @param bool True for <!--[if, false for <![if * @param int $offset Position to insert node, negative to count from end, null to append * @return DomNode Added node * @see addChild(); */ #php4 #function &addConditional($condition, $hidden = true, &$offset) { #php4e #php5 function &addConditional($condition, $hidden = true, &$offset = null) { #php5e return $this->addChild(new $this->childClass_Conditional($this, $condition, $hidden), $offset); } /** * Add CDATA node * @param string $text * @param int $offset Position to insert node, negative to count from end, null to append * @return DomNode Added node * @see addChild(); */ #php4 #function &addCDATA($text, &$offset) { #php4e #php5 function &addCDATA($text, &$offset = null) { #php5e return $this->addChild(new $this->childClass_CDATA($this, $text), $offset); } /** * Add doctype node * @param string $dtd * @param int $offset Position to insert node, negative to count from end, null to append * @return DomNode Added node * @see addChild(); */ #php4 #function &addDoctype($dtd, &$offset) { #php4e #php5 function &addDoctype($dtd, &$offset = null) { #php5e return $this->addChild(new $this->childClass_Doctype($this, $dtd), $offset); } /** * Add xml node * @param string $tag Tag name after "?", e.g. "php" or "xml" * @param string $text * @param array $attributes Array of attributes (array('attribute' => 'value')) * @param int $offset Position to insert node, negative to count from end, null to append * @return DomNode Added node * @see addChild(); */ #php4 #function &addXML($tag = 'xml', $text = '', $attributes = array(), &$offset) { #php4e #php5 function &addXML($tag = 'xml', $text = '', $attributes = array(), &$offset = null) { #php5e return $this->addChild(new $this->childClass_XML($this, $tag, $text, $attributes), $offset); } /** * Add ASP node * @param string $tag Tag name after "%" * @param string $text * @param array $attributes Array of attributes (array('attribute' => 'value')) * @param int $offset Position to insert node, negative to count from end, null to append * @return DomNode Added node * @see addChild(); */ #php4 #function &addASP($tag = '', $text = '', $attributes = array(), &$offset) { #php4e #php5 function &addASP($tag = '', $text = '', $attributes = array(), &$offset = null) { #php5e return $this->addChild(new $this->childClass_ASP($this, $tag, $text, $attributes), $offset); } /** * Delete a child node * @param int|DomNode $child Child(index) to delete, negative to count from end * @param bool $soft_delete False to call {@link delete()} from child */ function deleteChild($child, $soft_delete = false) { if (is_object($child)) { $child = $this->findChild($child); } elseif ($child < 0) { $child += count($this->children); } if (!$soft_delete) { $this->children[$child]->delete(); } unset($this->children[$child]); //Rebuild indices $tmp = array(); //foreach($this->children as &$c) { // $tmp[] =& $c; //} foreach(array_keys($this->children) as $k) { $tmp[] =& $this->children[$k]; } $this->children = $tmp; } /** * Wrap node * @param string|DomNode $node Wrapping node, string to create new element node * @param int $wrap_index Index to insert current node in wrapping node, -1 to append * @param int $node_index Index to insert wrapping node, null to keep at same position * @return DomNode Wrapping node */ function wrap($node, $wrap_index = -1, $node_index = null) { if ($node_index === null) { $node_index = $this->index(); } if (!is_object($node)) { $node = $this->parent->addChild($node, $node_index); } elseif ($node->parent !== $this->parent) { $node->changeParent($this->parent, $node_index); } $this->changeParent($node, $wrap_index); return $node; } /** * Wrap child nodes * @param string|DomNode $node Wrapping node, string to create new element node * @param int $start Index from child node where to start wrapping, 0 for first element * @param int $end Index from child node where to end wrapping, -1 for last element * @param int $wrap_index Index to insert in wrapping node, -1 to append * @param int $node_index Index to insert current node, null to keep at same position * @return DomNode Wrapping node */ function wrapInner($node, $start = 0, $end = -1, $wrap_index = -1, $node_index = null) { if ($end < 0) { $end += count($this->children); } if ($node_index === null) { $node_index = $end + 1; } if (!is_object($node)) { $node = $this->addChild($node, $node_index); } elseif ($node->parent !== $this) { $node->changeParent($this->parent, $node_index); } $this->moveChildren($node, $wrap_index, $start, $end); return $node; } /** * Number of attributes * @return int */ function attributeCount() { return count($this->attributes); } /** * Find attribute using namespace, name or both * @param string|int $attr Negative int to count from end * @param string $compare "namespace", "name" or "total" * @param bool $case_sensitive Compare with case sensitivity * @return array array('ns', 'attr', 'ns:attr', index) * @access private */ protected function findAttribute($attr, $compare = 'total', $case_sensitive = false) { if (is_int($attr)) { if ($attr < 0) { $attr += count($this->attributes); } $keys = array_keys($this->attributes); return $this->findAttribute($keys[$attr], 'total', true); } else if ($compare === 'total') { $b = explode(':', $attr, 2); if ($case_sensitive) { $t =& $this->attributes; } else { $t = array_change_key_case($this->attributes); $attr = strtolower($attr); } if (isset($t[$attr])) { $index = 0; foreach($this->attributes as $a => $v) { if (($v === $t[$attr]) && (strcasecmp($a, $attr) === 0)) { $attr = $a; $b = explode(':', $attr, 2); break; } ++$index; } if (empty($b[1])) { return array(array('', $b[0], $attr, $index)); } else { return array(array($b[0], $b[1], $attr, $index)); } } else { return false; } } else { if ($this->attributes_ns === null) { $index = 0; foreach($this->attributes as $a => $v) { $b = explode(':', $a, 2); if (empty($b[1])) { $this->attributes_ns[$b[0]][] = array('', $b[0], $a, $index); } else { $this->attributes_ns[$b[1]][] = array($b[0], $b[1], $a, $index); } ++$index; } } if ($case_sensitive) { $t =& $this->attributes_ns; } else { $t = array_change_key_case($this->attributes_ns); $attr = strtolower($attr); } if ($compare === 'namespace') { $res = array(); foreach($t as $ar) { foreach($ar as $a) { if ($a[0] === $attr) { $res[] = $a; } } } return $res; } elseif ($compare === 'name') { return ((isset($t[$attr])) ? $t[$attr] : false); } else { trigger_error('Unknown comparison mode'); } } } /** * Checks if node has attribute * @param string|int$attr Negative int to count from end * @param string $compare Find node using "namespace", "name" or "total" * @param bool $case_sensitive Compare with case sensitivity * @return bool */ function hasAttribute($attr, $compare = 'total', $case_sensitive = false) { return ((bool) $this->findAttribute($attr, $compare, $case_sensitive)); } /** * Gets namespace of attribute(s) * @param string|int $attr Negative int to count from end * @param string $compare Find node using "namespace", "name" or "total" * @param bool $case_sensitive Compare with case sensitivity * @return string|array False if not found */ function getAttributeNS($attr, $compare = 'name', $case_sensitive = false) { $f = $this->findAttribute($attr, $compare, $case_sensitive); if (is_array($f) && $f) { if (count($f) === 1) { return $this->attributes[$f[0][0]]; } else { $res = array(); foreach($f as $a) { $res[] = $a[0]; } return $res; } } else { return false; } } /** * Sets namespace of attribute(s) * @param string|int $attr Negative int to count from end * @param string $namespace * @param string $compare Find node using "namespace", "name" or "total" * @param bool $case_sensitive Compare with case sensitivity * @return bool */ function setAttributeNS($attr, $namespace, $compare = 'name', $case_sensitive = false) { $f = $this->findAttribute($attr, $compare, $case_sensitive); if (is_array($f) && $f) { if ($namespace) { $namespace .= ':'; } foreach($f as $a) { $val = $this->attributes[$a[2]]; unset($this->attributes[$a[2]]); $this->attributes[$namespace.$a[1]] = $val; } $this->attributes_ns = null; return true; } else { return false; } } /** * Gets value(s) of attribute(s) * @param string|int $attr Negative int to count from end * @param string $compare Find node using "namespace", "name" or "total" * @param bool $case_sensitive Compare with case sensitivity * @return string|array */ function getAttribute($attr, $compare = 'total', $case_sensitive = false) { $f = $this->findAttribute($attr, $compare, $case_sensitive); if (is_array($f) && $f){ if (count($f) === 1) { return $this->attributes[$f[0][2]]; } else { $res = array(); foreach($f as $a) { $res[] = $this->attributes[$a[2]]; } return $res; } } else { return null; } } /** * Sets value(s) of attribute(s) * @param string|int $attr Negative int to count from end * @param string $compare Find node using "namespace", "name" or "total" * @param bool $case_sensitive Compare with case sensitivity */ function setAttribute($attr, $val, $compare = 'total', $case_sensitive = false) { if ($val === null) { return $this->deleteAttribute($attr, $compare, $case_sensitive); } $f = $this->findAttribute($attr, $compare, $case_sensitive); if (is_array($f) && $f) { foreach($f as $a) { $this->attributes[$a[2]] = (string) $val; } } else { $this->attributes[$attr] = (string) $val; } } /** * Add new attribute * @param string $attr * @param string $val */ function addAttribute($attr, $val) { $this->setAttribute($attr, $val, 'total', true); } /** * Delete attribute(s) * @param string|int $attr Negative int to count from end * @param string $compare Find node using "namespace", "name" or "total" * @param bool $case_sensitive Compare with case sensitivity */ function deleteAttribute($attr, $compare = 'total', $case_sensitive = false) { $f = $this->findAttribute($attr, $compare, $case_sensitive); if (is_array($f) && $f) { foreach($f as $a) { unset($this->attributes[$a[2]]); if ($this->attributes_ns !== null) { unset($this->attributes_ns[$a[1]]); } } } } /** * Determine if node has a certain class * @param string $className * @return bool */ function hasClass($className) { return ($className && preg_match('`\b'.preg_quote($className).'\b`si', $this->class)); } /** * Add new class(es) * @param string|array $className */ function addClass($className) { if (!is_array($className)) { $className = array($className); } $class = isset($this->class) ? $this->class : ''; foreach ($className as $c) { if (!(preg_match('`\b'.preg_quote($c).'\b`si', $class) > 0)) { $class .= ' '.$c; } } $this->class = trim($class); } /** * Remove clas(ses) * @param string|array $className */ function removeClass($className) { if (!is_array($className)) { $className = array($className); } $class = $this->class; foreach ($className as $c) { $class = preg_replace('`\b'.preg_quote($c).'\b`si', '', $class); } if ($class) { $this->class = $class; } else { unset($this->class); } } /** * Finds children using a callback function * @param callable $callback Function($node) that returns a bool * @param bool|int $recursive Check recursively * @param bool $check_self Include this node in search? * @return array */ function getChildrenByCallback($callback, $recursive = true, $check_self = false) { $count = $this->childCount(); if ($check_self && $callback($this)) { $res = array($this); } else { $res = array(); } if ($count > 0) { if (is_int($recursive)) { $recursive = (($recursive > 1) ? $recursive - 1 : false); } for ($i = 0; $i < $count; $i++) { if ($callback($this->children[$i])) { $res[] = $this->children[$i]; } if ($recursive) { $res = array_merge($res, $this->children[$i]->getChildrenByCallback($callback, $recursive)); } } } return $res; } /** * Finds children using the {$link match()} function * @param $conditions See {$link match()} * @param $custom_filters See {$link match()} * @param bool|int $recursive Check recursively * @param bool $check_self Include this node in search? * @return array */ function getChildrenByMatch($conditions, $recursive = true, $check_self = false, $custom_filters = array()) { $count = $this->childCount(); if ($check_self && $this->match($conditions, true, $custom_filters)) { $res = array($this); } else { $res = array(); } if ($count > 0) { if (is_int($recursive)) { $recursive = (($recursive > 1) ? $recursive - 1 : false); } for ($i = 0; $i < $count; $i++) { if ($this->children[$i]->match($conditions, true, $custom_filters)) { $res[] = $this->children[$i]; } if ($recursive) { $res = array_merge($res, $this->children[$i]->getChildrenByMatch($conditions, $recursive, false, $custom_filters)); } } } return $res; } /** * Checks if tag matches certain conditions * @param array $tags array('tag1', 'tag2') or array(array( * 'tag' => 'tag1', * 'operator' => 'or'/'and', * 'compare' => 'total'/'namespace'/'name', * 'case_sensitive' => true)) * @return bool * @internal Used by selector class * @see match() * @access private */ protected function match_tags($tags) { $res = false; foreach($tags as $tag => $match) { if (!is_array($match)) { $match = array( 'match' => $match, 'operator' => 'or', 'compare' => 'total', 'case_sensitive' => false ); } else { if (is_int($tag)) { $tag = $match['tag']; } if (!isset($match['match'])) { $match['match'] = true; } if (!isset($match['operator'])) { $match['operator'] = 'or'; } if (!isset($match['compare'])) { $match['compare'] = 'total'; } if (!isset($match['case_sensitive'])) { $match['case_sensitive'] = false; } } if (($match['operator'] === 'and') && (!$res)) { return false; } elseif (!($res && ($match['operator'] === 'or'))) { if ($match['compare'] === 'total') { $a = $this->tag; } elseif ($match['compare'] === 'namespace') { $a = $this->getNamespace(); } elseif ($match['compare'] === 'name') { $a = $this->getTag(); } if ($match['case_sensitive']) { $res = (($a === $tag) === $match['match']); } else { $res = ((strcasecmp($a, $tag) === 0) === $match['match']); } } } return $res; } /** * Checks if attributes match certain conditions * @param array $attributes array('attr' => 'val') or array(array( * 'operator_value' => 'equals'/'='/'contains_regex'/etc * 'attribute' => 'attr', * 'value' => 'val', * 'match' => true, * 'operator_result' => 'or'/'and', * 'compare' => 'total'/'namespace'/'name', * 'case_sensitive' => true)) * @return bool * @internal Used by selector class * @see match() * @access private */ protected function match_attributes($attributes) { $res = false; foreach($attributes as $attribute => $match) { if (!is_array($match)) { $match = array( 'operator_value' => 'equals', 'value' => $match, 'match' => true, 'operator_result' => 'or', 'compare' => 'total', 'case_sensitive' => false ); } else { if (is_int($attribute)) { $attribute = $match['attribute']; } if (!isset($match['match'])) { $match['match'] = true; } if (!isset($match['operator_result'])) { $match['operator_result'] = 'or'; } if (!isset($match['compare'])) { $match['compare'] = 'total'; } if (!isset($match['case_sensitive'])) { $match['case_sensitive'] = false; } } if (is_string($match['value']) && (!$match['case_sensitive'])) { $match['value'] = strtolower($match['value']); } if (($match['operator_result'] === 'and') && (!$res)) { return false; } elseif (!($res && ($match['operator_result'] === 'or'))) { $possibles = $this->findAttribute($attribute, $match['compare'], $match['case_sensitive']); $has = (is_array($possibles) && $possibles); $res = (($match['value'] === $has) || (($match['match'] === false) && ($has === $match['match']))); if ((!$res) && $has && is_string($match['value'])) { foreach($possibles as $a) { $val = $this->attributes[$a[2]]; if (is_string($val) && (!$match['case_sensitive'])) { $val = strtolower($val); } switch($match['operator_value']) { case '%=': case 'contains_regex': $res = ((preg_match('`'.$match['value'].'`s', $val) > 0) === $match['match']); if ($res) break 1; else break 2; case '|=': case 'contains_prefix': $res = ((preg_match('`\b'.preg_quote($match['value']).'[\-\s]`s', $val) > 0) === $match['match']); if ($res) break 1; else break 2; case '~=': case 'contains_word': $res = ((preg_match('`\s'.preg_quote($match['value']).'\s`s', " $val ") > 0) === $match['match']); if ($res) break 1; else break 2; case '*=': case 'contains': $res = ((strpos($val, $match['value']) !== false) === $match['match']); if ($res) break 1; else break 2; case '$=': case 'ends_with': $res = ((substr($val, -strlen($match['value'])) === $match['value']) === $match['match']); if ($res) break 1; else break 2; case '^=': case 'starts_with': $res = ((substr($val, 0, strlen($match['value'])) === $match['value']) === $match['match']); if ($res) break 1; else break 2; case '!=': case 'not_equal': $res = (($val !== $match['value']) === $match['match']); if ($res) break 1; else break 2; case '=': case 'equals': $res = (($val === $match['value']) === $match['match']); if ($res) break 1; else break 2; case '>=': case 'bigger_than': $res = (($val >= $match['value']) === $match['match']); if ($res) break 1; else break 2; case '<=': case 'smaller_than': $res = (($val >= $match['value']) === $match['match']); if ($res) break 1; else break 2; default: trigger_error('Unknown operator "'.$match['operator_value'].'" to match attributes!'); return false; } } } } } return $res; } /** * Checks if node matches certain filters * @param array $tags array(array( * 'filter' => 'last-child', * 'params' => '123')) * @param array $custom_filters Custom map next to {@link $filter_map} * @return bool * @internal Used by selector class * @see match() * @access private */ protected function match_filters($conditions, $custom_filters = array()) { foreach($conditions as $c) { $c['filter'] = strtolower($c['filter']); if (isset($this->filter_map[$c['filter']])) { if (!$this->{$this->filter_map[$c['filter']]}($c['params'])) { return false; } } elseif (isset($custom_filters[$c['filter']])) { if (!call_user_func($custom_filters[$c['filter']], $this, $c['params'])) { return false; } } else { trigger_error('Unknown filter "'.$c['filter'].'"!'); return false; } } return true; } /** * Checks if node matches certain conditions * @param array $tags array('tags' => array(tag_conditions), 'attributes' => array(attr_conditions), 'filters' => array(filter_conditions)) * @param array $match Should conditions evaluate to true? * @param array $custom_filters Custom map next to {@link $filter_map} * @return bool * @internal Used by selector class * @see match_tags(); * @see match_attributes(); * @see match_filters(); * @access private */ function match($conditions, $match = true, $custom_filters = array()) { $t = isset($conditions['tags']); $a = isset($conditions['attributes']); $f = isset($conditions['filters']); if (!($t || $a || $f)) { if (is_array($conditions) && $conditions) { foreach($conditions as $c) { if ($this->match($c, $match)) { return true; } } } return false; } else { if (($t && (!$this->match_tags($conditions['tags']))) === $match) { return false; } if (($a && (!$this->match_attributes($conditions['attributes']))) === $match) { return false; } if (($f && (!$this->match_filters($conditions['filters'], $custom_filters))) === $match) { return false; } return true; } } /** * Finds children that match a certain attribute * @param string $attribute * @param string $value * @param string $mode Compare mode, "equals", "|=", "contains_regex", etc. * @param string $compare "total"/"namespace"/"name" * @param bool|int $recursive * @return array */ function getChildrenByAttribute($attribute, $value, $mode = 'equals', $compare = 'total', $recursive = true) { if ($this->childCount() < 1) { return array(); } $mode = explode(' ', strtolower($mode)); $match = ((isset($mode[1]) && ($mode[1] === 'not')) ? 'false' : 'true'); return $this->getChildrenByMatch( array( 'attributes' => array( $attribute => array( 'operator_value' => $mode[0], 'value' => $value, 'match' => $match, 'compare' => $compare ) ) ), $recursive ); } /** * Finds children that match a certain tag * @param string $tag * @param string $compare "total"/"namespace"/"name" * @param bool|int $recursive * @return array */ function getChildrenByTag($tag, $compare = 'total', $recursive = true) { if ($this->childCount() < 1) { return array(); } $tag = explode(' ', strtolower($tag)); $match = ((isset($tag[1]) && ($tag[1] === 'not')) ? 'false' : 'true'); return $this->getChildrenByMatch( array( 'tags' => array( $tag[0] => array( 'match' => $match, 'compare' => $compare ) ) ), $recursive ); } /** * Finds all children using ID attribute * @param string $id * @param bool|int $recursive * @return array */ function getChildrenByID($id, $recursive = true) { return $this->getChildrenByAttribute('id', $id, 'equals', 'total', $recursive); } /** * Finds all children using class attribute * @param string $class * @param bool|int $recursive * @return array */ function getChildrenByClass($class, $recursive = true) { return $this->getChildrenByAttribute('class', $class, 'equals', 'total', $recursive); } /** * Finds all children using name attribute * @param string $name * @param bool|int $recursive * @return array */ function getChildrenByName($name, $recursive = true) { return $this->getChildrenByAttribute('name', $name, 'equals', 'total', $recursive); } /** * Performs a css query on the node. * @param string $query * @return IQuery Returns the matching nodes from the query. */ public function query($query = '*') { $select = $this->select($query); $result = new \pagelayerQuery((array)$select); return $result; } /** * Performs css query on node * @param string $query * @param int|bool $index True to return node instead of array if only 1 match, * false to return array, int to return match at index, negative int to count from end * @param bool|int $recursive * @param bool $check_self Include this node in search or only search child nodes * @return DomNode[]|DomNode Returns an array of matching {@link DomNode} objects * or a single {@link DomNode} if `$index` is not false. */ function select($query = '*', $index = false, $recursive = true, $check_self = false) { $s = new $this->selectClass($this, $query, $check_self, $recursive); $res = $s->result; unset($s); if (is_array($res) && ($index === true) && (count($res) === 1)) { return $res[0]; } elseif (is_int($index) && is_array($res)) { if ($index < 0) { $index += count($res); } return ($index < count($res)) ? $res[$index] : null; } else { return $res; } } /** * Checks if node matches css query filter ":root" * @return bool * @see match() * @access private */ protected function filter_root() { return (strtolower($this->tag) === 'html'); } /** * Checks if node matches css query filter ":nth-child(n)" * @param string $n 1-based index * @return bool * @see match() * @access private */ protected function filter_nchild($n) { return ($this->index(false)+1 === (int) $n); } /** * Checks if node matches css query filter ":gt(n)" * @param string $n 0-based index * @return bool * @see match() * @access private */ protected function filter_gt($n) { return ($this->index(false) > (int) $n); } /** * Checks if node matches css query filter ":lt(n)" * @param string $n 0-based index * @return bool * @see match() * @access private */ protected function filter_lt($n) { return ($this->index(false) < (int) $n); } /** * Checks if node matches css query filter ":nth-last-child(n)" * @param string $n 1-based index * @return bool * @see match() * @access private */ protected function filter_nlastchild($n) { if ($this->parent === null) { return false; } else { return ($this->parent->childCount(true) - $this->index(false) === (int) $n); } } /** * Checks if node matches css query filter ":nth-of-type(n)" * @param string $n 1-based index * @return bool * @see match() * @access private */ protected function filter_ntype($n) { return ($this->typeIndex()+1 === (int) $n); } /** * Checks if node matches css query filter ":nth-last-of-type(n)" * @param string $n 1-based index * @return bool * @see match() * @access private */ protected function filter_nlastype($n) { if ($this->parent === null) { return false; } else { return (count($this->parent->getChildrenByTag($this->tag, 'total', false)) - $this->typeIndex() === (int) $n); } } /** * Checks if node matches css query filter ":odd" * @return bool * @see match() * @access private */ protected function filter_odd() { return (($this->index(false) & 1) === 1); } /** * Checks if node matches css query filter ":even" * @return bool * @see match() * @access private */ protected function filter_even() { return (($this->index(false) & 1) === 0); } /** * Checks if node matches css query filter ":every(n)" * @return bool * @see match() * @access private */ protected function filter_every($n) { return (($this->index(false) % (int) $n) === 0); } /** * Checks if node matches css query filter ":first" * @return bool * @see match() * @access private */ protected function filter_first() { return ($this->index(false) === 0); } /** * Checks if node matches css query filter ":last" * @return bool * @see match() * @access private */ protected function filter_last() { if ($this->parent === null) { return false; } else { return ($this->parent->childCount(true) - 1 === $this->index(false)); } } /** * Checks if node matches css query filter ":first-of-type" * @return bool * @see match() * @access private */ protected function filter_firsttype() { return ($this->typeIndex() === 0); } /** * Checks if node matches css query filter ":last-of-type" * @return bool * @see match() * @access private */ protected function filter_lasttype() { if ($this->parent === null) { return false; } else { return (count($this->parent->getChildrenByTag($this->tag, 'total', false)) - 1 === $this->typeIndex()); } } /** * Checks if node matches css query filter ":only-child" * @return bool * @see match() * @access private */ protected function filter_onlychild() { if ($this->parent === null) { return false; } else { return ($this->parent->childCount(true) === 1); } } /** * Checks if node matches css query filter ":only-of-type" * @return bool * @see match() * @access private */ protected function filter_onlytype() { if ($this->parent === null) { return false; } else { return (count($this->parent->getChildrenByTag($this->tag, 'total', false)) === 1); } } /** * Checks if node matches css query filter ":empty" * @return bool * @see match() * @access private */ protected function filter_empty() { return ($this->childCount() === 0); } /** * Checks if node matches css query filter ":not-empty" * @return bool * @see match() * @access private */ protected function filter_notempty() { return ($this->childCount() !== 0); } /** * Checks if node matches css query filter ":has-text" * @return bool * @see match() * @access private */ protected function filter_hastext() { return ($this->getPlainText() !== ''); } /** * Checks if node matches css query filter ":no-text" * @return bool * @see match() * @access private */ protected function filter_notext() { return ($this->getPlainText() === ''); } /** * Checks if node matches css query filter ":lang(s)" * @param string $lang * @return bool * @see match() * @access private */ protected function filter_lang($lang) { return ($this->lang === $lang); } /** * Checks if node matches css query filter ":contains(s)" * @param string $text * @return bool * @see match() * @access private */ protected function filter_contains($text) { return (strpos($this->getPlainTextUTF8(), $text) !== false); } /** * Checks if node matches css query filter ":has(s)" * @param string $selector * @return bool * @see match() * @access private */ protected function filter_has($selector) { $s = $this->select((string) $selector, false); return (is_array($s) && (count($s) > 0)); } /** * Checks if node matches css query filter ":not(s)" * @param string $selector * @return bool * @see match() * @access private */ protected function filter_not($selector) { $s = $this->select((string) $selector, false, true, true); return ((!is_array($s)) || (array_search($this, $s, true) === false)); } /** * Checks if node matches css query filter ":element" * @return bool * @see match() * @access private */ protected function filter_element() { return true; } /** * Checks if node matches css query filter ":text" * @return bool * @see match() * @access private */ protected function filter_text() { return false; } /** * Checks if a node matches css query filter ":checked" * @return bool * @see match() */ protected function filter_checked() { $attr = $this->getAttribute('checked'); if (is_array($attr)) $attr = reset($attr); return strcasecmp($attr, 'checked') === 0; } /** * Checks if node matches css query filter ":comment" * @return bool * @see match() * @access private */ protected function filter_comment() { return false; } /** * Checks if a node matches css query filter ":selected" * @return bool * @see match() */ protected function filter_selected() { $attr = $this->getAttribute('selected'); if (is_array($attr)) $attr = reset($attr); return strcasecmp($attr, 'selected') === 0; } public function after($content) { $offset = $this->index() + 1; $parent = $this->parent; $nodes = $this->createNodes($content); foreach ($nodes as $node) { $node->changeParent($parent, $offset); } return $this; } /** * Create a {@link DomNode} from its string representation. * @param string|DomNode $content * @return DomNode */ protected function createNode($content) { $nodes = $this->createNodes($content); return reset($nodes); } /** * Create an array of {@link DomNode} objects from their string representation. * @param string|DomNode $content * @return DomNode[] */ protected function createNodes($content) { if (is_string($content)) { if (strpos($content, ' ') === false) { $nodes = array(new $this->childClass($content, $this)); } else { $node = new $this->parserClass($content); $nodes = $node->root->children; } } else { $nodes = (array)$content; } return $nodes; } public function append($content) { $nodes = $this->createNodes($content); foreach ($nodes as $node) { $node->changeParent($this); } return $this; } public function attr($name, $value = null) { if ($value === null) return $this->getAttribute($name); $this->setAttribute($name, $value); return $this; } public function before($content) { $offset = $this->index(); $parent = $this->parent; $nodes = $this->createNodes($content); foreach ($nodes as $node) { $node->changeParent($parent, $offset); } return $this; } #[\ReturnTypeWillChange] public function count() { return 1; } // public function css($name, $value = null) { // // } public function prepend($content = null) { $offset = 0; $parent = $this; $nodes = $this->createNodes($content); foreach ($nodes as $node) { $node->changeParent($parent, $offset); } return $this; } public function prop($name, $value = null) { switch (strtolower($name)) { case 'checked': case 'disabled': case 'selected': if ($value !== null) { if ($value) { $this->attr($name, $name); } else { $this->removeAttr($name); } return $this; } return $this->attr($name) == $name; case 'tagname': return $this->tagName($value); } // The property is not supported, degrade gracefully if ($value === null) return $this; else return null; } public function remove($selector = null) { if ($selector == null) { $this->delete(); } else { $nodes = (array)$this->select($selector); foreach ($nodes as $node) { $node->delete(); } } } public function removeAttr($name) { $this->deleteAttribute($name); return $this; } function replaceWith($content) { $node_index = $this->index(); // Add the new node. $node = $this->createNode($content); $node->changeParent($this->parent, $node_index); // Remove this node. $this->remove(); return $node; } /** * @param type $value * @return string|DomNode */ public function tagName($value = null) { if ($value !== null) { $this->setTag($value); return $this; } return $this->getTag(); } public function text($value = null) { if ($value === null) return $this->getPlainText(); $this->setPlainText($value); return $this; } public function toggleClass($classname, $switch = null) { if ($switch === true) { $this->addClass($classname); } elseif ($switch === false) { $this->removeClass($classname); } else { if ($this->hasClass($classname)) $this->removeClass($classname); else $this->addClass($classname); } return $this; } public function unwrap() { $this->parent->detach(true); return $this; } public function val($value = null) { switch (strtolower($this->tag)) { case 'select': if ($value === null) { // Return the value of a selected child. return $this->query('option:selected')->attr('value'); } else { // Select the option with the right value and deselect the others. foreach ($this->query('option') as $option) { if ($option->attr('value') == $value) { $option->attr('selected', 'selected'); } else { $option->removeAttr('selected'); } } return $this; } case 'textarea': if ($value === null) { // Return the contents of the textarea. return $this->getInnerText(); } else { // Set the contents of the textarea. $this->setInnerText($value); return $this; } case 'input': switch (strtolower($this->getAttribute('type'))) { case 'checkbox': if ($value === null) return $this->prop('checked') ? $this->getAttribute('value') : null; else { if (!$value) { $this->deleteAttribute('checked'); } else { $this->setAttribute('value', $value); $this->setAttribute('checked', 'checked'); } return $this; } } } // Other node types can just get/set the value attribute. if ($value !== null) { $this->setAttribute('value', $value); return $this; } return $this->getAttribute('value'); } } /** * Node subclass for text */ class TextNode extends DomNode { #php4 Compatibility with PHP4, this gets changed to a regular var in release tool #static $NODE_TYPE = self::NODE_TEXT; #php4e #php5 const NODE_TYPE = self::NODE_TEXT; #php5e var $tag = '~text~'; /** * @var string */ var $text = ''; /** * Class constructor * @param DomNode $parent * @param string $text */ function __construct($parent, $text = '') { $this->parent = $parent; $this->text = $text; } #php4 PHP4 class constructor compatibility #function TextNode($parent, $text = '') {return $this->__construct($parent, $text);} #php4e function isText() {return true;} function isTextOrComment() {return true;} protected function filter_element() {return false;} protected function filter_text() {return true;} function toString_attributes() {return '';} function toString_content($attributes = true, $recursive = true, $content_only = false) {return $this->text;} function toString($attributes = true, $recursive = true, $content_only = false) {return $this->text;} /** * {@inheritdoc} */ public function text($value = null) { if ($value !== null) { $this->text = $value; return $this; } return $this->text; } /** * {@inheritdoc} */ public function html($value = null) { if ($value !== null) { $this->text = $value; return $this; } return $this->text; } } /** * Node subclass for comments */ class CommentNode extends DomNode { #php4 Compatibility with PHP4, this gets changed to a regular var in release tool #static $NODE_TYPE = self::NODE_COMMENT; #php4e #php5 const NODE_TYPE = self::NODE_COMMENT; #php5e var $tag = '~comment~'; /** * @var string */ var $text = ''; /** * Class constructor * @param DomNode $parent * @param string $text */ function __construct($parent, $text = '') { $this->parent = $parent; $this->text = $text; } #php4 PHP4 class constructor compatibility #function CommentNode($parent, $text = '') {return $this->__construct($parent, $text);} #php4e function isComment() {return true;} function isTextOrComment() {return true;} protected function filter_element() {return false;} protected function filter_comment() {return true;} function toString_attributes() {return '';} function toString_content($attributes = true, $recursive = true, $content_only = false) {return $this->text;} function toString($attributes = true, $recursive = true, $content_only = false) {return '<!--'.$this->text.'-->';} } /** * Node subclass for conditional tags */ class ConditionalTagNode extends DomNode { #php4 Compatibility with PHP4, this gets changed to a regular var in release tool #static $NODE_TYPE = self::NODE_CONDITIONAL; #php4e #php5 const NODE_TYPE = self::NODE_CONDITIONAL; #php5e var $tag = '~conditional~'; /** * @var string */ var $condition = ''; /** * Class constructor * @param DomNode $parent * @param string $condition e.g. "if IE" * @param bool $hidden <!--[if if true, <![if if false */ function __construct($parent, $condition = '', $hidden = true) { $this->parent = $parent; $this->hidden = $hidden; $this->condition = $condition; } #php4 PHP4 class constructor compatibility #function ConditionalTagNode($parent, $condition = '', $hidden = true) {return $this->__construct($parent, $condition, $hidden);} #php4e protected function filter_element() {return false;} function toString_attributes() {return '';} function toString($attributes = true, $recursive = true, $content_only = false) { if ($content_only) { if (is_int($content_only)) { --$content_only; } return $this->toString_content($attributes, $recursive, $content_only); } $s = '<!'.(($this->hidden) ? '--' : '').'['.$this->condition.']>'; if($recursive) { $s .= $this->toString_content($attributes); } $s .= '<![endif]'.(($this->hidden) ? '--' : '').'>'; return $s; } } /** * Node subclass for CDATA tags */ class CdataNode extends DomNode { #php4 Compatibility with PHP4, this gets changed to a regular var in release tool #static $NODE_TYPE = self::NODE_CDATA; #php4e #php5 const NODE_TYPE = self::NODE_CDATA; #php5e var $tag = '~cdata~'; /** * @var string */ var $text = ''; /** * Class constructor * @param DomNode $parent * @param string $text */ function __construct($parent, $text = '') { $this->parent = $parent; $this->text = $text; } #php4 PHP4 class constructor compatibility #function CdataNode($parent, $text = '') {return $this->__construct($parent, $text);} #php4e protected function filter_element() {return false;} function toString_attributes() {return '';} function toString_content($attributes = true, $recursive = true, $content_only = false) {return $this->text;} function toString($attributes = true, $recursive = true, $content_only = false) {return '<![CDATA['.$this->text.']]>';} } /** * Node subclass for doctype tags */ class DoctypeNode extends DomNode { #php4 Compatibility with PHP4, this gets changed to a regular var in release tool #static $NODE_TYPE = self::NODE_DOCTYPE; #php4e #php5 const NODE_TYPE = self::NODE_DOCTYPE; #php5e var $tag = '!DOCTYPE'; /** * @var string */ var $dtd = ''; /** * Class constructor * @param DomNode $parent * @param string $dtd */ function __construct($parent, $dtd = '') { $this->parent = $parent; $this->dtd = $dtd; } #php4 PHP4 class constructor compatibility #function DoctypeNode($parent, $dtd = '') {return $this->__construct($parent, $dtd);} #php4e protected function filter_element() {return false;} function toString_attributes() {return '';} function toString_content($attributes = true, $recursive = true, $content_only = false) {return $this->text;} function toString($attributes = true, $recursive = true, $content_only = false) {return '<'.$this->tag.' '.$this->dtd.'>';} } /** * Node subclass for embedded tags like xml, php and asp */ class EmbeddedNode extends DomNode { /** * @var string * @internal specific char for tags, like ? for php and % for asp * @access private */ var $tag_char = ''; /** * @var string */ var $text = ''; /** * Class constructor * @param DomNode $parent * @param string $tag_char {@link $tag_char} * @param string $tag {@link $tag} * @param string $text * @param array $attributes array('attr' => 'val') */ function __construct($parent, $tag_char = '', $tag = '', $text = '', $attributes = array()) { $this->parent = $parent; $this->tag_char = $tag_char; if ($tag[0] !== $this->tag_char) { $tag = $this->tag_char.$tag; } $this->tag = $tag; $this->text = $text; $this->attributes = $attributes; $this->self_close_str = $tag_char; } #php4 PHP4 class constructor compatibility #function EmbeddedNode($parent, $tag_char = '', $tag = '', $text = '', $attributes = array()) {return $this->__construct($parent, $tag_char, $tag, $text, $attributes);} #php4e protected function filter_element() {return false;} function toString($attributes = true, $recursive = true, $content_only = false) { $s = '<'.$this->tag; if ($attributes) { $s .= $this->toString_attributes(); } $s .= $this->text.$this->self_close_str.'>'; return $s; } } /** * Node subclass for "?" tags, like php and xml */ class XmlNode extends EmbeddedNode { #php4 Compatibility with PHP4, this gets changed to a regular var in release tool #static $NODE_TYPE = self::NODE_XML; #php4e #php5 const NODE_TYPE = self::NODE_XML; #php5e /** * Class constructor * @param DomNode $parent * @param string $tag {@link $tag} * @param string $text * @param array $attributes array('attr' => 'val') */ function __construct($parent, $tag = 'xml', $text = '', $attributes = array()) { return parent::__construct($parent, '?', $tag, $text, $attributes); } #php4 PHP4 class constructor compatibility #function XmlNode($parent, $tag = 'xml', $text = '', $attributes = array()) {return $this->__construct($parent, $tag, $text, $attributes);} #php4e } /** * Node subclass for asp tags */ class AspEmbeddedNode extends EmbeddedNode { #php4 Compatibility with PHP4, this gets changed to a regular var in release tool #static $NODE_TYPE = self::NODE_ASP; #php4e #php5 const NODE_TYPE = self::NODE_ASP; #php5e /** * Class constructor * @param DomNode $parent * @param string $tag {@link $tag} * @param string $text * @param array $attributes array('attr' => 'val') */ function __construct($parent, $tag = '', $text = '', $attributes = array()) { return parent::__construct($parent, '%', $tag, $text, $attributes); } #php4 PHP4 class constructor compatibility #function AspEmbeddedNode($parent, $tag = '', $text = '', $attributes = array()) {return $this->__construct($parent, $tag, $text, $attributes);} #php4e } ?>