Fixed Broken Shit

This commit is contained in:
2022-10-30 14:32:20 -07:00
parent 7335796263
commit 4dabf5a6bf
635 changed files with 74885 additions and 17688 deletions

100
includes/vendor/aura/sql/README.md vendored Normal file
View File

@ -0,0 +1,100 @@
# Aura.Sql
Provides an extension to the native [PDO](http://php.net/PDO) along with a
profiler and connection locator. Because _ExtendedPdo_ is an extension of the
native _PDO_, code already using the native _PDO_ or typehinted to the native
_PDO_ can use _ExtendedPdo_ without any changes.
Added functionality in _Aura.Sql_ over the native _PDO_ includes:
- **Lazy connection.** _ExtendedPdo_ connects to the database only on
method calls that require a connection. This means you can create an
instance and not incur the cost of a connection if you never make a query.
- **Decoration.** _DecoratedPdo_ can be used to decorate an existing PDO
instance. This means that a PDO instance can be "extended" **at runtime** to
provide the _ExtendedPdo_ behaviors.
- **Array quoting.** The `quote()` method will accept an array as input, and
return a string of comma-separated quoted values.
- **New `perform()` method.** The `perform()` method acts just like `query()`,
but binds values to a prepared statement as part of the call. In addition,
placeholders that represent array values will be replaced with comma-
separated quoted values. This means you can bind an array of values to a
placeholder used with an `IN (...)` condition when using `perform()`.
- **New `fetch*()` methods.** The new `fetch*()` methods provide for
commonly-used fetch actions. For example, you can call `fetchAll()` directly
on the instance instead of having to prepare a statement, bind values,
execute, and then fetch from the prepared statement. All of the `fetch*()`
methods take an array of values to bind to to the query statement, and use
the new `perform()` method internally.
- **New `yield*()` methods.** These are complements to the `fetch*()` methods
that `yield` results instead of `return`ing them.
- **Exceptions by default.** _ExtendedPdo_ starts in the `ERRMODE_EXCEPTION`
mode for error reporting instead of the `ERRMODE_SILENT` mode.
- **Profiler.** An optional query profiler is provided, along with an
interface for other implementations, that logs to any PSR-3 interface.
- **Connection locator.** A optional lazy-loading service locator is provided
for picking different database connections (default, read, and write).
## Installation and Autoloading
This package is installable and PSR-4 autoloadable via Composer as
[aura/sql][].
Alternatively, [download a release][], or clone this repository, then map the
`Aura\Sql\` namespace to the package `src/` directory.
## Dependencies
This package requires PHP 5.6 or later; it has also been tested on PHP 7 and
HHVM. We recommend using the latest available version of PHP as a matter of
principle.
Aura library packages may sometimes depend on external interfaces, but never on
external implementations. This allows compliance with community standards
without compromising flexibility. For specifics, please examine the package
[composer.json][] file.
## Quality
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/auraphp/Aura.Sql/badges/quality-score.png?b=3.x)](https://scrutinizer-ci.com/g/auraphp/Aura.Sql/)
[![Code Coverage](https://scrutinizer-ci.com/g/auraphp/Aura.Sql/badges/coverage.png?b=3.x)](https://scrutinizer-ci.com/g/auraphp/Aura.Sql/)
[![Build Status](https://travis-ci.org/auraphp/Aura.Sql.png?branch=3.x)](https://travis-ci.org/auraphp/Aura.Sql)
[![PDS Skeleton](https://img.shields.io/badge/pds-skeleton-blue.svg?style=flat-square)](https://github.com/php-pds/skeleton)
This project adheres to [Semantic Versioning](http://semver.org/).
To run the unit tests at the command line, issue `composer install` and then
`./vendor/bin/phpunit` at the package root. (This requires [Composer][] to be
available as `composer`.)
This package attempts to comply with [PSR-1][], [PSR-2][], and [PSR-4][]. If
you notice compliance oversights, please send a patch via pull request.
## Community
To ask questions, provide feedback, or otherwise communicate with other Aura
users, please join our [Google Group][], follow [@auraphp][], or chat with us
on Freenode in the #auraphp channel.
## Documentation
This package is fully documented [here](./docs/index.md).
[PSR-1]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md
[PSR-2]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md
[PSR-4]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md
[Composer]: http://getcomposer.org/
[Google Group]: http://groups.google.com/group/auraphp
[@auraphp]: http://twitter.com/auraphp
[download a release]: https://github.com/auraphp/Aura.Sql/releases
[aura/sql]: https://packagist.org/packages/aura/sql
[composer.json]: ./composer.json

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,203 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql;
/**
*
* Manages ExtendedPdo instances for default, read, and write connections.
*
* @package Aura.Sql
*
*/
class ConnectionLocator implements ConnectionLocatorInterface
{
/**
*
* A default ExtendedPdo connection factory/instance.
*
* @var callable
*
*/
protected $default;
/**
*
* A registry of ExtendedPdo "read" factories/instances.
*
* @var array
*
*/
protected $read = [];
/**
*
* A registry of ExtendedPdo "write" factories/instances.
*
* @var array
*
*/
protected $write = [];
/**
*
* Constructor.
*
* @param callable $default A callable to create a default connection.
*
* @param array $read An array of callables to create read connections.
*
* @param array $write An array of callables to create write connections.
*
*/
public function __construct(
$default = null,
array $read = [],
array $write = []
) {
if ($default) {
$this->setDefault($default);
}
foreach ($read as $name => $callable) {
$this->setRead($name, $callable);
}
foreach ($write as $name => $callable) {
$this->setWrite($name, $callable);
}
}
/**
*
* Sets the default connection factory.
*
* @param callable $callable The factory for the connection.
*
* @return null
*
*/
public function setDefault(callable $callable)
{
$this->default = $callable;
}
/**
*
* Returns the default connection object.
*
* @return ExtendedPdoInterface
*
*/
public function getDefault()
{
if (! $this->default instanceof ExtendedPdo) {
$this->default = call_user_func($this->default);
}
return $this->default;
}
/**
*
* Sets a read connection factory by name.
*
* @param string $name The name of the connection.
*
* @param callable $callable The factory for the connection.
*
* @return null
*
*/
public function setRead($name, callable $callable)
{
$this->read[$name] = $callable;
}
/**
*
* Returns a read connection by name; if no name is given, picks a
* random connection; if no read connections are present, returns the
* default connection.
*
* @param string $name The read connection name to return.
*
* @return ExtendedPdoInterface
*
*/
public function getRead($name = '')
{
return $this->getConnection('read', $name);
}
/**
*
* Sets a write connection factory by name.
*
* @param string $name The name of the connection.
*
* @param callable $callable The factory for the connection.
*
* @return null
*
*/
public function setWrite($name, callable $callable)
{
$this->write[$name] = $callable;
}
/**
*
* Returns a write connection by name; if no name is given, picks a
* random connection; if no write connections are present, returns the
* default connection.
*
* @param string $name The write connection name to return.
*
* @return ExtendedPdoInterface
*
*/
public function getWrite($name = '')
{
return $this->getConnection('write', $name);
}
/**
*
* Returns a connection by name.
*
* @param string $type The connection type ('read' or 'write').
*
* @param string $name The name of the connection.
*
* @return ExtendedPdoInterface
*
* @throws Exception\ConnectionNotFound
*
*/
protected function getConnection($type, $name)
{
$conn = &$this->{$type};
if (empty($conn)) {
return $this->getDefault();
}
if ($name === '') {
$name = array_rand($conn);
}
if (! isset($conn[$name])) {
throw new Exception\ConnectionNotFound("{$type}:{$name}");
}
if (! $conn[$name] instanceof ExtendedPdo) {
$conn[$name] = call_user_func($conn[$name]);
}
return $conn[$name];
}
}

View File

@ -0,0 +1,91 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql;
/**
*
* Locates PDO connections for default, read, and write databases.
*
* @package Aura.Sql
*
*/
interface ConnectionLocatorInterface
{
/**
*
* Sets the default connection registry entry.
*
* @param callable $callable The registry entry.
*
* @return null
*
*/
public function setDefault(callable $callable);
/**
*
* Returns the default connection object.
*
* @return ExtendedPdoInterface
*
*/
public function getDefault();
/**
*
* Sets a read connection registry entry by name.
*
* @param string $name The name of the registry entry.
*
* @param callable $callable The registry entry.
*
* @return null
*
*/
public function setRead($name, callable $callable);
/**
*
* Returns a read connection by name; if no name is given, picks a
* random connection; if no read connections are present, returns the
* default connection.
*
* @param string $name The read connection name to return.
*
* @return ExtendedPdoInterface
*
*/
public function getRead($name = '');
/**
*
* Sets a write connection registry entry by name.
*
* @param string $name The name of the registry entry.
*
* @param callable $callable The registry entry.
*
* @return null
*
*/
public function setWrite($name, callable $callable);
/**
*
* Returns a write connection by name; if no name is given, picks a
* random connection; if no write connections are present, returns the
* default connection.
*
* @param string $name The write connection name to return.
*
* @return ExtendedPdoInterface
*
*/
public function getWrite($name = '');
}

View File

@ -0,0 +1,74 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql;
use Aura\Sql\Profiler\Profiler;
use Aura\Sql\Profiler\ProfilerInterface;
use PDO;
/**
*
* Decorates an existing PDO instance with the extended methods.
*
* @package Aura.Sql
*
*/
class DecoratedPdo extends AbstractExtendedPdo
{
/**
*
* Constructor.
*
* This overrides the parent so that it can take an existing PDO instance
* and decorate it with the extended methods.
*
* @param PDO $pdo An existing PDO instance to decorate.
*
* @param ProfilerInterface $profiler Tracks and logs query profiles.
*
*/
public function __construct(PDO $pdo, ProfilerInterface $profiler = null)
{
$this->pdo = $pdo;
if ($profiler === null) {
$profiler = new Profiler();
}
$this->setProfiler($profiler);
$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
$this->setParser($this->newParser($driver));
$this->setQuoteName($driver);
}
/**
*
* Connects to the database.
*
* @return null
*
*/
public function connect()
{
// already connected
}
/**
*
* Disconnects from the database; disallowed with decorated PDO connections.
*
* @return null
*
*/
public function disconnect()
{
$message = "Cannot disconnect a DecoratedPdo instance.";
throw new Exception\CannotDisconnect($message);
}
}

View File

@ -0,0 +1,20 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql;
/**
*
* Base Exception class for Aura Sql
*
* @package Aura.Sql
*
*/
class Exception extends \Exception
{
}

View File

@ -0,0 +1,23 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql\Exception;
use Aura\Sql\Exception;
/**
*
* Could not bind a value to a placeholder in a statement, generally because
* the value is an array, object, or resource.
*
* @package Aura.Sql
*
*/
class CannotBindValue extends Exception
{
}

View File

@ -0,0 +1,23 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql\Exception;
use Aura\Sql\Exception;
/**
*
* ExtendedPdo could not disconnect; e.g., because its PDO connection was
* created externally and then injected.
*
* @package Aura.Sql
*
*/
class CannotDisconnect extends Exception
{
}

View File

@ -0,0 +1,22 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql\Exception;
use Aura\Sql\Exception;
/**
*
* Locator could not find a named connection.
*
* @package Aura.Sql
*
*/
class ConnectionNotFound extends Exception
{
}

View File

@ -0,0 +1,22 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql\Exception;
use Aura\Sql\Exception;
/**
*
* Missing a parameter in the values bound to a statement
*
* @package Aura.Sql
*
*/
class MissingParameter extends Exception
{
}

View File

@ -0,0 +1,163 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql;
use Aura\Sql\Profiler\Profiler;
use Aura\Sql\Profiler\ProfilerInterface;
use PDO;
/**
*
* A lazy-connecting PDO with extended methods.
*
* @package Aura.Sql
*
*/
class ExtendedPdo extends AbstractExtendedPdo
{
/**
*
* Constructor arguments for instantiating the PDO connection.
*
* @var array
*
*/
protected $args = [];
/**
*
* Constructor.
*
* This overrides the parent so that it can take connection attributes as a
* constructor parameter, and set them after connection.
*
* @param string $dsn The data source name for the connection.
*
* @param string $username The username for the connection.
*
* @param string $password The password for the connection.
*
* @param array $options Driver-specific options for the connection.
*
* @param array $queries Queries to execute after the connection.
*
* @param ProfilerInterface $profiler Tracks and logs query profiles.
*
* @see http://php.net/manual/en/pdo.construct.php
*
*/
public function __construct(
$dsn,
$username = null,
$password = null,
array $options = [],
array $queries = [],
ProfilerInterface $profiler = null
) {
// if no error mode is specified, use exceptions
if (! isset($options[PDO::ATTR_ERRMODE])) {
$options[PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION;
}
// retain the arguments for later
$this->args = [
$dsn,
$username,
$password,
$options,
$queries
];
// retain a profiler, instantiating a default one if needed
if ($profiler === null) {
$profiler = new Profiler();
}
$this->setProfiler($profiler);
// retain a query parser
$parts = explode(':', $dsn);
$parser = $this->newParser($parts[0]);
$this->setParser($parser);
// set quotes for identifier names
$this->setQuoteName($parts[0]);
}
/**
*
* Connects to the database.
*
* @return null
*
*/
public function connect()
{
if ($this->pdo) {
return;
}
// connect
$this->profiler->start(__FUNCTION__);
list($dsn, $username, $password, $options, $queries) = $this->args;
$this->pdo = new PDO($dsn, $username, $password, $options);
$this->profiler->finish();
// connection-time queries
foreach ($queries as $query) {
$this->exec($query);
}
}
/**
*
* Disconnects from the database.
*
* @return null
*
*/
public function disconnect()
{
$this->profiler->start(__FUNCTION__);
$this->pdo = null;
$this->profiler->finish();
}
/**
*
* The purpose of this method is to hide sensitive data from stack traces.
*
* @return array
*
*/
public function __debugInfo()
{
return [
'args' => [
$this->args[0],
'****',
'****',
$this->args[3],
$this->args[4],
]
];
}
/**
*
* Return the inner PDO (if any)
*
* @return \PDO
*
*/
public function getPdo()
{
$this->connect();
return $this->pdo;
}
}

View File

@ -0,0 +1,412 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql;
use Aura\Sql\Parser\ParserInterface;
use Aura\Sql\Profiler\ProfilerInterface;
use PDO;
/**
*
* An interface to the Aura.Sql extended PDO object.
*
* @package Aura.Sql
*
*/
interface ExtendedPdoInterface extends PdoInterface
{
/**
*
* Connects to the database.
*
*/
public function connect();
/**
*
* Disconnects from the database.
*
*/
public function disconnect();
/**
*
* Performs a statement and returns the number of affected rows.
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @return int
*
*/
public function fetchAffected($statement, array $values = []);
/**
*
* Fetches a sequential array of rows from the database; the rows
* are represented as associative arrays.
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @return array
*
*/
public function fetchAll($statement, array $values = []);
/**
*
* Fetches an associative array of rows from the database; the rows
* are represented as associative arrays. The array of rows is keyed
* on the first column of each row.
*
* N.b.: if multiple rows have the same first column value, the last
* row with that value will override earlier rows.
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @return array
*
*/
public function fetchAssoc($statement, array $values = []);
/**
*
* Fetches the first column of rows as a sequential array.
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @return array
*
*/
public function fetchCol($statement, array $values = []);
/**
*
* Fetches multiple from the database as an associative array.
* The first column will be the index
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @param int $style a fetch style defaults to PDO::FETCH_COLUMN for single
* values, use PDO::FETCH_NAMED when fetching a multiple columns
*
* @return array
*
*/
public function fetchGroup(
$statement,
array $values = [],
$style = PDO::FETCH_COLUMN
);
/**
*
* Fetches one row from the database as an object, mapping column values
* to object properties.
*
* Warning: PDO "injects property-values BEFORE invoking the constructor -
* in other words, if your class initializes property-values to defaults
* in the constructor, you will be overwriting the values injected by
* fetchObject() !"
* <http://www.php.net/manual/en/pdostatement.fetchobject.php#111744>
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @param string $class The name of the class to create.
*
* @param array $args Arguments to pass to the object constructor.
*
* @return object
*
*/
public function fetchObject(
$statement,
array $values = [],
$class = 'stdClass',
array $args = []
);
/**
*
* Fetches a sequential array of rows from the database; the rows
* are represented as objects, where the column values are mapped to
* object properties.
*
* Warning: PDO "injects property-values BEFORE invoking the constructor -
* in other words, if your class initializes property-values to defaults
* in the constructor, you will be overwriting the values injected by
* fetchObject() !"
* <http://www.php.net/manual/en/pdostatement.fetchobject.php#111744>
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @param string $class The name of the class to create from each
* row.
*
* @param array $args Arguments to pass to each object constructor.
*
* @return array
*
*/
public function fetchObjects(
$statement,
array $values = [],
$class = 'stdClass',
array $args = []
);
/**
*
* Fetches one row from the database as an associative array.
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @return array
*
*/
public function fetchOne($statement, array $values = []);
/**
*
* Fetches an associative array of rows as key-value pairs (first
* column is the key, second column is the value).
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @return array
*
*/
public function fetchPairs($statement, array $values = []);
/**
*
* Fetches the very first value (i.e., first column of the first row).
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @return mixed
*
*/
public function fetchValue($statement, array $values = []);
/**
*
* Returns the Parser instance.
*
* @return ParserInterface
*
*/
public function getParser();
/**
*
* Return the inner PDO (if any)
*
* @return \PDO
*
*/
public function getPdo();
/**
*
* Returns the Profiler instance.
*
* @return ProfilerInterface
*
*/
public function getProfiler();
/**
*
* Quotes a multi-part (dotted) identifier name.
*
* @param string $name The multi-part identifier name.
*
* @return string The multi-part identifier name, quoted.
*
*/
public function quoteName($name);
/**
*
* Quotes a single identifier name.
*
* @param string $name The identifier name.
*
* @return string The quoted identifier name.
*
*/
public function quoteSingleName($name);
/**
*
* Is the PDO connection active?
*
* @return bool
*
*/
public function isConnected();
/**
*
* Sets the Parser instance.
*
* @param ParserInterface $parser The Parser instance.
*
*/
public function setParser(ParserInterface $parser);
/**
*
* Sets the Profiler instance.
*
* @param ProfilerInterface $profiler The Profiler instance.
*
*/
public function setProfiler(ProfilerInterface $profiler);
/**
*
* Yields rows from the database
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @return \Generator
*
*/
public function yieldAll($statement, array $values = []);
/**
*
* Yields rows from the database keyed on the first column of each row.
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @return \Generator
*
*/
public function yieldAssoc($statement, array $values = []);
/**
*
* Yields the first column of all rows
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @return \Generator
*
*/
public function yieldCol($statement, array $values = []);
/**
*
* Yields objects where the column values are mapped to object properties.
*
* Warning: PDO "injects property-values BEFORE invoking the constructor -
* in other words, if your class initializes property-values to defaults
* in the constructor, you will be overwriting the values injected by
* fetchObject() !"
* <http://www.php.net/manual/en/pdostatement.fetchobject.php#111744>
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @param string $class The name of the class to create from each
* row.
*
* @param array $args Arguments to pass to each object constructor.
*
* @return \Generator
*
*/
public function yieldObjects(
$statement,
array $values = [],
$class = 'stdClass',
array $args = []
);
/**
*
* Yields key-value pairs (first column is the key, second column is the
* value).
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @return \Generator
*
*/
public function yieldPairs($statement, array $values = []);
/**
*
* Performs a query after preparing the statement with bound values, then
* returns the result as a PDOStatement.
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param array $values Values to bind to the query.
*
* @return \PDOStatement
*
*/
public function perform($statement, array $values = []);
/**
*
* Prepares an SQL statement with bound values.
*
* This method only binds values that have placeholders in the
* statement, thereby avoiding errors from PDO regarding too many bound
* values. It also binds all sequential (question-mark) placeholders.
*
* If a placeholder value is an array, the array is converted to a string
* of comma-separated quoted values; e.g., for an `IN (...)` condition.
* The quoted string is replaced directly into the statement instead of
* using `PDOStatement::bindValue()` proper.
*
* @param string $statement The SQL statement to prepare for execution.
*
* @param array $values The values to bind to the statement, if any.
*
* @return \PDOStatement
*
* @see http://php.net/manual/en/pdo.prepare.php
*
*/
public function prepareWithValues($statement, array $values = []);
}

View File

@ -0,0 +1,318 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql\Parser;
use Aura\Sql\Exception\MissingParameter;
/**
*
* Parsing/rebuilding functionality for all drivers.
*
* Note that this does not validate the syntax; it only replaces/rebuilds
* placeholders in the query.
*
* @package Aura.Sql
*
*/
abstract class AbstractParser implements ParserInterface
{
/**
*
* Split the query string on these regexes.
*
* @var array
*
*/
protected $split = [
// single-quoted string
"'(?:[^'\\\\]|\\\\'?)*'",
// double-quoted string
'"(?:[^"\\\\]|\\\\"?)*"',
];
/**
*
* Skip query parts matching this regex.
*
* @var string
*
*/
protected $skip = '/^(\'|\"|\:[^a-zA-Z_])/um';
/**
*
* The current numbered-placeholder in the original statement.
*
* @var int
*
*/
protected $num = 0;
/**
*
* How many times has a named placeholder been used?
*
* @var array
*
*/
protected $count = [
'__' => null,
];
/**
*
* The initial values to be bound.
*
* @var array
*
*/
protected $values = [];
/**
*
* Final placeholders and values to bind.
*
* @var array
*
*/
protected $final_values = [];
/**
*
* Rebuilds a statement with placeholders and bound values.
*
* @param string $statement The statement to rebuild.
*
* @param array $values The values to bind and/or replace into a statement.
*
* @return array An array where element 0 is the rebuilt statement and
* element 1 is the rebuilt array of values.
*
*/
public function rebuild($statement, array $values = [])
{
// match standard PDO execute() behavior of zero-indexed arrays
if (array_key_exists(0, $values)) {
array_unshift($values, null);
}
$this->values = $values;
$statement = $this->rebuildStatement($statement);
return [$statement, $this->final_values];
}
/**
*
* Given a statement, rebuilds it with array values embedded.
*
* @param string $statement The SQL statement.
*
* @return string The rebuilt statement.
*
*/
protected function rebuildStatement($statement)
{
$parts = $this->getParts($statement);
return $this->rebuildParts($parts);
}
/**
*
* Given an array of statement parts, rebuilds each part.
*
* @param array $parts The statement parts.
*
* @return string The rebuilt statement.
*
*/
protected function rebuildParts(array $parts)
{
$statement = '';
foreach ($parts as $part) {
$statement .= $this->rebuildPart($part);
}
return $statement;
}
/**
*
* Rebuilds a single statement part.
*
* @param string $part The statement part.
*
* @return string The rebuilt statement.
*
*/
protected function rebuildPart($part)
{
if (preg_match($this->skip, $part)) {
return $part;
}
// split into subparts by ":name" and "?"
$subs = preg_split(
"/(?<!:)(:[a-zA-Z_][a-zA-Z0-9_]*)|(\?)/um",
$part,
-1,
PREG_SPLIT_DELIM_CAPTURE
);
// check subparts to expand placeholders for bound arrays
return $this->prepareValuePlaceholders($subs);
}
/**
*
* Prepares the sub-parts of a query with placeholders.
*
* @param array $subs The query subparts.
*
* @return string The prepared subparts.
*
*/
protected function prepareValuePlaceholders(array $subs)
{
$str = '';
foreach ($subs as $i => $sub) {
$char = substr($sub, 0, 1);
if ($char == '?') {
$str .= $this->prepareNumberedPlaceholder();
} elseif ($char == ':') {
$str .= $this->prepareNamedPlaceholder($sub);
} else {
$str .= $sub;
}
}
return $str;
}
/**
*
* Bind or quote a numbered placeholder in a query subpart.
*
* @return string The prepared query subpart.
*
* @throws MissingParameter
*/
protected function prepareNumberedPlaceholder()
{
$this->num ++;
if (array_key_exists($this->num, $this->values) === false) {
throw new MissingParameter("Parameter {$this->num} is missing from the bound values");
}
$expanded = [];
$values = (array) $this->values[$this->num];
if (is_null($this->values[$this->num])) {
$values[] = null;
}
foreach ($values as $value) {
$count = ++ $this->count['__'];
$name = "__{$count}";
$expanded[] = ":{$name}";
$this->final_values[$name] = $value;
}
return implode(', ', $expanded);
}
/**
*
* Bind or quote a named placeholder in a query subpart.
*
* @param string $sub The query subpart.
*
* @return string The prepared query subpart.
*
*/
protected function prepareNamedPlaceholder($sub)
{
$orig = substr($sub, 1);
if (array_key_exists($orig, $this->values) === false) {
throw new MissingParameter("Parameter '{$orig}' is missing from the bound values");
}
$name = $this->getPlaceholderName($orig);
// is the corresponding data element an array?
$bind_array = is_array($this->values[$orig]);
if ($bind_array) {
// expand to multiple placeholders
return $this->expandNamedPlaceholder($name, $this->values[$orig]);
}
// not an array, retain the placeholder for later
$this->final_values[$name] = $this->values[$orig];
return ":$name";
}
/**
*
* Given an original placeholder name, return a replacement name.
*
* @param string $orig The original placeholder name.
*
* @return string
*
*/
protected function getPlaceholderName($orig)
{
if (! isset($this->count[$orig])) {
$this->count[$orig] = 0;
return $orig;
}
$count = ++ $this->count[$orig];
return "{$orig}__{$count}";
}
/**
*
* Given a named placeholder for an array, expand it for the array values,
* and bind those values to the expanded names.
*
* @param string $prefix The named placeholder.
*
* @param array $values The array values to be bound.
*
* @return string
*
*/
protected function expandNamedPlaceholder($prefix, array $values)
{
$i = 0;
$expanded = [];
foreach ($values as $value) {
$name = "{$prefix}_{$i}";
$expanded[] = ":{$name}";
$this->final_values[$name] = $value;
$i ++;
}
return implode(', ', $expanded);
}
/**
*
* Given a query string, split it into parts.
*
* @param string $statement The query string.
*
* @return array
*
*/
protected function getParts($statement)
{
$split = implode('|', $this->split);
return preg_split(
"/($split)/um",
$statement,
-1,
PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY
);
}
}

View File

@ -0,0 +1,44 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql\Parser;
/**
*
* Parsing/rebuilding functionality for the mysql driver.
*
* @package Aura.Sql
*
*/
class MysqlParser extends AbstractParser
{
/**
*
* Split the query string on these regexes.
*
* @var array
*
*/
protected $split = [
// single-quoted string
"'(?:[^'\\\\]|\\\\'?)*'",
// double-quoted string
'"(?:[^"\\\\]|\\\\"?)*"',
// backtick-quoted string
'`(?:[^`\\\\]|\\\\`?)*`',
];
/**
*
* Skip query parts matching this regex.
*
* @var string
*
*/
protected $skip = '/^(\'|\"|\`)/um';
}

View File

@ -0,0 +1,36 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql\Parser;
/**
*
* A parser/rebuilder that does nothing at all; use this when your placeholders
* and bound-values are already perfectly matched.
*
* @package Aura.Sql
*
*/
class NullParser implements ParserInterface
{
/**
*
* Leaves the query and parameters alone.
*
* @param string $statement The query statement string.
*
* @param array $values Bind these values into the query.
*
* @return array
*
*/
public function rebuild($statement, array $values = [])
{
return [$statement, $values];
}
}

View File

@ -0,0 +1,34 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql\Parser;
/**
*
* Interface for query parsing/rebuilding functionality.
*
* @package Aura.Sql
*
*/
interface ParserInterface
{
/**
*
* Rebuilds a query and its parameters to adapt it to PDO's limitations,
* and returns a list of queries.
*
* @param string $string The query statement string.
*
* @param array $parameters Bind these values into the query.
*
* @return array An array where element 0 is the rebuilt statement and
* element 1 is the rebuilt array of values.
*
*/
public function rebuild($string, array $parameters = []);
}

View File

@ -0,0 +1,46 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql\Parser;
/**
*
* Parsing/rebuilding functionality for the pgsl driver.
*
* @package Aura.Sql
*
*/
class PgsqlParser extends AbstractParser
{
/**
*
* Split the query string on these regexes.
*
* @var array
*
*/
protected $split = [
// single-quoted string
"'(?:[^'\\\\]|\\\\'?)*'",
// double-quoted string
'"(?:[^"\\\\]|\\\\"?)*"',
// double-dollar string (empty dollar-tag)
'\$\$(?:[^\$]?)*\$\$',
// dollar-tag string -- DOES NOT match tags properly
'\$[^\$]+\$.*\$[^\$]+\$',
];
/**
*
* Skip query parts matching this regex.
*
* @var string
*
*/
protected $skip = '/^(\'|\"|\$|\:[^a-zA-Z_])/um';
}

View File

@ -0,0 +1,36 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql\Parser;
/**
*
* Parsing/rebuilding functionality for the sqlite driver.
*
* @package Aura.Sql
*
*/
class SqliteParser extends AbstractParser
{
/**
* {@inheritDoc}
*/
protected $split = [
// single-quoted string
"'(?:[^'\\\\]|\\\\'?)*'",
// double-quoted string
'"(?:[^"\\\\]|\\\\"?)*"',
// backticked column names
'`(?:[^`\\\\]|\\\\`?)*`',
];
/**
* {@inheritDoc}
*/
protected $skip = '/^(\'|"|`|\:[^a-zA-Z_])/um';
}

View File

@ -0,0 +1,22 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql\Parser;
/**
*
* Parsing/rebuilding functionality for the sqlsrv driver.
*
* @package Aura.Sql
*
* @todo add $split and $skip for single quote, double quote, and square brackets
*
*/
class SqlsrvParser extends AbstractParser
{
}

View File

@ -0,0 +1,189 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql;
use PDO;
/**
*
* An interface to the native PDO object.
*
* @package Aura.Sql
*
*/
interface PdoInterface
{
/**
*
* Begins a transaction and turns off autocommit mode.
*
* @return bool True on success, false on failure.
*
* @see http://php.net/manual/en/pdo.begintransaction.php
*
*/
public function beginTransaction();
/**
*
* Commits the existing transaction and restores autocommit mode.
*
* @return bool True on success, false on failure.
*
* @see http://php.net/manual/en/pdo.commit.php
*
*/
public function commit();
/**
*
* Gets the most recent error code.
*
* @return mixed
*
*/
public function errorCode();
/**
*
* Gets the most recent error info.
*
* @return array
*
*/
public function errorInfo();
/**
*
* Executes an SQL statement and returns the number of affected rows.
*
* @param string $statement The SQL statement to execute.
*
* @return int The number of rows affected.
*
* @see http://php.net/manual/en/pdo.exec.php
*
*/
public function exec($statement);
/**
*
* Gets a PDO attribute value.
*
* @param mixed $attribute The PDO::ATTR_* constant.
*
* @return mixed The value for the attribute.
*
*/
public function getAttribute($attribute);
/**
*
* Is a transaction currently active?
*
* @return bool
*
* @see http://php.net/manual/en/pdo.intransaction.php
*
*/
public function inTransaction();
/**
*
* Returns the last inserted autoincrement sequence value.
*
* @param string $name The name of the sequence to check; typically needed
* only for PostgreSQL, where it takes the form of `<table>_<column>_seq`.
*
* @return string
*
* @see http://php.net/manual/en/pdo.lastinsertid.php
*
*/
public function lastInsertId($name = null);
/**
*
* Prepares an SQL statement for execution.
*
* @param string $statement The SQL statement to prepare for execution.
*
* @param array $options Set these attributes on the returned
* PDOStatement.
*
* @return \PDOStatement
*
* @see http://php.net/manual/en/pdo.prepare.php
*
*/
public function prepare($statement, $options = null);
/**
*
* Queries the database and returns a PDOStatement.
*
* @param string $statement The SQL statement to prepare and execute.
*
* @param mixed ...$fetch Optional fetch-related parameters.
*
* @return \PDOStatement
*
* @see http://php.net/manual/en/pdo.query.php
*
*/
public function query($statement, ...$fetch);
/**
*
* Quotes a value for use in an SQL statement.
*
* @param mixed $value The value to quote.
*
* @param int $parameter_type A data type hint for the database driver.
*
* @return string The quoted value.
*
* @see http://php.net/manual/en/pdo.quote.php
*
*/
public function quote($value, $parameter_type = PDO::PARAM_STR);
/**
*
* Rolls back the current transaction and restores autocommit mode.
*
* @return bool True on success, false on failure.
*
* @see http://php.net/manual/en/pdo.rollback.php
*
*/
public function rollBack();
/**
*
* Sets a PDO attribute value.
*
* @param mixed $attribute The PDO::ATTR_* constant.
*
* @param mixed $value The value for the attribute.
*
* @return bool
*
*/
public function setAttribute($attribute, $value);
/**
*
* Returns all currently available PDO drivers.
*
* @return array
*
*/
public static function getAvailableDrivers();
}

View File

@ -0,0 +1,64 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql\Profiler;
use Psr\Log\AbstractLogger;
/**
*
* A naive memory-based logger.
*
* @package Aura.Sql
*
*/
class MemoryLogger extends AbstractLogger
{
/**
*
* Log messages.
*
* @var array
*
*/
protected $messages = [];
/**
*
* Logs a message.
*
* @param mixed $level The log level (ignored).
*
* @param string $message The log message.
*
* @param array $context Data to interpolate into the message.
*
* @return null
*
*/
public function log($level, $message, array $context = [])
{
$replace = [];
foreach ($context as $key => $val) {
$replace['{' . $key . '}'] = $val;
}
$this->messages[] = strtr($message, $replace);
}
/**
*
* Returns the logged messages.
*
* @return array
*
*/
public function getMessages()
{
return $this->messages;
}
}

View File

@ -0,0 +1,229 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql\Profiler;
use Aura\Sql\Exception;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
/**
*
* Sends query profiles to a logger.
*
* @package Aura.Sql
*
*/
class Profiler implements ProfilerInterface
{
/**
*
* The current profile information.
*
* @var array
*
*/
protected $context = [];
/**
*
* Log profile data through this interface.
*
* @var LoggerInterface
*
*/
protected $logger;
/**
*
* Turns profile logging off and on.
*
* @var bool
*
* @see setActive()
*
*/
protected $active = false;
/**
*
* The log level for all messages.
*
* @var string
*
* @see setLogLevel()
*
*/
protected $logLevel = LogLevel::DEBUG;
/**
*
* Sets the format for the log message, with placeholders.
*
* @var string
*
* @see setLogFormat()
*
*/
protected $logFormat = "{function} ({duration} seconds): {statement} {backtrace}";
/**
*
* Constructor.
*
* @param LoggerInterface $logger Record profiles through this interface.
*
*/
public function __construct(LoggerInterface $logger = null)
{
if ($logger === null) {
$logger = new MemoryLogger();
}
$this->logger = $logger;
}
/**
*
* Enable or disable profiler logging.
*
* @param bool $active
*
*/
public function setActive($active)
{
$this->active = (bool) $active;
}
/**
*
* Returns true if logging is active.
*
* @return bool
*
*/
public function isActive()
{
return $this->active;
}
/**
*
* Returns the underlying logger instance.
*
* @return \Psr\Log\LoggerInterface
*
*/
public function getLogger()
{
return $this->logger;
}
/**
*
* Returns the level at which to log profile messages.
*
* @return string
*
*/
public function getLogLevel()
{
return $this->logLevel;
}
/**
*
* Level at which to log profile messages.
*
* @param string $logLevel A PSR LogLevel constant.
*
* @return null
*
*/
public function setLogLevel($logLevel)
{
$this->logLevel = $logLevel;
}
/**
*
* Returns the log message format string, with placeholders.
*
* @return string
*
*/
public function getLogFormat()
{
return $this->logFormat;
}
/**
*
* Sets the log message format string, with placeholders.
*
* @param string $logFormat
*
* @return null
*
*/
public function setLogFormat($logFormat)
{
$this->logFormat = $logFormat;
}
/**
*
* Starts a profile entry.
*
* @param string $function The function starting the profile entry.
*
* @return null
*
*/
public function start($function)
{
if (! $this->active) {
return;
}
$this->context = [
'function' => $function,
'start' => microtime(true),
];
}
/**
*
* Finishes and logs a profile entry.
*
* @param string $statement The statement being profiled, if any.
*
* @param array $values The values bound to the statement, if any.
*
* @return null
*
*/
public function finish($statement = null, array $values = [])
{
if (! $this->active) {
return;
}
$finish = microtime(true);
$e = new Exception();
$this->context['finish'] = $finish;
$this->context['duration'] = $finish - $this->context['start'];
$this->context['statement'] = $statement;
$this->context['values'] = empty($values) ? '' : print_r($values, true);
$this->context['backtrace'] = $e->getTraceAsString();
$this->logger->log($this->logLevel, $this->logFormat, $this->context);
$this->context = [];
}
}

View File

@ -0,0 +1,110 @@
<?php
/**
*
* This file is part of Aura for PHP.
*
* @license https://opensource.org/licenses/MIT MIT
*
*/
namespace Aura\Sql\Profiler;
/**
*
* Interface to send query profiles to a logger.
*
* @package Aura.Sql
*
*/
interface ProfilerInterface
{
/**
*
* Enable or disable profiler logging.
*
* @param bool $active
*
*/
public function setActive($active);
/**
*
* Returns true if logging is active.
*
* @return bool
*
*/
public function isActive();
/**
*
* Returns the underlying logger instance.
*
* @return \Psr\Log\LoggerInterface
*
*/
public function getLogger();
/**
*
* Returns the level at which to log profile messages.
*
* @return string
*
*/
public function getLogLevel();
/**
*
* Level at which to log profile messages.
*
* @param string $logLevel A PSR LogLevel constant.
*
* @return null
*
*/
public function setLogLevel($logLevel);
/**
*
* Returns the log message format string, with placeholders.
*
* @return string
*
*/
public function getLogFormat();
/**
*
* Sets the log message format string, with placeholders.
*
* @param string $logFormat
*
* @return null
*
*/
public function setLogFormat($logFormat);
/**
*
* Starts a profile entry.
*
* @param string $function The function starting the profile entry.
*
* @return null
*
*/
public function start($function);
/**
*
* Finishes and logs a profile entry.
*
* @param string $statement The statement being profiled, if any.
*
* @param array $values The values bound to the statement, if any.
*
* @return null
*
*/
public function finish($statement = null, array $values = []);
}

12
includes/vendor/autoload.php vendored Normal file
View File

@ -0,0 +1,12 @@
<?php
// autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) {
echo 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
exit(1);
}
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit2d6d15a8f6cc4bfbfd4a2943a6c4df59::getLoader();

View File

@ -0,0 +1,232 @@
#!/bin/bash
####################################################################
# This file is part of YOURLS
#
# List (and remove) unneeded files for production
#
# Run this script when adding, updating or removing a 3rd party
# library that goes in the `vendor` directory.
#
# Typical use:
#
# $ composer update --no-dev --prefer-dist
# $ ./includes/vendor/build-script/yourls-build.sh ./includes/vendor
# $ commit & push
#
####################################################################
## OPTIONS ##########################################################
# This directories in /vendor won't be cleaned up
# Must be explicit names, case sensitive, no wildcard eg "README.*"
#
PRESERVE_IN_VENDOR=(
'composer'
'build-script'
'symfony'
)
# Files & dirs to keep in each library directory
# Must be explicit names, case sensitive, no wildcard eg "README.*"
#
PRESERVE_IN_LIB=(
'src'
'library'
'lib'
'Psr'
'README.md'
'readme.md'
'certificates'
)
# Nothing to edit past this line !
## VARS #############################################################
# Default values.
TESTRUN=true
# Colors and fancyness
RED='\033[0;31m'
NORM='\033[0m'
BOLD='\033[1m'
GREEN='\033[0;32m'
PURPLE='\033[0;35m'
# Set Script Name variable
SCRIPT=`basename ${BASH_SOURCE[0]}`
## FUNCS ############################################################
# Print help
rtfm () {
echo -e "\nUsage: "
echo -e " ${BOLD}${SCRIPT}${NORM} [-dh] <directory to cleanup>"
echo -e ""
echo -e "Examples: "
echo -e " ${BOLD}${SCRIPT}${NORM} [-dh] ."
echo -e " ${BOLD}${SCRIPT}${NORM} [-dh] /some/path/to/clean"
echo -e ""
echo -e "Options:"
echo -e " ${BOLD}-h${NORM} Display this ${BOLD}H${NORM}elp message"
echo -e " ${BOLD}-d${NORM} Actually ${BOLD}D${NORM}elete files flagged by this script"
echo -e ""
exit 1
}
# in_array NEEDLE HAYSTACK
# Return 0/1
in_array () {
local e
for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 0; done
return 1
}
# Cleans the mess
cleanup () {
# Return if function called with no parameter
if [ -z "$1" ]
then
return
fi
# Directory we are in
CUR=$1
# Loop over each file and delete those we don't want to keep
echo -e "${PURPLE}Cleaning: $(basename $(dirname "$CUR"))/$(basename "$CUR") ${NORM}"
for FILE in $(ls -A $CUR)
do
if in_array $FILE "${PRESERVE_IN_LIB[@]}"
then
echo -e "${GREEN}+${NORM} KEEP: $FILE"
else
echo -e "${RED}-${NORM} DEL : $FILE"
maybe_delete "${CUR}${FILE}"
fi
done;
# If directory is empty, delete
if [ ! "$(ls -A $CUR)" ]
then
echo -e "${RED}-${NORM} del : $(basename "$CUR") (empty dir)"
maybe_delete "$CUR"
fi
echo ""
}
# Delete file if not in test run
maybe_delete () {
if [ "$TESTRUN" = false ]
then
rm -rf "$1"
fi
}
# Check the number of arguments. If none are passed, print help and exit.
args_or_die () {
if [ $1 -eq 0 ]; then
rtfm
fi
}
## WORK #############################################################
# We should have some arguments
args_or_die "$#"
# Check options
while getopts "dh" opt; do
case $opt in
d)
TESTRUN=false
;;
h)
rtfm
;;
\?)
rtfm
;;
esac
done
shift $((OPTIND-1)) #This tells getopts to move on to the next argument.
# Again, we should have some arguments after dealing with options if any
# Yes, this isn't perfect, there should be one test. Will do.
args_or_die "$#"
# Check for valid dir
if [ ! -d "$1" ]
then
echo -e "Need a valid directory, '${RED}$1${NORM}' is not."
rtfm
else
# Resolve directory (expand '.' or '../stuff' as full path)
TARGETDIR=$(cd "$1"; pwd)
fi
# Dry run notice if applicable
if [ "$TESTRUN" = true ]
then
echo -e "Test mode. ${RED}Nothing will be deleted${NORM}.\n"
echo -e "Use with ${RED}-d${NORM} if you are OK with the proposed changes (or clean up manually).\n"
fi
# 1. Get list of all directories in target directory, except the one
# listed in PRESERVE_IN_VENDOR that we don't want to touch
#
VENDORS=($(ls -d $TARGETDIR/*/))
TEMP=(${VENDORS[@]})
for (( i=0; i<${#VENDORS[@]}; i++ ))
do
DIR=$(basename "${VENDORS[i]}")
if in_array "$DIR" "${PRESERVE_IN_VENDOR[@]}"
then
unset TEMP[$i]
fi
done
VENDORS=(${TEMP[@]})
# 2. Loop over each directory and clean up
#
for DIR in ${VENDORS[@]}
do
SUBDIRS=$(ls -d $DIR*/ 2>/dev/null)
if [ ! -z "$SUBDIRS" ]
then
# This VENDORS directory has subdirectory: process each subdir
for SUBDIR in $SUBDIRS
do
cleanup $SUBDIR
done;
else
# This directory contains no subdirectory
cleanup $DIR
fi
done
# Exit reminder
if [ "$TESTRUN" = true ]
then
echo -e "(${GREEN}Nothing has been deleted${NORM})\n"
else
echo -e "... all done $GREEN ;) $NORM\n"
fi

572
includes/vendor/composer/ClassLoader.php vendored Normal file
View File

@ -0,0 +1,572 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see https://www.php-fig.org/psr/psr-0/
* @see https://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
/** @var ?string */
private $vendorDir;
// PSR-4
/**
* @var array[]
* @psalm-var array<string, array<string, int>>
*/
private $prefixLengthsPsr4 = array();
/**
* @var array[]
* @psalm-var array<string, array<int, string>>
*/
private $prefixDirsPsr4 = array();
/**
* @var array[]
* @psalm-var array<string, string>
*/
private $fallbackDirsPsr4 = array();
// PSR-0
/**
* @var array[]
* @psalm-var array<string, array<string, string[]>>
*/
private $prefixesPsr0 = array();
/**
* @var array[]
* @psalm-var array<string, string>
*/
private $fallbackDirsPsr0 = array();
/** @var bool */
private $useIncludePath = false;
/**
* @var string[]
* @psalm-var array<string, string>
*/
private $classMap = array();
/** @var bool */
private $classMapAuthoritative = false;
/**
* @var bool[]
* @psalm-var array<string, bool>
*/
private $missingClasses = array();
/** @var ?string */
private $apcuPrefix;
/**
* @var self[]
*/
private static $registeredLoaders = array();
/**
* @param ?string $vendorDir
*/
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
}
/**
* @return string[]
*/
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
}
/**
* @return array[]
* @psalm-return array<string, array<int, string>>
*/
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
/**
* @return array[]
* @psalm-return array<string, string>
*/
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
/**
* @return array[]
* @psalm-return array<string, string>
*/
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
/**
* @return string[] Array of classname => path
* @psalm-return array<string, string>
*/
public function getClassMap()
{
return $this->classMap;
}
/**
* @param string[] $classMap Class to filename map
* @psalm-param array<string, string> $classMap
*
* @return void
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param string[]|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*
* @return void
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param string[]|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param string[]|string $paths The PSR-0 base directories
*
* @return void
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param string[]|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*
* @return void
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*
* @return void
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*
* @return void
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*
* @return void
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
}
/**
* Unregisters this instance as an autoloader.
*
* @return void
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return true|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
return null;
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
/**
* Returns the currently registered loaders indexed by their corresponding vendor directories.
*
* @return self[]
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
/**
* @param string $class
* @param string $ext
* @return string|false
*/
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
* @private
*/
function includeFile($file)
{
include $file;
}

View File

@ -0,0 +1,352 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer;
use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;
/**
* This class is copied in every Composer installed project and available to all
*
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
*
* To require its presence, you can require `composer-runtime-api ^2.0`
*
* @final
*/
class InstalledVersions
{
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}|array{}|null
*/
private static $installed;
/**
* @var bool|null
*/
private static $canGetVendors;
/**
* @var array[]
* @psalm-var array<string, array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
*/
private static $installedByVendor = array();
/**
* Returns a list of all package names which are present, either by being installed, replaced or provided
*
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackages()
{
$packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
}
/**
* Returns a list of all package names with a specific type e.g. 'library'
*
* @param string $type
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackagesByType($type)
{
$packagesByType = array();
foreach (self::getInstalled() as $installed) {
foreach ($installed['versions'] as $name => $package) {
if (isset($package['type']) && $package['type'] === $type) {
$packagesByType[] = $name;
}
}
}
return $packagesByType;
}
/**
* Checks whether the given package is installed
*
* This also returns true if the package name is provided or replaced by another package
*
* @param string $packageName
* @param bool $includeDevRequirements
* @return bool
*/
public static function isInstalled($packageName, $includeDevRequirements = true)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
}
}
return false;
}
/**
* Checks whether the given package satisfies a version constraint
*
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
*
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
*
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
* @param string $packageName
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
* @return bool
*/
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints($constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
/**
* Returns a version constraint representing all the range(s) which are installed for a given package
*
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
* whether a given version of a package is installed, and not just whether it exists
*
* @param string $packageName
* @return string Version constraint usable with composer/semver
*/
public static function getVersionRanges($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getPrettyVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
*/
public static function getReference($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
*/
public static function getInstallPath($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @return array
* @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}
*/
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
/**
* Returns the raw installed.php data for custom implementations
*
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
* @return array[]
* @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}
*/
public static function getRawData()
{
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = include __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
return self::$installed;
}
/**
* Returns the raw data of all installed.php which are currently loaded for custom implementations
*
* @return array[]
* @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
*/
public static function getAllRawData()
{
return self::getInstalled();
}
/**
* Lets you reload the static array from another file
*
* This is only useful for complex integrations in which a project needs to use
* this class but then also needs to execute another project's autoloader in process,
* and wants to ensure both projects have access to their version of installed.php.
*
* A typical case would be PHPUnit, where it would need to make sure it reads all
* the data it needs from this class, then call reload() with
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
* the project in which it runs can then also use this class safely, without
* interference between PHPUnit's dependencies and the project's dependencies.
*
* @param array[] $data A vendor/composer/installed.php data set
* @return void
*
* @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>} $data
*/
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
}
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
*/
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
if (self::$canGetVendors) {
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
self::$installed = $installed[count($installed) - 1];
}
}
}
}
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = require __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
$installed[] = self::$installed;
return $installed;
}
}

21
includes/vendor/composer/LICENSE vendored Normal file
View File

@ -0,0 +1,21 @@
Copyright (c) Nils Adermann, Jordi Boggiano
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.

View File

@ -0,0 +1,12 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname(dirname($vendorDir));
return array(
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
'Requests' => $vendorDir . '/rmccue/requests/library/Requests.php',
);

View File

@ -0,0 +1,15 @@
<?php
// autoload_files.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname(dirname($vendorDir));
return array(
'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php',
'b45b351e6b6f7487d819961fef2fda77' => $vendorDir . '/jakeasmith/http_build_url/src/http_build_url.php',
'941748b3c8cae4466c827dfb5ca9602a' => $vendorDir . '/rmccue/requests/library/Deprecated.php',
'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
);

View File

@ -0,0 +1,10 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname(dirname($vendorDir));
return array(
'Ozh\\Bookmarkletgen\\' => array($vendorDir . '/ozh/bookmarkletgen/src'),
);

View File

@ -0,0 +1,24 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname(dirname($vendorDir));
return array(
'YOURLS\\' => array($baseDir . '/includes'),
'WpOrg\\Requests\\' => array($vendorDir . '/rmccue/requests/src'),
'Symfony\\Polyfill\\Php72\\' => array($vendorDir . '/symfony/polyfill-php72'),
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'),
'Symfony\\Polyfill\\Intl\\Idn\\' => array($vendorDir . '/symfony/polyfill-intl-idn'),
'Spatie\\ArrayToXml\\' => array($vendorDir . '/spatie/array-to-xml/src'),
'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
'POMO\\' => array($vendorDir . '/pomo/pomo/src'),
'MaxMind\\WebService\\' => array($vendorDir . '/maxmind/web-service-common/src/WebService'),
'MaxMind\\Exception\\' => array($vendorDir . '/maxmind/web-service-common/src/Exception'),
'MaxMind\\Db\\' => array($vendorDir . '/maxmind-db/reader/src/MaxMind/Db'),
'GeoIp2\\' => array($vendorDir . '/geoip2/geoip2/src'),
'Composer\\CaBundle\\' => array($vendorDir . '/composer/ca-bundle/src'),
'Aura\\Sql\\' => array($vendorDir . '/aura/sql/src'),
);

View File

@ -0,0 +1,57 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit2d6d15a8f6cc4bfbfd4a2943a6c4df59
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInit2d6d15a8f6cc4bfbfd4a2943a6c4df59', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInit2d6d15a8f6cc4bfbfd4a2943a6c4df59', 'loadClassLoader'));
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit2d6d15a8f6cc4bfbfd4a2943a6c4df59::getInitializer($loader));
$loader->register(true);
$includeFiles = \Composer\Autoload\ComposerStaticInit2d6d15a8f6cc4bfbfd4a2943a6c4df59::$files;
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequire2d6d15a8f6cc4bfbfd4a2943a6c4df59($fileIdentifier, $file);
}
return $loader;
}
}
/**
* @param string $fileIdentifier
* @param string $file
* @return void
*/
function composerRequire2d6d15a8f6cc4bfbfd4a2943a6c4df59($fileIdentifier, $file)
{
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
require $file;
}
}

View File

@ -0,0 +1,149 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInit2d6d15a8f6cc4bfbfd4a2943a6c4df59
{
public static $files = array (
'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php',
'b45b351e6b6f7487d819961fef2fda77' => __DIR__ . '/..' . '/jakeasmith/http_build_url/src/http_build_url.php',
'941748b3c8cae4466c827dfb5ca9602a' => __DIR__ . '/..' . '/rmccue/requests/library/Deprecated.php',
'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
);
public static $prefixLengthsPsr4 = array (
'Y' =>
array (
'YOURLS\\' => 7,
),
'W' =>
array (
'WpOrg\\Requests\\' => 15,
),
'S' =>
array (
'Symfony\\Polyfill\\Php72\\' => 23,
'Symfony\\Polyfill\\Mbstring\\' => 26,
'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33,
'Symfony\\Polyfill\\Intl\\Idn\\' => 26,
'Spatie\\ArrayToXml\\' => 18,
),
'P' =>
array (
'Psr\\Log\\' => 8,
'POMO\\' => 5,
),
'M' =>
array (
'MaxMind\\WebService\\' => 19,
'MaxMind\\Exception\\' => 18,
'MaxMind\\Db\\' => 11,
),
'G' =>
array (
'GeoIp2\\' => 7,
),
'C' =>
array (
'Composer\\CaBundle\\' => 18,
),
'A' =>
array (
'Aura\\Sql\\' => 9,
),
);
public static $prefixDirsPsr4 = array (
'YOURLS\\' =>
array (
0 => __DIR__ . '/../../..' . '/includes',
),
'WpOrg\\Requests\\' =>
array (
0 => __DIR__ . '/..' . '/rmccue/requests/src',
),
'Symfony\\Polyfill\\Php72\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php72',
),
'Symfony\\Polyfill\\Mbstring\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
),
'Symfony\\Polyfill\\Intl\\Normalizer\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer',
),
'Symfony\\Polyfill\\Intl\\Idn\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-intl-idn',
),
'Spatie\\ArrayToXml\\' =>
array (
0 => __DIR__ . '/..' . '/spatie/array-to-xml/src',
),
'Psr\\Log\\' =>
array (
0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
),
'POMO\\' =>
array (
0 => __DIR__ . '/..' . '/pomo/pomo/src',
),
'MaxMind\\WebService\\' =>
array (
0 => __DIR__ . '/..' . '/maxmind/web-service-common/src/WebService',
),
'MaxMind\\Exception\\' =>
array (
0 => __DIR__ . '/..' . '/maxmind/web-service-common/src/Exception',
),
'MaxMind\\Db\\' =>
array (
0 => __DIR__ . '/..' . '/maxmind-db/reader/src/MaxMind/Db',
),
'GeoIp2\\' =>
array (
0 => __DIR__ . '/..' . '/geoip2/geoip2/src',
),
'Composer\\CaBundle\\' =>
array (
0 => __DIR__ . '/..' . '/composer/ca-bundle/src',
),
'Aura\\Sql\\' =>
array (
0 => __DIR__ . '/..' . '/aura/sql/src',
),
);
public static $prefixesPsr0 = array (
'O' =>
array (
'Ozh\\Bookmarkletgen\\' =>
array (
0 => __DIR__ . '/..' . '/ozh/bookmarkletgen/src',
),
),
);
public static $classMap = array (
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
'Requests' => __DIR__ . '/..' . '/rmccue/requests/library/Requests.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit2d6d15a8f6cc4bfbfd4a2943a6c4df59::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit2d6d15a8f6cc4bfbfd4a2943a6c4df59::$prefixDirsPsr4;
$loader->prefixesPsr0 = ComposerStaticInit2d6d15a8f6cc4bfbfd4a2943a6c4df59::$prefixesPsr0;
$loader->classMap = ComposerStaticInit2d6d15a8f6cc4bfbfd4a2943a6c4df59::$classMap;
}, null, ClassLoader::class);
}
}

View File

@ -0,0 +1,19 @@
Copyright (C) 2016 Composer
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.

View File

@ -0,0 +1,85 @@
composer/ca-bundle
==================
Small utility library that lets you find a path to the system CA bundle,
and includes a fallback to the Mozilla CA bundle.
Originally written as part of [composer/composer](https://github.com/composer/composer),
now extracted and made available as a stand-alone library.
Installation
------------
Install the latest version with:
```bash
$ composer require composer/ca-bundle
```
Requirements
------------
* PHP 5.3.2 is required but using the latest version of PHP is highly recommended.
Basic usage
-----------
### `Composer\CaBundle\CaBundle`
- `CaBundle::getSystemCaRootBundlePath()`: Returns the system CA bundle path, or a path to the bundled one as fallback
- `CaBundle::getBundledCaBundlePath()`: Returns the path to the bundled CA file
- `CaBundle::validateCaFile($filename)`: Validates a CA file using openssl_x509_parse only if it is safe to use
- `CaBundle::isOpensslParseSafe()`: Test if it is safe to use the PHP function openssl_x509_parse()
- `CaBundle::reset()`: Resets the static caches
#### To use with curl
```php
$curl = curl_init("https://example.org/");
$caPathOrFile = \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath();
if (is_dir($caPathOrFile)) {
curl_setopt($curl, CURLOPT_CAPATH, $caPathOrFile);
} else {
curl_setopt($curl, CURLOPT_CAINFO, $caPathOrFile);
}
$result = curl_exec($curl);
```
#### To use with php streams
```php
$opts = array(
'http' => array(
'method' => "GET"
)
);
$caPathOrFile = \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath();
if (is_dir($caPathOrFile)) {
$opts['ssl']['capath'] = $caPathOrFile;
} else {
$opts['ssl']['cafile'] = $caPathOrFile;
}
$context = stream_context_create($opts);
$result = file_get_contents('https://example.com', false, $context);
```
#### To use with Guzzle
```php
$client = new \GuzzleHttp\Client([
\GuzzleHttp\RequestOptions::VERIFY => \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath()
]);
```
License
-------
composer/ca-bundle is licensed under the MIT License, see the LICENSE file for details.

View File

@ -0,0 +1,54 @@
{
"name": "composer/ca-bundle",
"description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.",
"type": "library",
"license": "MIT",
"keywords": [
"cabundle",
"cacert",
"certificate",
"ssl",
"tls"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"support": {
"irc": "irc://irc.freenode.org/composer",
"issues": "https://github.com/composer/ca-bundle/issues"
},
"require": {
"ext-openssl": "*",
"ext-pcre": "*",
"php": "^5.3.2 || ^7.0 || ^8.0"
},
"require-dev": {
"symfony/phpunit-bridge": "^4.2 || ^5",
"phpstan/phpstan": "^0.12.55",
"psr/log": "^1.0",
"symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0"
},
"autoload": {
"psr-4": {
"Composer\\CaBundle\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Composer\\CaBundle\\": "tests"
}
},
"extra": {
"branch-alias": {
"dev-main": "1.x-dev"
}
},
"scripts": {
"test": "SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT=1 vendor/bin/simple-phpunit",
"phpstan": "vendor/bin/phpstan analyse"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,431 @@
<?php
/*
* This file is part of composer/ca-bundle.
*
* (c) Composer <https://github.com/composer>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Composer\CaBundle;
use Psr\Log\LoggerInterface;
use Symfony\Component\Process\PhpProcess;
/**
* @author Chris Smith <chris@cs278.org>
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class CaBundle
{
/** @var string|null */
private static $caPath;
/** @var array<string, bool> */
private static $caFileValidity = array();
/** @var bool|null */
private static $useOpensslParse;
/**
* Returns the system CA bundle path, or a path to the bundled one
*
* This method was adapted from Sslurp.
* https://github.com/EvanDotPro/Sslurp
*
* (c) Evan Coury <me@evancoury.com>
*
* For the full copyright and license information, please see below:
*
* Copyright (c) 2013, Evan Coury
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @param LoggerInterface $logger optional logger for information about which CA files were loaded
* @return string path to a CA bundle file or directory
*/
public static function getSystemCaRootBundlePath(LoggerInterface $logger = null)
{
if (self::$caPath !== null) {
return self::$caPath;
}
$caBundlePaths = array();
// If SSL_CERT_FILE env variable points to a valid certificate/bundle, use that.
// This mimics how OpenSSL uses the SSL_CERT_FILE env variable.
$caBundlePaths[] = self::getEnvVariable('SSL_CERT_FILE');
// If SSL_CERT_DIR env variable points to a valid certificate/bundle, use that.
// This mimics how OpenSSL uses the SSL_CERT_FILE env variable.
$caBundlePaths[] = self::getEnvVariable('SSL_CERT_DIR');
$caBundlePaths[] = ini_get('openssl.cafile');
$caBundlePaths[] = ini_get('openssl.capath');
$otherLocations = array(
'/etc/pki/tls/certs/ca-bundle.crt', // Fedora, RHEL, CentOS (ca-certificates package)
'/etc/ssl/certs/ca-certificates.crt', // Debian, Ubuntu, Gentoo, Arch Linux (ca-certificates package)
'/etc/ssl/ca-bundle.pem', // SUSE, openSUSE (ca-certificates package)
'/usr/local/share/certs/ca-root-nss.crt', // FreeBSD (ca_root_nss_package)
'/usr/ssl/certs/ca-bundle.crt', // Cygwin
'/opt/local/share/curl/curl-ca-bundle.crt', // OS X macports, curl-ca-bundle package
'/usr/local/share/curl/curl-ca-bundle.crt', // Default cURL CA bunde path (without --with-ca-bundle option)
'/usr/share/ssl/certs/ca-bundle.crt', // Really old RedHat?
'/etc/ssl/cert.pem', // OpenBSD
'/usr/local/etc/ssl/cert.pem', // FreeBSD 10.x
'/usr/local/etc/openssl/cert.pem', // OS X homebrew, openssl package
'/usr/local/etc/openssl@1.1/cert.pem', // OS X homebrew, openssl@1.1 package
);
foreach($otherLocations as $location) {
$otherLocations[] = dirname($location);
}
$caBundlePaths = array_merge($caBundlePaths, $otherLocations);
foreach ($caBundlePaths as $caBundle) {
if ($caBundle && self::caFileUsable($caBundle, $logger)) {
return self::$caPath = $caBundle;
}
if ($caBundle && self::caDirUsable($caBundle, $logger)) {
return self::$caPath = $caBundle;
}
}
return self::$caPath = static::getBundledCaBundlePath(); // Bundled CA file, last resort
}
/**
* Returns the path to the bundled CA file
*
* In case you don't want to trust the user or the system, you can use this directly
*
* @return string path to a CA bundle file
*/
public static function getBundledCaBundlePath()
{
$caBundleFile = __DIR__.'/../res/cacert.pem';
// cURL does not understand 'phar://' paths
// see https://github.com/composer/ca-bundle/issues/10
if (0 === strpos($caBundleFile, 'phar://')) {
$tempCaBundleFile = tempnam(sys_get_temp_dir(), 'openssl-ca-bundle-');
if (false === $tempCaBundleFile) {
throw new \RuntimeException('Could not create a temporary file to store the bundled CA file');
}
file_put_contents(
$tempCaBundleFile,
file_get_contents($caBundleFile)
);
register_shutdown_function(function() use ($tempCaBundleFile) {
@unlink($tempCaBundleFile);
});
$caBundleFile = $tempCaBundleFile;
}
return $caBundleFile;
}
/**
* Validates a CA file using opensl_x509_parse only if it is safe to use
*
* @param string $filename
* @param LoggerInterface $logger optional logger for information about which CA files were loaded
*
* @return bool
*/
public static function validateCaFile($filename, LoggerInterface $logger = null)
{
static $warned = false;
if (isset(self::$caFileValidity[$filename])) {
return self::$caFileValidity[$filename];
}
$contents = file_get_contents($filename);
// assume the CA is valid if php is vulnerable to
// https://www.sektioneins.de/advisories/advisory-012013-php-openssl_x509_parse-memory-corruption-vulnerability.html
if (!static::isOpensslParseSafe()) {
if (!$warned && $logger) {
$logger->warning(sprintf(
'Your version of PHP, %s, is affected by CVE-2013-6420 and cannot safely perform certificate validation, we strongly suggest you upgrade.',
PHP_VERSION
));
$warned = true;
}
$isValid = !empty($contents);
} elseif (is_string($contents) && strlen($contents) > 0) {
$contents = preg_replace("/^(\\-+(?:BEGIN|END))\\s+TRUSTED\\s+(CERTIFICATE\\-+)\$/m", '$1 $2', $contents);
if (null === $contents) {
// regex extraction failed
$isValid = false;
} else {
$isValid = (bool) openssl_x509_parse($contents);
}
} else {
$isValid = false;
}
if ($logger) {
$logger->debug('Checked CA file '.realpath($filename).': '.($isValid ? 'valid' : 'invalid'));
}
return self::$caFileValidity[$filename] = $isValid;
}
/**
* Test if it is safe to use the PHP function openssl_x509_parse().
*
* This checks if OpenSSL extensions is vulnerable to remote code execution
* via the exploit documented as CVE-2013-6420.
*
* @return bool
*/
public static function isOpensslParseSafe()
{
if (null !== self::$useOpensslParse) {
return self::$useOpensslParse;
}
if (PHP_VERSION_ID >= 50600) {
return self::$useOpensslParse = true;
}
// Vulnerable:
// PHP 5.3.0 - PHP 5.3.27
// PHP 5.4.0 - PHP 5.4.22
// PHP 5.5.0 - PHP 5.5.6
if (
(PHP_VERSION_ID < 50400 && PHP_VERSION_ID >= 50328)
|| (PHP_VERSION_ID < 50500 && PHP_VERSION_ID >= 50423)
|| PHP_VERSION_ID >= 50507
) {
// This version of PHP has the fix for CVE-2013-6420 applied.
return self::$useOpensslParse = true;
}
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
// Windows is probably insecure in this case.
return self::$useOpensslParse = false;
}
$compareDistroVersionPrefix = function ($prefix, $fixedVersion) {
$regex = '{^'.preg_quote($prefix).'([0-9]+)$}';
if (preg_match($regex, PHP_VERSION, $m)) {
return ((int) $m[1]) >= $fixedVersion;
}
return false;
};
// Hard coded list of PHP distributions with the fix backported.
if (
$compareDistroVersionPrefix('5.3.3-7+squeeze', 18) // Debian 6 (Squeeze)
|| $compareDistroVersionPrefix('5.4.4-14+deb7u', 7) // Debian 7 (Wheezy)
|| $compareDistroVersionPrefix('5.3.10-1ubuntu3.', 9) // Ubuntu 12.04 (Precise)
) {
return self::$useOpensslParse = true;
}
// Symfony Process component is missing so we assume it is unsafe at this point
if (!class_exists('Symfony\Component\Process\PhpProcess')) {
return self::$useOpensslParse = false;
}
// This is where things get crazy, because distros backport security
// fixes the chances are on NIX systems the fix has been applied but
// it's not possible to verify that from the PHP version.
//
// To verify exec a new PHP process and run the issue testcase with
// known safe input that replicates the bug.
// Based on testcase in https://github.com/php/php-src/commit/c1224573c773b6845e83505f717fbf820fc18415
// changes in https://github.com/php/php-src/commit/76a7fd893b7d6101300cc656058704a73254d593
$cert = 'LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVwRENDQTR5Z0F3SUJBZ0lKQUp6dThyNnU2ZUJjTUEwR0NTcUdTSWIzRFFFQkJRVUFNSUhETVFzd0NRWUQKVlFRR0V3SkVSVEVjTUJvR0ExVUVDQXdUVG05eVpISm9aV2x1TFZkbGMzUm1ZV3hsYmpFUU1BNEdBMVVFQnd3SApTOE9Ed3Jac2JqRVVNQklHQTFVRUNnd0xVMlZyZEdsdmJrVnBibk14SHpBZEJnTlZCQXNNRmsxaGJHbGphVzkxCmN5QkRaWEowSUZObFkzUnBiMjR4SVRBZkJnTlZCQU1NR0cxaGJHbGphVzkxY3k1elpXdDBhVzl1WldsdWN5NWsKWlRFcU1DZ0dDU3FHU0liM0RRRUpBUlliYzNSbFptRnVMbVZ6YzJWeVFITmxhM1JwYjI1bGFXNXpMbVJsTUhVWQpaREU1TnpBd01UQXhNREF3TURBd1dnQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBCkFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUEKQUFBQUFBQVhEVEUwTVRFeU9ERXhNemt6TlZvd2djTXhDekFKQmdOVkJBWVRBa1JGTVJ3d0dnWURWUVFJREJOTwpiM0prY21obGFXNHRWMlZ6ZEdaaGJHVnVNUkF3RGdZRFZRUUhEQWRMdzRQQ3RteHVNUlF3RWdZRFZRUUtEQXRUClpXdDBhVzl1UldsdWN6RWZNQjBHQTFVRUN3d1dUV0ZzYVdOcGIzVnpJRU5sY25RZ1UyVmpkR2x2YmpFaE1COEcKQTFVRUF3d1liV0ZzYVdOcGIzVnpMbk5sYTNScGIyNWxhVzV6TG1SbE1Tb3dLQVlKS29aSWh2Y05BUWtCRmh0egpkR1ZtWVc0dVpYTnpaWEpBYzJWcmRHbHZibVZwYm5NdVpHVXdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCCkR3QXdnZ0VLQW9JQkFRRERBZjNobDdKWTBYY0ZuaXlFSnBTU0RxbjBPcUJyNlFQNjV1c0pQUnQvOFBhRG9xQnUKd0VZVC9OYSs2ZnNnUGpDMHVLOURaZ1dnMnRIV1dvYW5TYmxBTW96NVBINlorUzRTSFJaN2UyZERJalBqZGhqaAowbUxnMlVNTzV5cDBWNzk3R2dzOWxOdDZKUmZIODFNTjJvYlhXczROdHp0TE11RDZlZ3FwcjhkRGJyMzRhT3M4CnBrZHVpNVVhd1Raa3N5NXBMUEhxNWNNaEZHbTA2djY1Q0xvMFYyUGQ5K0tBb2tQclBjTjVLTEtlYno3bUxwazYKU01lRVhPS1A0aWRFcXh5UTdPN2ZCdUhNZWRzUWh1K3ByWTNzaTNCVXlLZlF0UDVDWm5YMmJwMHdLSHhYMTJEWAoxbmZGSXQ5RGJHdkhUY3lPdU4rblpMUEJtM3ZXeG50eUlJdlZBZ01CQUFHalFqQkFNQWtHQTFVZEV3UUNNQUF3CkVRWUpZSVpJQVliNFFnRUJCQVFEQWdlQU1Bc0dBMVVkRHdRRUF3SUZvREFUQmdOVkhTVUVEREFLQmdnckJnRUYKQlFjREFqQU5CZ2txaGtpRzl3MEJBUVVGQUFPQ0FRRUFHMGZaWVlDVGJkajFYWWMrMVNub2FQUit2SThDOENhRAo4KzBVWWhkbnlVNGdnYTBCQWNEclk5ZTk0ZUVBdTZacXljRjZGakxxWFhkQWJvcHBXb2NyNlQ2R0QxeDMzQ2tsClZBcnpHL0t4UW9oR0QySmVxa2hJTWxEb214SE83a2EzOStPYThpMnZXTFZ5alU4QVp2V01BcnVIYTRFRU55RzcKbFcyQWFnYUZLRkNyOVRuWFRmcmR4R1ZFYnY3S1ZRNmJkaGc1cDVTanBXSDErTXEwM3VSM1pYUEJZZHlWODMxOQpvMGxWajFLRkkyRENML2xpV2lzSlJvb2YrMWNSMzVDdGQwd1lCY3BCNlRac2xNY09QbDc2ZHdLd0pnZUpvMlFnClpzZm1jMnZDMS9xT2xOdU5xLzBUenprVkd2OEVUVDNDZ2FVK1VYZTRYT1Z2a2NjZWJKbjJkZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K';
$script = <<<'EOT'
error_reporting(-1);
$info = openssl_x509_parse(base64_decode('%s'));
var_dump(PHP_VERSION, $info['issuer']['emailAddress'], $info['validFrom_time_t']);
EOT;
$script = '<'."?php\n".sprintf($script, $cert);
try {
$process = new PhpProcess($script);
$process->mustRun();
} catch (\Exception $e) {
// In the case of any exceptions just accept it is not possible to
// determine the safety of openssl_x509_parse and bail out.
return self::$useOpensslParse = false;
}
$output = preg_split('{\r?\n}', trim($process->getOutput()));
$errorOutput = trim($process->getErrorOutput());
if (
is_array($output)
&& count($output) === 3
&& $output[0] === sprintf('string(%d) "%s"', strlen(PHP_VERSION), PHP_VERSION)
&& $output[1] === 'string(27) "stefan.esser@sektioneins.de"'
&& $output[2] === 'int(-1)'
&& preg_match('{openssl_x509_parse\(\): illegal (?:ASN1 data type for|length in) timestamp in - on line \d+}', $errorOutput)
) {
// This PHP has the fix backported probably by a distro security team.
return self::$useOpensslParse = true;
}
return self::$useOpensslParse = false;
}
/**
* Resets the static caches
* @return void
*/
public static function reset()
{
self::$caFileValidity = array();
self::$caPath = null;
self::$useOpensslParse = null;
}
/**
* @param string $name
* @return string|false
*/
private static function getEnvVariable($name)
{
if (isset($_SERVER[$name])) {
return (string) $_SERVER[$name];
}
if (PHP_SAPI === 'cli' && ($value = getenv($name)) !== false && $value !== null) {
return (string) $value;
}
return false;
}
/**
* @param string|false $certFile
* @param LoggerInterface|null $logger
* @return bool
*/
private static function caFileUsable($certFile, LoggerInterface $logger = null)
{
return $certFile
&& static::isFile($certFile, $logger)
&& static::isReadable($certFile, $logger)
&& static::validateCaFile($certFile, $logger);
}
/**
* @param string|false $certDir
* @param LoggerInterface|null $logger
* @return bool
*/
private static function caDirUsable($certDir, LoggerInterface $logger = null)
{
return $certDir
&& static::isDir($certDir, $logger)
&& static::isReadable($certDir, $logger)
&& static::glob($certDir . '/*', $logger);
}
/**
* @param string $certFile
* @param LoggerInterface|null $logger
* @return bool
*/
private static function isFile($certFile, LoggerInterface $logger = null)
{
$isFile = @is_file($certFile);
if (!$isFile && $logger) {
$logger->debug(sprintf('Checked CA file %s does not exist or it is not a file.', $certFile));
}
return $isFile;
}
/**
* @param string $certDir
* @param LoggerInterface|null $logger
* @return bool
*/
private static function isDir($certDir, LoggerInterface $logger = null)
{
$isDir = @is_dir($certDir);
if (!$isDir && $logger) {
$logger->debug(sprintf('Checked directory %s does not exist or it is not a directory.', $certDir));
}
return $isDir;
}
/**
* @param string $certFileOrDir
* @param LoggerInterface|null $logger
* @return bool
*/
private static function isReadable($certFileOrDir, LoggerInterface $logger = null)
{
$isReadable = @is_readable($certFileOrDir);
if (!$isReadable && $logger) {
$logger->debug(sprintf('Checked file or directory %s is not readable.', $certFileOrDir));
}
return $isReadable;
}
/**
* @param string $pattern
* @param LoggerInterface|null $logger
* @return bool
*/
private static function glob($pattern, LoggerInterface $logger = null)
{
$certs = glob($pattern);
if ($certs === false) {
if ($logger) {
$logger->debug(sprintf("An error occurred while trying to find certificates for pattern: %s", $pattern));
}
return false;
}
if (count($certs) === 0) {
if ($logger) {
$logger->debug(sprintf("No CA files found for pattern: %s", $pattern));
}
return false;
}
return true;
}
}

1011
includes/vendor/composer/installed.json vendored Normal file

File diff suppressed because it is too large Load Diff

158
includes/vendor/composer/installed.php vendored Normal file
View File

@ -0,0 +1,158 @@
<?php return array(
'root' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'type' => 'project',
'install_path' => __DIR__ . '/../../../',
'aliases' => array(),
'reference' => '59464102ee27cb5c808625c92448a48cebefda6f',
'name' => 'yourls/yourls',
'dev' => true,
),
'versions' => array(
'aura/sql' => array(
'pretty_version' => '3.1.0',
'version' => '3.1.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../aura/sql',
'aliases' => array(),
'reference' => '88d8b8ed1bcfd588a649fdc7e7ac89ec047abbca',
'dev_requirement' => false,
),
'composer/ca-bundle' => array(
'pretty_version' => '1.3.1',
'version' => '1.3.1.0',
'type' => 'library',
'install_path' => __DIR__ . '/./ca-bundle',
'aliases' => array(),
'reference' => '4c679186f2aca4ab6a0f1b0b9cf9252decb44d0b',
'dev_requirement' => false,
),
'geoip2/geoip2' => array(
'pretty_version' => 'v2.12.2',
'version' => '2.12.2.0',
'type' => 'library',
'install_path' => __DIR__ . '/../geoip2/geoip2',
'aliases' => array(),
'reference' => '83adb44ac4b9553d36b579a14673ed124583082f',
'dev_requirement' => false,
),
'jakeasmith/http_build_url' => array(
'pretty_version' => '1.0.1',
'version' => '1.0.1.0',
'type' => 'library',
'install_path' => __DIR__ . '/../jakeasmith/http_build_url',
'aliases' => array(),
'reference' => '93c273e77cb1edead0cf8bcf8cd2003428e74e37',
'dev_requirement' => false,
),
'maxmind-db/reader' => array(
'pretty_version' => 'v1.11.0',
'version' => '1.11.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../maxmind-db/reader',
'aliases' => array(),
'reference' => 'b1f3c0699525336d09cc5161a2861268d9f2ae5b',
'dev_requirement' => false,
),
'maxmind/web-service-common' => array(
'pretty_version' => 'v0.9.0',
'version' => '0.9.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../maxmind/web-service-common',
'aliases' => array(),
'reference' => '4dc5a3e8df38aea4ca3b1096cee3a038094e9b53',
'dev_requirement' => false,
),
'ozh/bookmarkletgen' => array(
'pretty_version' => '1.2',
'version' => '1.2.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../ozh/bookmarkletgen',
'aliases' => array(),
'reference' => '3319b53c493a1474a03d1cc4e087617652284c20',
'dev_requirement' => false,
),
'pomo/pomo' => array(
'pretty_version' => 'v1.4.1',
'version' => '1.4.1.0',
'type' => 'library',
'install_path' => __DIR__ . '/../pomo/pomo',
'aliases' => array(),
'reference' => '1594bd1f90c89a45ffc3da2ee6d5d582bfac7542',
'dev_requirement' => false,
),
'psr/log' => array(
'pretty_version' => '1.1.4',
'version' => '1.1.4.0',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/log',
'aliases' => array(),
'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11',
'dev_requirement' => false,
),
'rmccue/requests' => array(
'pretty_version' => '2.0.2',
'version' => '2.0.2.0',
'type' => 'library',
'install_path' => __DIR__ . '/../rmccue/requests',
'aliases' => array(),
'reference' => 'b01ce7c91657d604f298751686d7f5eec87b9493',
'dev_requirement' => false,
),
'spatie/array-to-xml' => array(
'pretty_version' => '2.16.0',
'version' => '2.16.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../spatie/array-to-xml',
'aliases' => array(),
'reference' => 'db39308c5236b69b89cadc3f44f191704814eae2',
'dev_requirement' => false,
),
'symfony/polyfill-intl-idn' => array(
'pretty_version' => 'v1.25.0',
'version' => '1.25.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-intl-idn',
'aliases' => array(),
'reference' => '749045c69efb97c70d25d7463abba812e91f3a44',
'dev_requirement' => false,
),
'symfony/polyfill-intl-normalizer' => array(
'pretty_version' => 'v1.25.0',
'version' => '1.25.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-intl-normalizer',
'aliases' => array(),
'reference' => '8590a5f561694770bdcd3f9b5c69dde6945028e8',
'dev_requirement' => false,
),
'symfony/polyfill-mbstring' => array(
'pretty_version' => 'v1.25.0',
'version' => '1.25.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-mbstring',
'aliases' => array(),
'reference' => '0abb51d2f102e00a4eefcf46ba7fec406d245825',
'dev_requirement' => false,
),
'symfony/polyfill-php72' => array(
'pretty_version' => 'v1.25.0',
'version' => '1.25.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-php72',
'aliases' => array(),
'reference' => '9a142215a36a3888e30d0a9eeea9766764e96976',
'dev_requirement' => false,
),
'yourls/yourls' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'type' => 'project',
'install_path' => __DIR__ . '/../../../',
'aliases' => array(),
'reference' => '59464102ee27cb5c808625c92448a48cebefda6f',
'dev_requirement' => false,
),
),
);

View File

@ -0,0 +1,26 @@
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 70400)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.4.0". You are running ' . PHP_VERSION . '.';
}
if ($issues) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
} elseif (!headers_sent()) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
);
}

428
includes/vendor/geoip2/geoip2/README.md vendored Normal file
View File

@ -0,0 +1,428 @@
# GeoIP2 PHP API #
## Description ##
This package provides an API for the GeoIP2 and GeoLite2
[web services](https://dev.maxmind.com/geoip/docs/web-services?lang=en) and
[databases](https://dev.maxmind.com/geoip/docs/databases?lang=en).
## Install via Composer ##
We recommend installing this package with [Composer](https://getcomposer.org/).
### Download Composer ###
To download Composer, run in the root directory of your project:
```bash
curl -sS https://getcomposer.org/installer | php
```
You should now have the file `composer.phar` in your project directory.
### Install Dependencies ###
Run in your project root:
```sh
php composer.phar require geoip2/geoip2:~2.0
```
You should now have the files `composer.json` and `composer.lock` as well as
the directory `vendor` in your project directory. If you use a version control
system, `composer.json` should be added to it.
### Require Autoloader ###
After installing the dependencies, you need to require the Composer autoloader
from your code:
```php
require 'vendor/autoload.php';
```
## Install via Phar ##
Although we strongly recommend using Composer, we also provide a
[phar archive](https://php.net/manual/en/book.phar.php) containing most of the
dependencies for GeoIP2. Our latest phar archive is available on
[our releases page](https://github.com/maxmind/GeoIP2-php/releases).
### Install Dependencies ###
In order to use the phar archive, you must have the PHP
[Phar extension](https://php.net/manual/en/book.phar.php) installed and
enabled.
If you will be making web service requests, you must have the PHP
[cURL extension](https://php.net/manual/en/book.curl.php)
installed to use this archive. For Debian based distributions, this can
typically be found in the the `php-curl` package. For other operating
systems, please consult the relevant documentation. After installing the
extension you may need to restart your web server.
If you are missing this extension, you will see errors like the following:
```
PHP Fatal error: Uncaught Error: Call to undefined function MaxMind\WebService\curl_version()
```
### Require Package ###
To use the archive, just require it from your script:
```php
require 'geoip2.phar';
```
## Optional C Extension ##
The [MaxMind DB API](https://github.com/maxmind/MaxMind-DB-Reader-php)
includes an optional C extension that you may install to dramatically increase
the performance of lookups in GeoIP2 or GeoLite2 databases. To install, please
follow the instructions included with that API.
The extension has no effect on web-service lookups.
## IP Geolocation Usage ##
IP geolocation is inherently imprecise. Locations are often near the center of
the population. Any location provided by a GeoIP2 database or web service
should not be used to identify a particular address or household.
## Database Reader ##
### Usage ###
To use this API, you must create a new `\GeoIp2\Database\Reader` object with
the path to the database file as the first argument to the constructor. You
may then call the method corresponding to the database you are using.
If the lookup succeeds, the method call will return a model class for the
record in the database. This model in turn contains multiple container
classes for the different parts of the data such as the city in which the
IP address is located.
If the record is not found, a `\GeoIp2\Exception\AddressNotFoundException`
is thrown. If the database is invalid or corrupt, a
`\MaxMind\Db\InvalidDatabaseException` will be thrown.
See the API documentation for more details.
### City Example ###
```php
<?php
require_once 'vendor/autoload.php';
use GeoIp2\Database\Reader;
// This creates the Reader object, which should be reused across
// lookups.
$reader = new Reader('/usr/local/share/GeoIP/GeoIP2-City.mmdb');
// Replace "city" with the appropriate method for your database, e.g.,
// "country".
$record = $reader->city('128.101.101.101');
print($record->country->isoCode . "\n"); // 'US'
print($record->country->name . "\n"); // 'United States'
print($record->country->names['zh-CN'] . "\n"); // '美国'
print($record->mostSpecificSubdivision->name . "\n"); // 'Minnesota'
print($record->mostSpecificSubdivision->isoCode . "\n"); // 'MN'
print($record->city->name . "\n"); // 'Minneapolis'
print($record->postal->code . "\n"); // '55455'
print($record->location->latitude . "\n"); // 44.9733
print($record->location->longitude . "\n"); // -93.2323
print($record->traits->network . "\n"); // '128.101.101.101/32'
```
### Anonymous IP Example ###
```php
<?php
require_once 'vendor/autoload.php';
use GeoIp2\Database\Reader;
// This creates the Reader object, which should be reused across
// lookups.
$reader = new Reader('/usr/local/share/GeoIP/GeoIP2-Anonymous-IP.mmdb');
$record = $reader->anonymousIp('128.101.101.101');
if ($record->isAnonymous) { print "anon\n"; }
print($record->ipAddress . "\n"); // '128.101.101.101'
print($record->network . "\n"); // '128.101.101.101/32'
```
### Connection-Type Example ###
```php
<?php
require_once 'vendor/autoload.php';
use GeoIp2\Database\Reader;
// This creates the Reader object, which should be reused across
// lookups.
$reader = new Reader('/usr/local/share/GeoIP/GeoIP2-Connection-Type.mmdb');
$record = $reader->connectionType('128.101.101.101');
print($record->connectionType . "\n"); // 'Corporate'
print($record->ipAddress . "\n"); // '128.101.101.101'
print($record->network . "\n"); // '128.101.101.101/32'
```
### Domain Example ###
```php
<?php
require_once 'vendor/autoload.php';
use GeoIp2\Database\Reader;
// This creates the Reader object, which should be reused across
// lookups.
$reader = new Reader('/usr/local/share/GeoIP/GeoIP2-Domain.mmdb');
$record = $reader->domain('128.101.101.101');
print($record->domain . "\n"); // 'umn.edu'
print($record->ipAddress . "\n"); // '128.101.101.101'
print($record->network . "\n"); // '128.101.101.101/32'
```
### Enterprise Example ###
```php
<?php
require_once 'vendor/autoload.php';
use GeoIp2\Database\Reader;
// This creates the Reader object, which should be reused across
// lookups.
$reader = new Reader('/usr/local/share/GeoIP/GeoIP2-Enterprise.mmdb');
// Use the ->enterprise method to do a lookup in the Enterprise database
$record = $reader->enterprise('128.101.101.101');
print($record->country->confidence . "\n"); // 99
print($record->country->isoCode . "\n"); // 'US'
print($record->country->name . "\n"); // 'United States'
print($record->country->names['zh-CN'] . "\n"); // '美国'
print($record->mostSpecificSubdivision->confidence . "\n"); // 77
print($record->mostSpecificSubdivision->name . "\n"); // 'Minnesota'
print($record->mostSpecificSubdivision->isoCode . "\n"); // 'MN'
print($record->city->confidence . "\n"); // 60
print($record->city->name . "\n"); // 'Minneapolis'
print($record->postal->code . "\n"); // '55455'
print($record->location->accuracyRadius . "\n"); // 50
print($record->location->latitude . "\n"); // 44.9733
print($record->location->longitude . "\n"); // -93.2323
print($record->traits->network . "\n"); // '128.101.101.101/32'
```
### ISP Example ###
```php
<?php
require_once 'vendor/autoload.php';
use GeoIp2\Database\Reader;
// This creates the Reader object, which should be reused across
// lookups.
$reader = new Reader('/usr/local/share/GeoIP/GeoIP2-ISP.mmdb');
$record = $reader->isp('128.101.101.101');
print($record->autonomousSystemNumber . "\n"); // 217
print($record->autonomousSystemOrganization . "\n"); // 'University of Minnesota'
print($record->isp . "\n"); // 'University of Minnesota'
print($record->organization . "\n"); // 'University of Minnesota'
print($record->ipAddress . "\n"); // '128.101.101.101'
print($record->network . "\n"); // '128.101.101.101/32'
```
## Web Service Client ##
### Usage ###
To use this API, you must create a new `\GeoIp2\WebService\Client`
object with your `$accountId` and `$licenseKey`:
```php
$client = new Client(42, 'abcdef123456');
```
You may also call the constructor with additional arguments. The third argument
specifies the language preferences when using the `->name` method on the model
classes that this client creates. The fourth argument is additional options
such as `host` and `timeout`.
For instance, to call the GeoLite2 web service instead of GeoIP2 Precision:
```php
$client = new Client(42, 'abcdef123456', ['en'], ['host' => 'geolite.info']);
```
After creating the client, you may now call the method corresponding to a
specific endpoint with the IP address to look up, e.g.:
```php
$record = $client->city('128.101.101.101');
```
If the request succeeds, the method call will return a model class for the
endpoint you called. This model in turn contains multiple record classes, each
of which represents part of the data returned by the web service.
If there is an error, a structured exception is thrown.
See the API documentation for more details.
### Example ###
```php
<?php
require_once 'vendor/autoload.php';
use GeoIp2\WebService\Client;
// This creates a Client object that can be reused across requests.
// Replace "42" with your account ID and "license_key" with your license
// key. Set the "host" to "geolite.info" in the fourth argument options
// array to use the GeoLite2 web service instead of GeoIP2 Precision.
$client = new Client(42, 'abcdef123456');
// Replace "city" with the method corresponding to the web service that
// you are using, e.g., "country", "insights".
$record = $client->city('128.101.101.101');
print($record->country->isoCode . "\n"); // 'US'
print($record->country->name . "\n"); // 'United States'
print($record->country->names['zh-CN'] . "\n"); // '美国'
print($record->mostSpecificSubdivision->name . "\n"); // 'Minnesota'
print($record->mostSpecificSubdivision->isoCode . "\n"); // 'MN'
print($record->city->name . "\n"); // 'Minneapolis'
print($record->postal->code . "\n"); // '55455'
print($record->location->latitude . "\n"); // 44.9733
print($record->location->longitude . "\n"); // -93.2323
print($record->traits->network . "\n"); // '128.101.101.101/32'
```
## Values to use for Database or Array Keys ##
**We strongly discourage you from using a value from any `names` property as
a key in a database or array.**
These names may change between releases. Instead we recommend using one of the
following:
* `GeoIp2\Record\City` - `$city->geonameId`
* `GeoIp2\Record\Continent` - `$continent->code` or `$continent->geonameId`
* `GeoIp2\Record\Country` and `GeoIp2\Record\RepresentedCountry` -
`$country->isoCode` or `$country->geonameId`
* `GeoIp2\Record\Subdivision` - `$subdivision->isoCode` or `$subdivision->geonameId`
### What data is returned? ###
While many of the end points return the same basic records, the attributes
which can be populated vary between end points. In addition, while an end
point may offer a particular piece of data, MaxMind does not always have every
piece of data for any given IP address.
Because of these factors, it is possible for any end point to return a record
where some or all of the attributes are unpopulated.
See the
[GeoIP2 Precision web service docs](https://dev.maxmind.com/geoip/docs/web-services?lang=en)
for details on what data each end point may return.
The only piece of data which is always returned is the `ipAddress`
attribute in the `GeoIp2\Record\Traits` record.
## Integration with GeoNames ##
[GeoNames](https://www.geonames.org/) offers web services and downloadable
databases with data on geographical features around the world, including
populated places. They offer both free and paid premium data. Each
feature is unique identified by a `geonameId`, which is an integer.
Many of the records returned by the GeoIP2 web services and databases
include a `geonameId` property. This is the ID of a geographical feature
(city, region, country, etc.) in the GeoNames database.
Some of the data that MaxMind provides is also sourced from GeoNames. We
source things like place names, ISO codes, and other similar data from
the GeoNames premium data set.
## Reporting data problems ##
If the problem you find is that an IP address is incorrectly mapped,
please
[submit your correction to MaxMind](https://www.maxmind.com/en/correction).
If you find some other sort of mistake, like an incorrect spelling,
please check the [GeoNames site](https://www.geonames.org/) first. Once
you've searched for a place and found it on the GeoNames map view, there
are a number of links you can use to correct data ("move", "edit",
"alternate names", etc.). Once the correction is part of the GeoNames
data set, it will be automatically incorporated into future MaxMind
releases.
If you are a paying MaxMind customer and you're not sure where to submit
a correction, please
[contact MaxMind support](https://www.maxmind.com/en/support) for help.
## Other Support ##
Please report all issues with this code using the
[GitHub issue tracker](https://github.com/maxmind/GeoIP2-php/issues).
If you are having an issue with a MaxMind service that is not specific
to the client API, please see
[our support page](https://www.maxmind.com/en/support).
## Requirements ##
This library requires PHP 7.2 or greater.
This library also relies on the [MaxMind DB Reader](https://github.com/maxmind/MaxMind-DB-Reader-php).
## Contributing ##
Patches and pull requests are encouraged. All code should follow the PSR-2
style guidelines. Please include unit tests whenever possible. You may obtain
the test data for the maxmind-db folder by running `git submodule update
--init --recursive` or adding `--recursive` to your initial clone, or from
https://github.com/maxmind/MaxMind-DB
## Versioning ##
The GeoIP2 PHP API uses [Semantic Versioning](https://semver.org/).
## Copyright and License ##
This software is Copyright (c) 2013-2020 by MaxMind, Inc.
This is free software, licensed under the Apache License, Version 2.0.

View File

@ -0,0 +1,292 @@
<?php
declare(strict_types=1);
namespace GeoIp2\Database;
use GeoIp2\Exception\AddressNotFoundException;
use GeoIp2\Model\AbstractModel;
use GeoIp2\ProviderInterface;
use MaxMind\Db\Reader as DbReader;
use MaxMind\Db\Reader\InvalidDatabaseException;
/**
* Instances of this class provide a reader for the GeoIP2 database format.
* IP addresses can be looked up using the database specific methods.
*
* ## Usage ##
*
* The basic API for this class is the same for every database. First, you
* create a reader object, specifying a file name. You then call the method
* corresponding to the specific database, passing it the IP address you want
* to look up.
*
* If the request succeeds, the method call will return a model class for
* the method you called. This model in turn contains multiple record classes,
* each of which represents part of the data returned by the database. If
* the database does not contain the requested information, the attributes
* on the record class will have a `null` value.
*
* If the address is not in the database, an
* {@link \GeoIp2\Exception\AddressNotFoundException} exception will be
* thrown. If an invalid IP address is passed to one of the methods, a
* SPL {@link \InvalidArgumentException} will be thrown. If the database is
* corrupt or invalid, a {@link \MaxMind\Db\Reader\InvalidDatabaseException}
* will be thrown.
*/
class Reader implements ProviderInterface
{
/**
* @var DbReader
*/
private $dbReader;
/**
* @var string
*/
private $dbType;
/**
* @var array<string>
*/
private $locales;
/**
* Constructor.
*
* @param string $filename the path to the GeoIP2 database file
* @param array $locales list of locale codes to use in name property
* from most preferred to least preferred
*
* @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
* is corrupt or invalid
*/
public function __construct(
string $filename,
array $locales = ['en']
) {
$this->dbReader = new DbReader($filename);
$this->dbType = $this->dbReader->metadata()->databaseType;
$this->locales = $locales;
}
/**
* This method returns a GeoIP2 City model.
*
* @param string $ipAddress an IPv4 or IPv6 address as a string
*
* @throws \GeoIp2\Exception\AddressNotFoundException if the address is
* not in the database
* @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
* is corrupt or invalid
*/
public function city(string $ipAddress): \GeoIp2\Model\City
{
// @phpstan-ignore-next-line
return $this->modelFor('City', 'City', $ipAddress);
}
/**
* This method returns a GeoIP2 Country model.
*
* @param string $ipAddress an IPv4 or IPv6 address as a string
*
* @throws \GeoIp2\Exception\AddressNotFoundException if the address is
* not in the database
* @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
* is corrupt or invalid
*/
public function country(string $ipAddress): \GeoIp2\Model\Country
{
// @phpstan-ignore-next-line
return $this->modelFor('Country', 'Country', $ipAddress);
}
/**
* This method returns a GeoIP2 Anonymous IP model.
*
* @param string $ipAddress an IPv4 or IPv6 address as a string
*
* @throws \GeoIp2\Exception\AddressNotFoundException if the address is
* not in the database
* @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
* is corrupt or invalid
*/
public function anonymousIp(string $ipAddress): \GeoIp2\Model\AnonymousIp
{
// @phpstan-ignore-next-line
return $this->flatModelFor(
'AnonymousIp',
'GeoIP2-Anonymous-IP',
$ipAddress
);
}
/**
* This method returns a GeoLite2 ASN model.
*
* @param string $ipAddress an IPv4 or IPv6 address as a string
*
* @throws \GeoIp2\Exception\AddressNotFoundException if the address is
* not in the database
* @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
* is corrupt or invalid
*/
public function asn(string $ipAddress): \GeoIp2\Model\Asn
{
// @phpstan-ignore-next-line
return $this->flatModelFor(
'Asn',
'GeoLite2-ASN',
$ipAddress
);
}
/**
* This method returns a GeoIP2 Connection Type model.
*
* @param string $ipAddress an IPv4 or IPv6 address as a string
*
* @throws \GeoIp2\Exception\AddressNotFoundException if the address is
* not in the database
* @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
* is corrupt or invalid
*/
public function connectionType(string $ipAddress): \GeoIp2\Model\ConnectionType
{
// @phpstan-ignore-next-line
return $this->flatModelFor(
'ConnectionType',
'GeoIP2-Connection-Type',
$ipAddress
);
}
/**
* This method returns a GeoIP2 Domain model.
*
* @param string $ipAddress an IPv4 or IPv6 address as a string
*
* @throws \GeoIp2\Exception\AddressNotFoundException if the address is
* not in the database
* @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
* is corrupt or invalid
*/
public function domain(string $ipAddress): \GeoIp2\Model\Domain
{
// @phpstan-ignore-next-line
return $this->flatModelFor(
'Domain',
'GeoIP2-Domain',
$ipAddress
);
}
/**
* This method returns a GeoIP2 Enterprise model.
*
* @param string $ipAddress an IPv4 or IPv6 address as a string
*
* @throws \GeoIp2\Exception\AddressNotFoundException if the address is
* not in the database
* @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
* is corrupt or invalid
*/
public function enterprise(string $ipAddress): \GeoIp2\Model\Enterprise
{
// @phpstan-ignore-next-line
return $this->modelFor('Enterprise', 'Enterprise', $ipAddress);
}
/**
* This method returns a GeoIP2 ISP model.
*
* @param string $ipAddress an IPv4 or IPv6 address as a string
*
* @throws \GeoIp2\Exception\AddressNotFoundException if the address is
* not in the database
* @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
* is corrupt or invalid
*/
public function isp(string $ipAddress): \GeoIp2\Model\Isp
{
// @phpstan-ignore-next-line
return $this->flatModelFor(
'Isp',
'GeoIP2-ISP',
$ipAddress
);
}
private function modelFor(string $class, string $type, string $ipAddress): AbstractModel
{
[$record, $prefixLen] = $this->getRecord($class, $type, $ipAddress);
$record['traits']['ip_address'] = $ipAddress;
$record['traits']['prefix_len'] = $prefixLen;
$class = 'GeoIp2\\Model\\' . $class;
return new $class($record, $this->locales);
}
private function flatModelFor(string $class, string $type, string $ipAddress): AbstractModel
{
[$record, $prefixLen] = $this->getRecord($class, $type, $ipAddress);
$record['ip_address'] = $ipAddress;
$record['prefix_len'] = $prefixLen;
$class = 'GeoIp2\\Model\\' . $class;
return new $class($record);
}
private function getRecord(string $class, string $type, string $ipAddress): array
{
if (strpos($this->dbType, $type) === false) {
$method = lcfirst($class);
throw new \BadMethodCallException(
"The $method method cannot be used to open a {$this->dbType} database"
);
}
[$record, $prefixLen] = $this->dbReader->getWithPrefixLen($ipAddress);
if ($record === null) {
throw new AddressNotFoundException(
"The address $ipAddress is not in the database."
);
}
if (!\is_array($record)) {
// This can happen on corrupt databases. Generally,
// MaxMind\Db\Reader will throw a
// MaxMind\Db\Reader\InvalidDatabaseException, but occasionally
// the lookup may result in a record that looks valid but is not
// an array. This mostly happens when the user is ignoring all
// exceptions and the more frequent InvalidDatabaseException
// exceptions go unnoticed.
throw new InvalidDatabaseException(
"Expected an array when looking up $ipAddress but received: "
. \gettype($record)
);
}
return [$record, $prefixLen];
}
/**
* @throws \InvalidArgumentException if arguments are passed to the method
* @throws \BadMethodCallException if the database has been closed
*
* @return \MaxMind\Db\Reader\Metadata object for the database
*/
public function metadata(): DbReader\Metadata
{
return $this->dbReader->metadata();
}
/**
* Closes the GeoIP2 database and returns the resources to the system.
*/
public function close(): void
{
$this->dbReader->close();
}
}

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace GeoIp2\Exception;
/**
* This class represents a generic error.
*/
class AddressNotFoundException extends GeoIp2Exception
{
}

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace GeoIp2\Exception;
/**
* This class represents a generic error.
*/
class AuthenticationException extends GeoIp2Exception
{
}

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace GeoIp2\Exception;
/**
* This class represents a generic error.
*/
class GeoIp2Exception extends \Exception
{
}

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace GeoIp2\Exception;
/**
* This class represents an HTTP transport error.
*/
class HttpException extends GeoIp2Exception
{
/**
* The URI queried.
*
* @var string
*/
public $uri;
public function __construct(
string $message,
int $httpStatus,
string $uri,
\Exception $previous = null
) {
$this->uri = $uri;
parent::__construct($message, $httpStatus, $previous);
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace GeoIp2\Exception;
/**
* This class represents an error returned by MaxMind's GeoIP2
* web service.
*/
class InvalidRequestException extends HttpException
{
/**
* The code returned by the MaxMind web service.
*
* @var string
*/
public $error;
public function __construct(
string $message,
string $error,
int $httpStatus,
string $uri,
\Exception $previous = null
) {
$this->error = $error;
parent::__construct($message, $httpStatus, $uri, $previous);
}
}

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace GeoIp2\Exception;
/**
* This class represents a generic error.
*/
class OutOfQueriesException extends GeoIp2Exception
{
}

View File

@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace GeoIp2\Model;
/**
* @ignore
*/
abstract class AbstractModel implements \JsonSerializable
{
/**
* @var array<string, mixed>
*/
protected $raw;
/**
* @ignore
*/
public function __construct(array $raw)
{
$this->raw = $raw;
}
/**
* @ignore
*
* @return mixed
*/
protected function get(string $field)
{
if (isset($this->raw[$field])) {
return $this->raw[$field];
}
if (preg_match('/^is_/', $field)) {
return false;
}
return null;
}
/**
* @ignore
*
* @return mixed
*/
public function __get(string $attr)
{
if ($attr !== 'instance' && property_exists($this, $attr)) {
return $this->{$attr};
}
throw new \RuntimeException("Unknown attribute: $attr");
}
/**
* @ignore
*/
public function __isset(string $attr): bool
{
return $attr !== 'instance' && isset($this->{$attr});
}
public function jsonSerialize(): array
{
return $this->raw;
}
}

View File

@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace GeoIp2\Model;
use GeoIp2\Util;
/**
* This class provides the GeoIP2 Anonymous IP model.
*
* @property-read bool $isAnonymous This is true if the IP address belongs to
* any sort of anonymous network.
* @property-read bool $isAnonymousVpn This is true if the IP address is
* registered to an anonymous VPN provider. If a VPN provider does not
* register subnets under names associated with them, we will likely only
* flag their IP ranges using the isHostingProvider property.
* @property-read bool $isHostingProvider This is true if the IP address belongs
* to a hosting or VPN provider (see description of isAnonymousVpn property).
* @property-read bool $isPublicProxy This is true if the IP address belongs to
* a public proxy.
* @property-read bool $isResidentialProxy This is true if the IP address is
* on a suspected anonymizing network and belongs to a residential ISP.
* @property-read bool $isTorExitNode This is true if the IP address is a Tor
* exit node.
* @property-read string $ipAddress The IP address that the data in the model is
* for.
* @property-read string $network The network in CIDR notation associated with
* the record. In particular, this is the largest network where all of the
* fields besides $ipAddress have the same value.
*/
class AnonymousIp extends AbstractModel
{
/**
* @var bool
*/
protected $isAnonymous;
/**
* @var bool
*/
protected $isAnonymousVpn;
/**
* @var bool
*/
protected $isHostingProvider;
/**
* @var bool
*/
protected $isPublicProxy;
/**
* @var bool
*/
protected $isResidentialProxy;
/**
* @var bool
*/
protected $isTorExitNode;
/**
* @var string
*/
protected $ipAddress;
/**
* @var string
*/
protected $network;
/**
* @ignore
*/
public function __construct(array $raw)
{
parent::__construct($raw);
$this->isAnonymous = $this->get('is_anonymous');
$this->isAnonymousVpn = $this->get('is_anonymous_vpn');
$this->isHostingProvider = $this->get('is_hosting_provider');
$this->isPublicProxy = $this->get('is_public_proxy');
$this->isResidentialProxy = $this->get('is_residential_proxy');
$this->isTorExitNode = $this->get('is_tor_exit_node');
$ipAddress = $this->get('ip_address');
$this->ipAddress = $ipAddress;
$this->network = Util::cidr($ipAddress, $this->get('prefix_len'));
}
}

View File

@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace GeoIp2\Model;
use GeoIp2\Util;
/**
* This class provides the GeoLite2 ASN model.
*
* @property-read int|null $autonomousSystemNumber The autonomous system number
* associated with the IP address.
* @property-read string|null $autonomousSystemOrganization The organization
* associated with the registered autonomous system number for the IP
* address.
* @property-read string $ipAddress The IP address that the data in the model is
* for.
* @property-read string $network The network in CIDR notation associated with
* the record. In particular, this is the largest network where all of the
* fields besides $ipAddress have the same value.
*/
class Asn extends AbstractModel
{
/**
* @var int|null
*/
protected $autonomousSystemNumber;
/**
* @var string|null
*/
protected $autonomousSystemOrganization;
/**
* @var string
*/
protected $ipAddress;
/**
* @var string
*/
protected $network;
/**
* @ignore
*/
public function __construct(array $raw)
{
parent::__construct($raw);
$this->autonomousSystemNumber = $this->get('autonomous_system_number');
$this->autonomousSystemOrganization =
$this->get('autonomous_system_organization');
$ipAddress = $this->get('ip_address');
$this->ipAddress = $ipAddress;
$this->network = Util::cidr($ipAddress, $this->get('prefix_len'));
}
}

View File

@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
namespace GeoIp2\Model;
/**
* Model class for the data returned by GeoIP2 City web service and database.
*
* The only difference between the City and Insights model classes is which
* fields in each record may be populated. See
* https://dev.maxmind.com/geoip/docs/web-services?lang=en for more details.
*
* @property-read \GeoIp2\Record\City $city City data for the requested IP
* address.
* @property-read \GeoIp2\Record\Location $location Location data for the
* requested IP address.
* @property-read \GeoIp2\Record\Postal $postal Postal data for the
* requested IP address.
* @property-read array $subdivisions An array \GeoIp2\Record\Subdivision
* objects representing the country subdivisions for the requested IP
* address. The number and type of subdivisions varies by country, but a
* subdivision is typically a state, province, county, etc. Subdivisions
* are ordered from most general (largest) to most specific (smallest).
* If the response did not contain any subdivisions, this method returns
* an empty array.
* @property-read \GeoIp2\Record\Subdivision $mostSpecificSubdivision An object
* representing the most specific subdivision returned. If the response
* did not contain any subdivisions, this method returns an empty
* \GeoIp2\Record\Subdivision object.
*/
class City extends Country
{
/**
* @ignore
*
* @var \GeoIp2\Record\City
*/
protected $city;
/**
* @ignore
*
* @var \GeoIp2\Record\Location
*/
protected $location;
/**
* @ignore
*
* @var \GeoIp2\Record\Postal
*/
protected $postal;
/**
* @ignore
*
* @var array<\GeoIp2\Record\Subdivision>
*/
protected $subdivisions = [];
/**
* @ignore
*/
public function __construct(array $raw, array $locales = ['en'])
{
parent::__construct($raw, $locales);
$this->city = new \GeoIp2\Record\City($this->get('city'), $locales);
$this->location = new \GeoIp2\Record\Location($this->get('location'));
$this->postal = new \GeoIp2\Record\Postal($this->get('postal'));
$this->createSubdivisions($raw, $locales);
}
private function createSubdivisions(array $raw, array $locales): void
{
if (!isset($raw['subdivisions'])) {
return;
}
foreach ($raw['subdivisions'] as $sub) {
$this->subdivisions[] =
new \GeoIp2\Record\Subdivision($sub, $locales)
;
}
}
/**
* @ignore
*
* @return mixed
*/
public function __get(string $attr)
{
if ($attr === 'mostSpecificSubdivision') {
return $this->{$attr}();
}
return parent::__get($attr);
}
/**
* @ignore
*/
public function __isset(string $attr): bool
{
if ($attr === 'mostSpecificSubdivision') {
// We always return a mostSpecificSubdivision, even if it is the
// empty subdivision
return true;
}
return parent::__isset($attr);
}
private function mostSpecificSubdivision(): \GeoIp2\Record\Subdivision
{
return empty($this->subdivisions) ?
new \GeoIp2\Record\Subdivision([], $this->locales) :
end($this->subdivisions);
}
}

View File

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace GeoIp2\Model;
use GeoIp2\Util;
/**
* This class provides the GeoIP2 Connection-Type model.
*
* @property-read string|null $connectionType The connection type may take the
* following values: "Dialup", "Cable/DSL", "Corporate", "Cellular".
* Additional values may be added in the future.
* @property-read string $ipAddress The IP address that the data in the model is
* for.
* @property-read string $network The network in CIDR notation associated with
* the record. In particular, this is the largest network where all of the
* fields besides $ipAddress have the same value.
*/
class ConnectionType extends AbstractModel
{
/**
* @var string|null
*/
protected $connectionType;
/**
* @var string
*/
protected $ipAddress;
/**
* @var string
*/
protected $network;
/**
* @ignore
*/
public function __construct(array $raw)
{
parent::__construct($raw);
$this->connectionType = $this->get('connection_type');
$ipAddress = $this->get('ip_address');
$this->ipAddress = $ipAddress;
$this->network = Util::cidr($ipAddress, $this->get('prefix_len'));
}
}

View File

@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
namespace GeoIp2\Model;
/**
* Model class for the data returned by GeoIP2 Country web service and database.
*
* The only difference between the City and Insights model classes is which
* fields in each record may be populated. See
* https://dev.maxmind.com/geoip/docs/web-services?lang=en for more details.
*
* @property-read \GeoIp2\Record\Continent $continent Continent data for the
* requested IP address.
* @property-read \GeoIp2\Record\Country $country Country data for the requested
* IP address. This object represents the country where MaxMind believes the
* end user is located.
* @property-read \GeoIp2\Record\MaxMind $maxmind Data related to your MaxMind
* account.
* @property-read \GeoIp2\Record\Country $registeredCountry Registered country
* data for the requested IP address. This record represents the country
* where the ISP has registered a given IP block and may differ from the
* user's country.
* @property-read \GeoIp2\Record\RepresentedCountry $representedCountry
* Represented country data for the requested IP address. The represented
* country is used for things like military bases. It is only present when
* the represented country differs from the country.
* @property-read \GeoIp2\Record\Traits $traits Data for the traits of the
* requested IP address.
* @property-read array $raw The raw data from the web service.
*/
class Country extends AbstractModel
{
/**
* @var \GeoIp2\Record\Continent
*/
protected $continent;
/**
* @var \GeoIp2\Record\Country
*/
protected $country;
/**
* @var array<string>
*/
protected $locales;
/**
* @var \GeoIp2\Record\MaxMind
*/
protected $maxmind;
/**
* @var \GeoIp2\Record\Country
*/
protected $registeredCountry;
/**
* @var \GeoIp2\Record\RepresentedCountry
*/
protected $representedCountry;
/**
* @var \GeoIp2\Record\Traits
*/
protected $traits;
/**
* @ignore
*/
public function __construct(array $raw, array $locales = ['en'])
{
parent::__construct($raw);
$this->continent = new \GeoIp2\Record\Continent(
$this->get('continent'),
$locales
);
$this->country = new \GeoIp2\Record\Country(
$this->get('country'),
$locales
);
$this->maxmind = new \GeoIp2\Record\MaxMind($this->get('maxmind'));
$this->registeredCountry = new \GeoIp2\Record\Country(
$this->get('registered_country'),
$locales
);
$this->representedCountry = new \GeoIp2\Record\RepresentedCountry(
$this->get('represented_country'),
$locales
);
$this->traits = new \GeoIp2\Record\Traits($this->get('traits'));
$this->locales = $locales;
}
}

View File

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace GeoIp2\Model;
use GeoIp2\Util;
/**
* This class provides the GeoIP2 Domain model.
*
* @property-read string|null $domain The second level domain associated with the
* IP address. This will be something like "example.com" or
* "example.co.uk", not "foo.example.com".
* @property-read string $ipAddress The IP address that the data in the model is
* for.
* @property-read string $network The network in CIDR notation associated with
* the record. In particular, this is the largest network where all of the
* fields besides $ipAddress have the same value.
*/
class Domain extends AbstractModel
{
/**
* @var string|null
*/
protected $domain;
/**
* @var string
*/
protected $ipAddress;
/**
* @var string
*/
protected $network;
/**
* @ignore
*/
public function __construct(array $raw)
{
parent::__construct($raw);
$this->domain = $this->get('domain');
$ipAddress = $this->get('ip_address');
$this->ipAddress = $ipAddress;
$this->network = Util::cidr($ipAddress, $this->get('prefix_len'));
}
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace GeoIp2\Model;
/**
* Model class for the data returned by GeoIP2 Enterprise database lookups.
*
* The only difference between the City and Enterprise model classes is which
* fields in each record may be populated. See
* https://dev.maxmind.com/geoip/docs/web-services?lang=en for more details.
*/
class Enterprise extends City
{
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace GeoIp2\Model;
/**
* Model class for the data returned by GeoIP2 Precision: Insights web service.
*
* The only difference between the City and Insights model classes is which
* fields in each record may be populated. See
* https://dev.maxmind.com/geoip/docs/web-services?lang=en for more details.
*/
class Insights extends City
{
}

View File

@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
namespace GeoIp2\Model;
use GeoIp2\Util;
/**
* This class provides the GeoIP2 ISP model.
*
* @property-read int|null $autonomousSystemNumber The autonomous system number
* associated with the IP address.
* @property-read string|null $autonomousSystemOrganization The organization
* associated with the registered autonomous system number for the IP
* address.
* @property-read string|null $isp The name of the ISP associated with the IP
* address.
* @property-read string|null $mobileCountryCode The [mobile country code
* (MCC)](https://en.wikipedia.org/wiki/Mobile_country_code) associated with
* the IP address and ISP.
* @property-read string|null $mobileNetworkCode The [mobile network code
* (MNC)](https://en.wikipedia.org/wiki/Mobile_country_code) associated with
* the IP address and ISP.
* @property-read string|null $organization The name of the organization associated
* with the IP address.
* @property-read string $ipAddress The IP address that the data in the model is
* for.
* @property-read string $network The network in CIDR notation associated with
* the record. In particular, this is the largest network where all of the
* fields besides $ipAddress have the same value.
*/
class Isp extends AbstractModel
{
/**
* @var int|null
*/
protected $autonomousSystemNumber;
/**
* @var string|null
*/
protected $autonomousSystemOrganization;
/**
* @var string|null
*/
protected $isp;
/**
* @var string|null
*/
protected $mobileCountryCode;
/**
* @var string|null
*/
protected $mobileNetworkCode;
/**
* @var string|null
*/
protected $organization;
/**
* @var string
*/
protected $ipAddress;
/**
* @var string
*/
protected $network;
/**
* @ignore
*/
public function __construct(array $raw)
{
parent::__construct($raw);
$this->autonomousSystemNumber = $this->get('autonomous_system_number');
$this->autonomousSystemOrganization =
$this->get('autonomous_system_organization');
$this->isp = $this->get('isp');
$this->mobileCountryCode = $this->get('mobile_country_code');
$this->mobileNetworkCode = $this->get('mobile_network_code');
$this->organization = $this->get('organization');
$ipAddress = $this->get('ip_address');
$this->ipAddress = $ipAddress;
$this->network = Util::cidr($ipAddress, $this->get('prefix_len'));
}
}

View File

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace GeoIp2;
interface ProviderInterface
{
/**
* @param string $ipAddress an IPv4 or IPv6 address to lookup
*
* @return \GeoIp2\Model\Country a Country model for the requested IP address
*/
public function country(string $ipAddress): Model\Country;
/**
* @param string $ipAddress an IPv4 or IPv6 address to lookup
*
* @return \GeoIp2\Model\City a City model for the requested IP address
*/
public function city(string $ipAddress): Model\City;
}

View File

@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace GeoIp2\Record;
abstract class AbstractPlaceRecord extends AbstractRecord
{
/**
* @var array<string>
*/
private $locales;
/**
* @ignore
*/
public function __construct(?array $record, array $locales = ['en'])
{
$this->locales = $locales;
parent::__construct($record);
}
/**
* @ignore
*
* @return mixed
*/
public function __get(string $attr)
{
if ($attr === 'name') {
return $this->name();
}
return parent::__get($attr);
}
/**
* @ignore
*/
public function __isset(string $attr): bool
{
if ($attr === 'name') {
return $this->firstSetNameLocale() !== null;
}
return parent::__isset($attr);
}
private function name(): ?string
{
$locale = $this->firstSetNameLocale();
// @phpstan-ignore-next-line
return $locale === null ? null : $this->names[$locale];
}
private function firstSetNameLocale(): ?string
{
foreach ($this->locales as $locale) {
// @phpstan-ignore-next-line
if (isset($this->names[$locale])) {
return $locale;
}
}
return null;
}
}

View File

@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace GeoIp2\Record;
abstract class AbstractRecord implements \JsonSerializable
{
/**
* @var array<string, mixed>
*/
private $record;
/**
* @ignore
*/
public function __construct(?array $record)
{
$this->record = isset($record) ? $record : [];
}
/**
* @ignore
*
* @return mixed
*/
public function __get(string $attr)
{
// XXX - kind of ugly but greatly reduces boilerplate code
$key = $this->attributeToKey($attr);
if ($this->__isset($attr)) {
return $this->record[$key];
}
if ($this->validAttribute($attr)) {
if (preg_match('/^is_/', $key)) {
return false;
}
return null;
}
throw new \RuntimeException("Unknown attribute: $attr");
}
public function __isset(string $attr): bool
{
return $this->validAttribute($attr)
&& isset($this->record[$this->attributeToKey($attr)]);
}
private function attributeToKey(string $attr): string
{
return strtolower(preg_replace('/([A-Z])/', '_\1', $attr));
}
private function validAttribute(string $attr): bool
{
// @phpstan-ignore-next-line
return \in_array($attr, $this->validAttributes, true);
}
public function jsonSerialize(): ?array
{
return $this->record;
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace GeoIp2\Record;
/**
* City-level data associated with an IP address.
*
* This record is returned by all location services and databases besides
* Country.
*
* @property-read int|null $confidence A value from 0-100 indicating MaxMind's
* confidence that the city is correct. This attribute is only available
* from the Insights service and the GeoIP2 Enterprise database.
* @property-read int|null $geonameId The GeoName ID for the city. This attribute
* is returned by all location services and databases.
* @property-read string|null $name The name of the city based on the locales list
* passed to the constructor. This attribute is returned by all location
* services and databases.
* @property-read array|null $names A array map where the keys are locale codes
* and the values are names. This attribute is returned by all location
* services and databases.
*/
class City extends AbstractPlaceRecord
{
/**
* @ignore
*
* @var array<string>
*/
protected $validAttributes = ['confidence', 'geonameId', 'names'];
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace GeoIp2\Record;
/**
* Contains data for the continent record associated with an IP address.
*
* This record is returned by all location services and databases.
*
* @property-read string|null $code A two character continent code like "NA" (North
* America) or "OC" (Oceania). This attribute is returned by all location
* services and databases.
* @property-read int|null $geonameId The GeoName ID for the continent. This
* attribute is returned by all location services and databases.
* @property-read string|null $name Returns the name of the continent based on the
* locales list passed to the constructor. This attribute is returned by all location
* services and databases.
* @property-read array|null $names An array map where the keys are locale codes
* and the values are names. This attribute is returned by all location
* services and databases.
*/
class Continent extends AbstractPlaceRecord
{
/**
* @ignore
*
* @var array<string>
*/
protected $validAttributes = [
'code',
'geonameId',
'names',
];
}

View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace GeoIp2\Record;
/**
* Contains data for the country record associated with an IP address.
*
* This record is returned by all location services and databases.
*
* @property-read int|null $confidence A value from 0-100 indicating MaxMind's
* confidence that the country is correct. This attribute is only available
* from the Insights service and the GeoIP2 Enterprise database.
* @property-read int|null $geonameId The GeoName ID for the country. This
* attribute is returned by all location services and databases.
* @property-read bool $isInEuropeanUnion This is true if the country is a
* member state of the European Union. This attribute is returned by all
* location services and databases.
* @property-read string|null $isoCode The two-character ISO 3166-1 alpha code
* for the country. See https://en.wikipedia.org/wiki/ISO_3166-1. This
* attribute is returned by all location services and databases.
* @property-read string|null $name The name of the country based on the locales
* list passed to the constructor. This attribute is returned by all location
* services and databases.
* @property-read array|null $names An array map where the keys are locale codes
* and the values are names. This attribute is returned by all location
* services and databases.
*/
class Country extends AbstractPlaceRecord
{
/**
* @ignore
*
* @var array<string>
*/
protected $validAttributes = [
'confidence',
'geonameId',
'isInEuropeanUnion',
'isoCode',
'names',
];
}

View File

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace GeoIp2\Record;
/**
* Contains data for the location record associated with an IP address.
*
* This record is returned by all location services and databases besides
* Country.
*
* @property-read int|null $averageIncome The average income in US dollars
* associated with the requested IP address. This attribute is only available
* from the Insights service.
* @property-read int|null $accuracyRadius The approximate accuracy radius in
* kilometers around the latitude and longitude for the IP address. This is
* the radius where we have a 67% confidence that the device using the IP
* address resides within the circle centered at the latitude and longitude
* with the provided radius.
* @property-read float|null $latitude The approximate latitude of the location
* associated with the IP address. This value is not precise and should not be
* used to identify a particular address or household.
* @property-read float|null $longitude The approximate longitude of the location
* associated with the IP address. This value is not precise and should not be
* used to identify a particular address or household.
* @property-read int|null $populationDensity The estimated population per square
* kilometer associated with the IP address. This attribute is only available
* from the Insights service.
* @property-read int|null $metroCode The metro code of the location if the location
* is in the US. MaxMind returns the same metro codes as the
* Google AdWords API. See
* https://developers.google.com/adwords/api/docs/appendix/cities-DMAregions.
* @property-read string|null $timeZone The time zone associated with location, as
* specified by the IANA Time Zone Database, e.g., "America/New_York". See
* https://www.iana.org/time-zones.
*/
class Location extends AbstractRecord
{
/**
* @ignore
*
* @var array<string>
*/
protected $validAttributes = [
'averageIncome',
'accuracyRadius',
'latitude',
'longitude',
'metroCode',
'populationDensity',
'postalCode',
'postalConfidence',
'timeZone',
];
}

View File

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace GeoIp2\Record;
/**
* Contains data about your account.
*
* This record is returned by all location services and databases.
*
* @property-read int|null $queriesRemaining The number of remaining queries you
* have for the service you are calling.
*/
class MaxMind extends AbstractRecord
{
/**
* @ignore
*
* @var array<string>
*/
protected $validAttributes = ['queriesRemaining'];
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace GeoIp2\Record;
/**
* Contains data for the postal record associated with an IP address.
*
* This record is returned by all location databases and services besides
* Country.
*
* @property-read string|null $code The postal code of the location. Postal codes
* are not available for all countries. In some countries, this will only
* contain part of the postal code. This attribute is returned by all location
* databases and services besides Country.
* @property-read int|null $confidence A value from 0-100 indicating MaxMind's
* confidence that the postal code is correct. This attribute is only
* available from the Insights service and the GeoIP2 Enterprise
* database.
*/
class Postal extends AbstractRecord
{
/**
* @ignore
*
* @var array<string>
*/
protected $validAttributes = ['code', 'confidence'];
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace GeoIp2\Record;
/**
* Contains data for the represented country associated with an IP address.
*
* This class contains the country-level data associated with an IP address
* for the IP's represented country. The represented country is the country
* represented by something like a military base.
*
* @property-read string|null $type A string indicating the type of entity that is
* representing the country. Currently we only return <code>military</code>
* but this could expand to include other types in the future.
*/
class RepresentedCountry extends Country
{
/**
* @ignore
*
* @var array<string>
*/
protected $validAttributes = [
'confidence',
'geonameId',
'isInEuropeanUnion',
'isoCode',
'names',
'type',
];
}

View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace GeoIp2\Record;
/**
* Contains data for the subdivisions associated with an IP address.
*
* This record is returned by all location databases and services besides
* Country.
*
* @property-read int|null $confidence This is a value from 0-100 indicating
* MaxMind's confidence that the subdivision is correct. This attribute is
* only available from the Insights service and the GeoIP2 Enterprise
* database.
* @property-read int|null $geonameId This is a GeoName ID for the subdivision.
* This attribute is returned by all location databases and services besides
* Country.
* @property-read string|null $isoCode This is a string up to three characters long
* contain the subdivision portion of the ISO 3166-2 code. See
* https://en.wikipedia.org/wiki/ISO_3166-2. This attribute is returned by all
* location databases and services except Country.
* @property-read string|null $name The name of the subdivision based on the
* locales list passed to the constructor. This attribute is returned by all
* location databases and services besides Country.
* @property-read array|null $names An array map where the keys are locale codes
* and the values are names. This attribute is returned by all location
* databases and services besides Country.
*/
class Subdivision extends AbstractPlaceRecord
{
/**
* @ignore
*
* @var array<string>
*/
protected $validAttributes = [
'confidence',
'geonameId',
'isoCode',
'names',
];
}

View File

@ -0,0 +1,158 @@
<?php
declare(strict_types=1);
namespace GeoIp2\Record;
use GeoIp2\Util;
/**
* Contains data for the traits record associated with an IP address.
*
* This record is returned by all location services and databases.
*
* @property-read int|null $autonomousSystemNumber The autonomous system number
* associated with the IP address. See
* https://en.wikipedia.org/wiki/Autonomous_system_(Internet%29. This attribute
* is only available from the City and Insights web service and the GeoIP2
* Enterprise database.
* @property-read string|null $autonomousSystemOrganization The organization
* associated with the registered autonomous system number for the IP address.
* See https://en.wikipedia.org/wiki/Autonomous_system_(Internet%29. This
* attribute is only available from the City and Insights web service and the
* GeoIP2 Enterprise database.
* @property-read string|null $connectionType The connection type may take the
* following values: "Dialup", "Cable/DSL", "Corporate", "Cellular".
* Additional values may be added in the future. This attribute is only
* available in the GeoIP2 Enterprise database.
* @property-read string|null $domain The second level domain associated with the
* IP address. This will be something like "example.com" or "example.co.uk",
* not "foo.example.com". This attribute is only available from the
* City and Insights web service and the GeoIP2 Enterprise
* database.
* @property-read string $ipAddress The IP address that the data in the model
* is for. If you performed a "me" lookup against the web service, this
* will be the externally routable IP address for the system the code is
* running on. If the system is behind a NAT, this may differ from the IP
* address locally assigned to it. This attribute is returned by all end
* points.
* @property-read bool $isAnonymous This is true if the IP address belongs to
* any sort of anonymous network. This property is only available from GeoIP2
* Precision Insights.
* @property-read bool $isAnonymousProxy *Deprecated.* Please see our GeoIP2
* Anonymous IP database
* (https://www.maxmind.com/en/geoip2-anonymous-ip-database) to determine
* whether the IP address is used by an anonymizing service.
* @property-read bool $isAnonymousVpn This is true if the IP address is
* registered to an anonymous VPN provider. If a VPN provider does not register
* subnets under names associated with them, we will likely only flag their IP
* ranges using the isHostingProvider property. This property is only available
* from GeoIP2 Precision Insights.
* @property-read bool $isHostingProvider This is true if the IP address belongs
* to a hosting or VPN provider (see description of isAnonymousVpn property).
* This property is only available from GeoIP2 Precision Insights.
* @property-read bool $isLegitimateProxy This attribute is true if MaxMind
* believes this IP address to be a legitimate proxy, such as an internal
* VPN used by a corporation. This attribute is only available in the GeoIP2
* Enterprise database.
* @property-read bool $isPublicProxy This is true if the IP address belongs to
* a public proxy. This property is only available from GeoIP2 Precision
* Insights.
* @property-read bool $isResidentialProxy This is true if the IP address is
* on a suspected anonymizing network and belongs to a residential ISP. This
* property is only available from GeoIP2 Precision Insights.
* @property-read bool $isSatelliteProvider *Deprecated.* Due to the
* increased coverage by mobile carriers, very few satellite providers now
* serve multiple countries. As a result, the output does not provide
* sufficiently relevant data for us to maintain it.
* @property-read bool $isTorExitNode This is true if the IP address is a Tor
* exit node. This property is only available from GeoIP2 Precision Insights.
* @property-read string|null $isp The name of the ISP associated with the IP
* address. This attribute is only available from the City and Insights web
* services and the GeoIP2 Enterprise database.
* @property-read string $network The network in CIDR notation associated with
* the record. In particular, this is the largest network where all of the
* fields besides $ipAddress have the same value.
* @property-read string|null $organization The name of the organization associated
* with the IP address. This attribute is only available from the City and
* Insights web services and the GeoIP2 Enterprise database.
* @property-read string|null $mobileCountryCode The [mobile country code
* (MCC)](https://en.wikipedia.org/wiki/Mobile_country_code) associated with
* the IP address and ISP. This property is available from the City and
* Insights web services and the GeoIP2 Enterprise database.
* @property-read string|null $mobileNetworkCode The [mobile network code
* (MNC)](https://en.wikipedia.org/wiki/Mobile_country_code) associated with
* the IP address and ISP. This property is available from the City and
* Insights web services and the GeoIP2 Enterprise database.
* @property-read float|null $staticIpScore An indicator of how static or
* dynamic an IP address is. This property is only available from GeoIP2
* Precision Insights.
* @property-read int|null $userCount The estimated number of users sharing
* the IP/network during the past 24 hours. For IPv4, the count is for the
* individual IP. For IPv6, the count is for the /64 network. This property is
* only available from GeoIP2 Precision Insights.
* @property-read string|null $userType <p>The user type associated with the IP
* address. This can be one of the following values:</p>
* <ul>
* <li>business
* <li>cafe
* <li>cellular
* <li>college
* <li>content_delivery_network
* <li>dialup
* <li>government
* <li>hosting
* <li>library
* <li>military
* <li>residential
* <li>router
* <li>school
* <li>search_engine_spider
* <li>traveler
* </ul>
* <p>
* This attribute is only available from the Insights web service and the
* GeoIP2 Enterprise database.
* </p>
*/
class Traits extends AbstractRecord
{
/**
* @ignore
*
* @var array<string>
*/
protected $validAttributes = [
'autonomousSystemNumber',
'autonomousSystemOrganization',
'connectionType',
'domain',
'ipAddress',
'isAnonymous',
'isAnonymousProxy',
'isAnonymousVpn',
'isHostingProvider',
'isLegitimateProxy',
'isp',
'isPublicProxy',
'isResidentialProxy',
'isSatelliteProvider',
'isTorExitNode',
'mobileCountryCode',
'mobileNetworkCode',
'network',
'organization',
'staticIpScore',
'userCount',
'userType',
];
public function __construct(?array $record)
{
if (!isset($record['network']) && isset($record['ip_address'], $record['prefix_len'])) {
$record['network'] = Util::cidr($record['ip_address'], $record['prefix_len']);
}
parent::__construct($record);
}
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace GeoIp2;
class Util
{
/**
* This returns the network in CIDR notation for the given IP and prefix
* length. This is for internal use only.
*
* @internal
* @ignore
*/
public static function cidr(string $ipAddress, int $prefixLen): string
{
$ipBytes = inet_pton($ipAddress);
$networkBytes = str_repeat("\0", \strlen($ipBytes));
$curPrefix = $prefixLen;
for ($i = 0; $i < \strlen($ipBytes) && $curPrefix > 0; $i++) {
$b = $ipBytes[$i];
if ($curPrefix < 8) {
$shiftN = 8 - $curPrefix;
$b = \chr(0xFF & (\ord($b) >> $shiftN) << $shiftN);
}
$networkBytes[$i] = $b;
$curPrefix -= 8;
}
$network = inet_ntop($networkBytes);
return "$network/$prefixLen";
}
}

View File

@ -0,0 +1,253 @@
<?php
declare(strict_types=1);
namespace GeoIp2\WebService;
use GeoIp2\Exception\AddressNotFoundException;
use GeoIp2\Exception\AuthenticationException;
use GeoIp2\Exception\GeoIp2Exception;
use GeoIp2\Exception\HttpException;
use GeoIp2\Exception\InvalidRequestException;
use GeoIp2\Exception\OutOfQueriesException;
use GeoIp2\Model\City;
use GeoIp2\Model\Country;
use GeoIp2\Model\Insights;
use GeoIp2\ProviderInterface;
use MaxMind\WebService\Client as WsClient;
/**
* This class provides a client API for all the GeoIP2 Precision web services.
* The services are Country, City, and Insights. Each service returns a
* different set of data about an IP address, with Country returning the
* least data and Insights the most.
*
* Each web service is represented by a different model class, and these model
* classes in turn contain multiple record classes. The record classes have
* attributes which contain data about the IP address.
*
* If the web service does not return a particular piece of data for an IP
* address, the associated attribute is not populated.
*
* The web service may not return any information for an entire record, in
* which case all of the attributes for that record class will be empty.
*
* ## Usage ##
*
* The basic API for this class is the same for all of the web service end
* points. First you create a web service object with your MaxMind `$accountId`
* and `$licenseKey`, then you call the method corresponding to a specific end
* point, passing it the IP address you want to look up.
*
* If the request succeeds, the method call will return a model class for
* the service you called. This model in turn contains multiple record
* classes, each of which represents part of the data returned by the web
* service.
*
* If the request fails, the client class throws an exception.
*/
class Client implements ProviderInterface
{
/**
* @var array<string>
*/
private $locales;
/**
* @var WsClient
*/
private $client;
/**
* @var string
*/
private static $basePath = '/geoip/v2.1';
public const VERSION = 'v2.12.2';
/**
* Constructor.
*
* @param int $accountId your MaxMind account ID
* @param string $licenseKey your MaxMind license key
* @param array $locales list of locale codes to use in name property
* from most preferred to least preferred
* @param array $options array of options. Valid options include:
* * `host` - The host to use when querying the web service. To
* query the GeoLite2 web service instead of GeoIP2 Precision,
* set the host to `geolite.info`.
* * `timeout` - Timeout in seconds.
* * `connectTimeout` - Initial connection timeout in seconds.
* * `proxy` - The HTTP proxy to use. May include a schema, port,
* username, and password, e.g.,
* `http://username:password@127.0.0.1:10`.
*/
public function __construct(
int $accountId,
string $licenseKey,
array $locales = ['en'],
array $options = []
) {
$this->locales = $locales;
// This is for backwards compatibility. Do not remove except for a
// major version bump.
// @phpstan-ignore-next-line
if (\is_string($options)) {
$options = ['host' => $options];
}
if (!isset($options['host'])) {
$options['host'] = 'geoip.maxmind.com';
}
$options['userAgent'] = $this->userAgent();
$this->client = new WsClient($accountId, $licenseKey, $options);
}
private function userAgent(): string
{
return 'GeoIP2-API/' . self::VERSION;
}
/**
* This method calls the City service.
*
* @param string $ipAddress IPv4 or IPv6 address as a string. If no
* address is provided, the address that the web service is called
* from will be used.
*
* @throws \GeoIp2\Exception\AddressNotFoundException if the address you
* provided is not in our database (e.g., a private address).
* @throws \GeoIp2\Exception\AuthenticationException if there is a problem
* with the account ID or license key that you provided
* @throws \GeoIp2\Exception\OutOfQueriesException if your account is out
* of queries
* @throws \GeoIp2\Exception\InvalidRequestException} if your request was received by the web service but is
* invalid for some other reason. This may indicate an issue
* with this API. Please report the error to MaxMind.
* @throws \GeoIp2\Exception\HttpException if an unexpected HTTP error code or message was returned.
* This could indicate a problem with the connection between
* your server and the web service or that the web service
* returned an invalid document or 500 error code
* @throws \GeoIp2\Exception\GeoIp2Exception This serves as the parent
* class to the above exceptions. It will be thrown directly
* if a 200 status code is returned but the body is invalid.
*/
public function city(string $ipAddress = 'me'): City
{
// @phpstan-ignore-next-line
return $this->responseFor('city', 'City', $ipAddress);
}
/**
* This method calls the Country service.
*
* @param string $ipAddress IPv4 or IPv6 address as a string. If no
* address is provided, the address that the web service is called
* from will be used.
*
* @throws \GeoIp2\Exception\AddressNotFoundException if the address you provided is not in our database (e.g.,
* a private address).
* @throws \GeoIp2\Exception\AuthenticationException if there is a problem
* with the account ID or license key that you provided
* @throws \GeoIp2\Exception\OutOfQueriesException if your account is out of queries
* @throws \GeoIp2\Exception\InvalidRequestException} if your request was received by the web service but is
* invalid for some other reason. This may indicate an
* issue with this API. Please report the error to MaxMind.
* @throws \GeoIp2\Exception\HttpException if an unexpected HTTP error
* code or message was returned. This could indicate a problem
* with the connection between your server and the web service
* or that the web service returned an invalid document or 500
* error code.
* @throws \GeoIp2\Exception\GeoIp2Exception This serves as the parent class to the above exceptions. It
* will be thrown directly if a 200 status code is returned but
* the body is invalid.
*/
public function country(string $ipAddress = 'me'): Country
{
return $this->responseFor('country', 'Country', $ipAddress);
}
/**
* This method calls the Insights service. Insights is only supported by GeoIP2
* Precision. The GeoLite2 web service does not support it.
*
* @param string $ipAddress IPv4 or IPv6 address as a string. If no
* address is provided, the address that the web service is called
* from will be used.
*
* @throws \GeoIp2\Exception\AddressNotFoundException if the address you
* provided is not in our database (e.g., a private address).
* @throws \GeoIp2\Exception\AuthenticationException if there is a problem
* with the account ID or license key that you provided
* @throws \GeoIp2\Exception\OutOfQueriesException if your account is out
* of queries
* @throws \GeoIp2\Exception\InvalidRequestException} if your request was received by the web service but is
* invalid for some other reason. This may indicate an
* issue with this API. Please report the error to MaxMind.
* @throws \GeoIp2\Exception\HttpException if an unexpected HTTP error code or message was returned.
* This could indicate a problem with the connection between
* your server and the web service or that the web service
* returned an invalid document or 500 error code
* @throws \GeoIp2\Exception\GeoIp2Exception This serves as the parent
* class to the above exceptions. It will be thrown directly
* if a 200 status code is returned but the body is invalid.
*/
public function insights(string $ipAddress = 'me'): Insights
{
// @phpstan-ignore-next-line
return $this->responseFor('insights', 'Insights', $ipAddress);
}
private function responseFor(string $endpoint, string $class, string $ipAddress): Country
{
$path = implode('/', [self::$basePath, $endpoint, $ipAddress]);
try {
$body = $this->client->get('GeoIP2 ' . $class, $path);
} catch (\MaxMind\Exception\IpAddressNotFoundException $ex) {
throw new AddressNotFoundException(
$ex->getMessage(),
$ex->getStatusCode(),
$ex
);
} catch (\MaxMind\Exception\AuthenticationException $ex) {
throw new AuthenticationException(
$ex->getMessage(),
$ex->getStatusCode(),
$ex
);
} catch (\MaxMind\Exception\InsufficientFundsException $ex) {
throw new OutOfQueriesException(
$ex->getMessage(),
$ex->getStatusCode(),
$ex
);
} catch (\MaxMind\Exception\InvalidRequestException $ex) {
throw new InvalidRequestException(
$ex->getMessage(),
$ex->getErrorCode(),
$ex->getStatusCode(),
$ex->getUri(),
$ex
);
} catch (\MaxMind\Exception\HttpException $ex) {
throw new HttpException(
$ex->getMessage(),
$ex->getStatusCode(),
$ex->getUri(),
$ex
);
} catch (\MaxMind\Exception\WebServiceException $ex) {
throw new GeoIp2Exception(
$ex->getMessage(),
$ex->getCode(),
$ex
);
}
$class = 'GeoIp2\\Model\\' . $class;
return new $class($body, $this->locales);
}
}

View File

@ -0,0 +1,20 @@
# http_build_url() for PHP
[![Build Status](https://travis-ci.org/jakeasmith/http_build_url.png)](https://travis-ci.org/jakeasmith/http_build_url)
[![Code Climate](https://codeclimate.com/github/jakeasmith/http_build_url/badges/gpa.svg)](https://codeclimate.com/github/jakeasmith/http_build_url)
[![Latest Stable Version](https://poser.pugx.org/jakeasmith/http_build_url/v/stable.png)](https://packagist.org/packages/jakeasmith/http_build_url)
[![Total Downloads](https://poser.pugx.org/jakeasmith/http_build_url/downloads.png)](https://packagist.org/packages/jakeasmith/http_build_url)
This simple library provides functionality for [`http_build_url()`](http://us2.php.net/manual/en/function.http-build-url.php) to environments without pecl_http. It aims to mimic the functionality of the pecl function in every way and ships with a full suite of tests that have been run against both the original function and the one in this package.
## Installation
The easiest way to install this library is to use [Composer](https://getcomposer.org/) from the command line.
```
$ composer require jakeasmith/http_build_url ^1
```
## License
This project is licensed under the MIT License - see the LICENSE file for details.

View File

@ -0,0 +1,174 @@
<?php
/**
* URL constants as defined in the PHP Manual under "Constants usable with
* http_build_url()".
*
* @see http://us2.php.net/manual/en/http.constants.php#http.constants.url
*/
if (!defined('HTTP_URL_REPLACE')) {
define('HTTP_URL_REPLACE', 1);
}
if (!defined('HTTP_URL_JOIN_PATH')) {
define('HTTP_URL_JOIN_PATH', 2);
}
if (!defined('HTTP_URL_JOIN_QUERY')) {
define('HTTP_URL_JOIN_QUERY', 4);
}
if (!defined('HTTP_URL_STRIP_USER')) {
define('HTTP_URL_STRIP_USER', 8);
}
if (!defined('HTTP_URL_STRIP_PASS')) {
define('HTTP_URL_STRIP_PASS', 16);
}
if (!defined('HTTP_URL_STRIP_AUTH')) {
define('HTTP_URL_STRIP_AUTH', 32);
}
if (!defined('HTTP_URL_STRIP_PORT')) {
define('HTTP_URL_STRIP_PORT', 64);
}
if (!defined('HTTP_URL_STRIP_PATH')) {
define('HTTP_URL_STRIP_PATH', 128);
}
if (!defined('HTTP_URL_STRIP_QUERY')) {
define('HTTP_URL_STRIP_QUERY', 256);
}
if (!defined('HTTP_URL_STRIP_FRAGMENT')) {
define('HTTP_URL_STRIP_FRAGMENT', 512);
}
if (!defined('HTTP_URL_STRIP_ALL')) {
define('HTTP_URL_STRIP_ALL', 1024);
}
if (!function_exists('http_build_url')) {
/**
* Build a URL.
*
* The parts of the second URL will be merged into the first according to
* the flags argument.
*
* @param mixed $url (part(s) of) an URL in form of a string or
* associative array like parse_url() returns
* @param mixed $parts same as the first argument
* @param int $flags a bitmask of binary or'ed HTTP_URL constants;
* HTTP_URL_REPLACE is the default
* @param array $new_url if set, it will be filled with the parts of the
* composed url like parse_url() would return
* @return string
*/
function http_build_url($url, $parts = array(), $flags = HTTP_URL_REPLACE, &$new_url = array())
{
is_array($url) || $url = parse_url($url);
is_array($parts) || $parts = parse_url($parts);
isset($url['query']) && is_string($url['query']) || $url['query'] = null;
isset($parts['query']) && is_string($parts['query']) || $parts['query'] = null;
$keys = array('user', 'pass', 'port', 'path', 'query', 'fragment');
// HTTP_URL_STRIP_ALL and HTTP_URL_STRIP_AUTH cover several other flags.
if ($flags & HTTP_URL_STRIP_ALL) {
$flags |= HTTP_URL_STRIP_USER | HTTP_URL_STRIP_PASS
| HTTP_URL_STRIP_PORT | HTTP_URL_STRIP_PATH
| HTTP_URL_STRIP_QUERY | HTTP_URL_STRIP_FRAGMENT;
} elseif ($flags & HTTP_URL_STRIP_AUTH) {
$flags |= HTTP_URL_STRIP_USER | HTTP_URL_STRIP_PASS;
}
// Schema and host are alwasy replaced
foreach (array('scheme', 'host') as $part) {
if (isset($parts[$part])) {
$url[$part] = $parts[$part];
}
}
if ($flags & HTTP_URL_REPLACE) {
foreach ($keys as $key) {
if (isset($parts[$key])) {
$url[$key] = $parts[$key];
}
}
} else {
if (isset($parts['path']) && ($flags & HTTP_URL_JOIN_PATH)) {
if (isset($url['path']) && substr($parts['path'], 0, 1) !== '/') {
// Workaround for trailing slashes
$url['path'] .= 'a';
$url['path'] = rtrim(
str_replace(basename($url['path']), '', $url['path']),
'/'
) . '/' . ltrim($parts['path'], '/');
} else {
$url['path'] = $parts['path'];
}
}
if (isset($parts['query']) && ($flags & HTTP_URL_JOIN_QUERY)) {
if (isset($url['query'])) {
parse_str($url['query'], $url_query);
parse_str($parts['query'], $parts_query);
$url['query'] = http_build_query(
array_replace_recursive(
$url_query,
$parts_query
)
);
} else {
$url['query'] = $parts['query'];
}
}
}
if (isset($url['path']) && $url['path'] !== '' && substr($url['path'], 0, 1) !== '/') {
$url['path'] = '/' . $url['path'];
}
foreach ($keys as $key) {
$strip = 'HTTP_URL_STRIP_' . strtoupper($key);
if ($flags & constant($strip)) {
unset($url[$key]);
}
}
$parsed_string = '';
if (!empty($url['scheme'])) {
$parsed_string .= $url['scheme'] . '://';
}
if (!empty($url['user'])) {
$parsed_string .= $url['user'];
if (isset($url['pass'])) {
$parsed_string .= ':' . $url['pass'];
}
$parsed_string .= '@';
}
if (!empty($url['host'])) {
$parsed_string .= $url['host'];
}
if (!empty($url['port'])) {
$parsed_string .= ':' . $url['port'];
}
if (!empty($url['path'])) {
$parsed_string .= $url['path'];
}
if (!empty($url['query'])) {
$parsed_string .= '?' . $url['query'];
}
if (!empty($url['fragment'])) {
$parsed_string .= '#' . $url['fragment'];
}
$new_url = $url;
return $parsed_string;
}
}

View File

@ -0,0 +1,185 @@
# MaxMind DB Reader PHP API #
## Description ##
This is the PHP API for reading MaxMind DB files. MaxMind DB is a binary file
format that stores data indexed by IP address subnets (IPv4 or IPv6).
## Installation (Composer) ##
We recommend installing this package with [Composer](https://getcomposer.org/).
### Download Composer ###
To download Composer, run in the root directory of your project:
```bash
curl -sS https://getcomposer.org/installer | php
```
You should now have the file `composer.phar` in your project directory.
### Install Dependencies ###
Run in your project root:
```
php composer.phar require maxmind-db/reader:~1.0
```
You should now have the files `composer.json` and `composer.lock` as well as
the directory `vendor` in your project directory. If you use a version control
system, `composer.json` should be added to it.
### Require Autoloader ###
After installing the dependencies, you need to require the Composer autoloader
from your code:
```php
require 'vendor/autoload.php';
```
## Installation (Standalone) ##
If you don't want to use Composer for some reason, a custom
`autoload.php` is provided for you in the project root. To use the
library, simply include that file,
```php
require('/path/to/MaxMind-DB-Reader-php/autoload.php');
```
and then instantiate the reader class normally:
```php
use MaxMind\Db\Reader;
$reader = new Reader('example.mmdb');
```
## Installation (RPM)
RPMs are available in the [official Fedora repository](https://apps.fedoraproject.org/packages/php-maxminddb).
To install on Fedora, run:
```bash
dnf install php-maxminddb
```
To install on CentOS or RHEL 7, first [enable the EPEL repository](https://fedoraproject.org/wiki/EPEL)
and then run:
```bash
yum install php-maxminddb
```
Please note that these packages are *not* maintained by MaxMind.
## Usage ##
## Example ##
```php
<?php
require_once 'vendor/autoload.php';
use MaxMind\Db\Reader;
$ipAddress = '24.24.24.24';
$databaseFile = 'GeoIP2-City.mmdb';
$reader = new Reader($databaseFile);
// get returns just the record for the IP address
print_r($reader->get($ipAddress));
// getWithPrefixLen returns an array containing the record and the
// associated prefix length for that record.
print_r($reader->getWithPrefixLen($ipAddress));
$reader->close();
```
## Optional PHP C Extension ##
MaxMind provides an optional C extension that is a drop-in replacement for
`MaxMind\Db\Reader`. In order to use this extension, you must install the
Reader API as described above and install the extension as described below. If
you are using an autoloader, no changes to your code should be necessary.
### Installing Extension ###
First install [libmaxminddb](https://github.com/maxmind/libmaxminddb) as
described in its [README.md
file](https://github.com/maxmind/libmaxminddb/blob/main/README.md#installing-from-a-tarball).
After successfully installing libmaxmindb, you may install the extension
from [pecl](https://pecl.php.net/package/maxminddb):
```
pecl install maxminddb
```
Alternatively, you may install it from the source. To do so, run the following
commands from the top-level directory of this distribution:
```
cd ext
phpize
./configure
make
make test
sudo make install
```
You then must load your extension. The recommend method is to add the
following to your `php.ini` file:
```
extension=maxminddb.so
```
Note: You may need to install the PHP development package on your OS such as
php5-dev for Debian-based systems or php-devel for RedHat/Fedora-based ones.
## 128-bit Integer Support ##
The MaxMind DB format includes 128-bit unsigned integer as a type. Although
no MaxMind-distributed database currently makes use of this type, both the
pure PHP reader and the C extension support this type. The pure PHP reader
requires gmp or bcmath to read databases with 128-bit unsigned integers.
The integer is currently returned as a hexadecimal string (prefixed with "0x")
by the C extension and a decimal string (no prefix) by the pure PHP reader.
Any change to make the reader implementations always return either a
hexadecimal or decimal representation of the integer will NOT be considered a
breaking change.
## Support ##
Please report all issues with this code using the [GitHub issue tracker](https://github.com/maxmind/MaxMind-DB-Reader-php/issues).
If you are having an issue with a MaxMind service that is not specific to the
client API, please see [our support page](https://www.maxmind.com/en/support).
## Requirements ##
This library requires PHP 7.2 or greater.
The GMP or BCMath extension may be required to read some databases
using the pure PHP API.
## Contributing ##
Patches and pull requests are encouraged. All code should follow the PSR-1 and
PSR-2 style guidelines. Please include unit tests whenever possible.
## Versioning ##
The MaxMind DB Reader PHP API uses [Semantic Versioning](https://semver.org/).
## Copyright and License ##
This software is Copyright (c) 2014-2020 by MaxMind, Inc.
This is free software, licensed under the Apache License, Version 2.0.

View File

@ -0,0 +1,373 @@
<?php
declare(strict_types=1);
namespace MaxMind\Db;
use ArgumentCountError;
use BadMethodCallException;
use Exception;
use InvalidArgumentException;
use MaxMind\Db\Reader\Decoder;
use MaxMind\Db\Reader\InvalidDatabaseException;
use MaxMind\Db\Reader\Metadata;
use MaxMind\Db\Reader\Util;
use UnexpectedValueException;
/**
* Instances of this class provide a reader for the MaxMind DB format. IP
* addresses can be looked up using the get method.
*/
class Reader
{
/**
* @var int
*/
private static $DATA_SECTION_SEPARATOR_SIZE = 16;
/**
* @var string
*/
private static $METADATA_START_MARKER = "\xAB\xCD\xEFMaxMind.com";
/**
* @var int
*/
private static $METADATA_START_MARKER_LENGTH = 14;
/**
* @var int
*/
private static $METADATA_MAX_SIZE = 131072; // 128 * 1024 = 128KiB
/**
* @var Decoder
*/
private $decoder;
/**
* @var resource
*/
private $fileHandle;
/**
* @var int
*/
private $fileSize;
/**
* @var int
*/
private $ipV4Start;
/**
* @var Metadata
*/
private $metadata;
/**
* Constructs a Reader for the MaxMind DB format. The file passed to it must
* be a valid MaxMind DB file such as a GeoIp2 database file.
*
* @param string $database
* the MaxMind DB file to use
*
* @throws InvalidArgumentException for invalid database path or unknown arguments
* @throws InvalidDatabaseException
* if the database is invalid or there is an error reading
* from it
*/
public function __construct(string $database)
{
if (\func_num_args() !== 1) {
throw new ArgumentCountError(
sprintf('%s() expects exactly 1 parameter, %d given', __METHOD__, \func_num_args())
);
}
$fileHandle = @fopen($database, 'rb');
if ($fileHandle === false) {
throw new InvalidArgumentException(
"The file \"$database\" does not exist or is not readable."
);
}
$this->fileHandle = $fileHandle;
$fileSize = @filesize($database);
if ($fileSize === false) {
throw new UnexpectedValueException(
"Error determining the size of \"$database\"."
);
}
$this->fileSize = $fileSize;
$start = $this->findMetadataStart($database);
$metadataDecoder = new Decoder($this->fileHandle, $start);
[$metadataArray] = $metadataDecoder->decode($start);
$this->metadata = new Metadata($metadataArray);
$this->decoder = new Decoder(
$this->fileHandle,
$this->metadata->searchTreeSize + self::$DATA_SECTION_SEPARATOR_SIZE
);
$this->ipV4Start = $this->ipV4StartNode();
}
/**
* Retrieves the record for the IP address.
*
* @param string $ipAddress
* the IP address to look up
*
* @throws BadMethodCallException if this method is called on a closed database
* @throws InvalidArgumentException if something other than a single IP address is passed to the method
* @throws InvalidDatabaseException
* if the database is invalid or there is an error reading
* from it
*
* @return mixed the record for the IP address
*/
public function get(string $ipAddress)
{
if (\func_num_args() !== 1) {
throw new ArgumentCountError(
sprintf('%s() expects exactly 1 parameter, %d given', __METHOD__, \func_num_args())
);
}
[$record] = $this->getWithPrefixLen($ipAddress);
return $record;
}
/**
* Retrieves the record for the IP address and its associated network prefix length.
*
* @param string $ipAddress
* the IP address to look up
*
* @throws BadMethodCallException if this method is called on a closed database
* @throws InvalidArgumentException if something other than a single IP address is passed to the method
* @throws InvalidDatabaseException
* if the database is invalid or there is an error reading
* from it
*
* @return array an array where the first element is the record and the
* second the network prefix length for the record
*/
public function getWithPrefixLen(string $ipAddress): array
{
if (\func_num_args() !== 1) {
throw new ArgumentCountError(
sprintf('%s() expects exactly 1 parameter, %d given', __METHOD__, \func_num_args())
);
}
if (!\is_resource($this->fileHandle)) {
throw new BadMethodCallException(
'Attempt to read from a closed MaxMind DB.'
);
}
[$pointer, $prefixLen] = $this->findAddressInTree($ipAddress);
if ($pointer === 0) {
return [null, $prefixLen];
}
return [$this->resolveDataPointer($pointer), $prefixLen];
}
private function findAddressInTree(string $ipAddress): array
{
$packedAddr = @inet_pton($ipAddress);
if ($packedAddr === false) {
throw new InvalidArgumentException(
"The value \"$ipAddress\" is not a valid IP address."
);
}
$rawAddress = unpack('C*', $packedAddr);
$bitCount = \count($rawAddress) * 8;
// The first node of the tree is always node 0, at the beginning of the
// value
$node = 0;
$metadata = $this->metadata;
// Check if we are looking up an IPv4 address in an IPv6 tree. If this
// is the case, we can skip over the first 96 nodes.
if ($metadata->ipVersion === 6) {
if ($bitCount === 32) {
$node = $this->ipV4Start;
}
} elseif ($metadata->ipVersion === 4 && $bitCount === 128) {
throw new InvalidArgumentException(
"Error looking up $ipAddress. You attempted to look up an"
. ' IPv6 address in an IPv4-only database.'
);
}
$nodeCount = $metadata->nodeCount;
for ($i = 0; $i < $bitCount && $node < $nodeCount; ++$i) {
$tempBit = 0xFF & $rawAddress[($i >> 3) + 1];
$bit = 1 & ($tempBit >> 7 - ($i % 8));
$node = $this->readNode($node, $bit);
}
if ($node === $nodeCount) {
// Record is empty
return [0, $i];
}
if ($node > $nodeCount) {
// Record is a data pointer
return [$node, $i];
}
throw new InvalidDatabaseException(
'Invalid or corrupt database. Maximum search depth reached without finding a leaf node'
);
}
private function ipV4StartNode(): int
{
// If we have an IPv4 database, the start node is the first node
if ($this->metadata->ipVersion === 4) {
return 0;
}
$node = 0;
for ($i = 0; $i < 96 && $node < $this->metadata->nodeCount; ++$i) {
$node = $this->readNode($node, 0);
}
return $node;
}
private function readNode(int $nodeNumber, int $index): int
{
$baseOffset = $nodeNumber * $this->metadata->nodeByteSize;
switch ($this->metadata->recordSize) {
case 24:
$bytes = Util::read($this->fileHandle, $baseOffset + $index * 3, 3);
[, $node] = unpack('N', "\x00" . $bytes);
return $node;
case 28:
$bytes = Util::read($this->fileHandle, $baseOffset + 3 * $index, 4);
if ($index === 0) {
$middle = (0xF0 & \ord($bytes[3])) >> 4;
} else {
$middle = 0x0F & \ord($bytes[0]);
}
[, $node] = unpack('N', \chr($middle) . substr($bytes, $index, 3));
return $node;
case 32:
$bytes = Util::read($this->fileHandle, $baseOffset + $index * 4, 4);
[, $node] = unpack('N', $bytes);
return $node;
default:
throw new InvalidDatabaseException(
'Unknown record size: '
. $this->metadata->recordSize
);
}
}
/**
* @return mixed
*/
private function resolveDataPointer(int $pointer)
{
$resolved = $pointer - $this->metadata->nodeCount
+ $this->metadata->searchTreeSize;
if ($resolved >= $this->fileSize) {
throw new InvalidDatabaseException(
"The MaxMind DB file's search tree is corrupt"
);
}
[$data] = $this->decoder->decode($resolved);
return $data;
}
/*
* This is an extremely naive but reasonably readable implementation. There
* are much faster algorithms (e.g., Boyer-Moore) for this if speed is ever
* an issue, but I suspect it won't be.
*/
private function findMetadataStart(string $filename): int
{
$handle = $this->fileHandle;
$fstat = fstat($handle);
$fileSize = $fstat['size'];
$marker = self::$METADATA_START_MARKER;
$markerLength = self::$METADATA_START_MARKER_LENGTH;
$minStart = $fileSize - min(self::$METADATA_MAX_SIZE, $fileSize);
for ($offset = $fileSize - $markerLength; $offset >= $minStart; --$offset) {
if (fseek($handle, $offset) !== 0) {
break;
}
$value = fread($handle, $markerLength);
if ($value === $marker) {
return $offset + $markerLength;
}
}
throw new InvalidDatabaseException(
"Error opening database file ($filename). " .
'Is this a valid MaxMind DB file?'
);
}
/**
* @throws InvalidArgumentException if arguments are passed to the method
* @throws BadMethodCallException if the database has been closed
*
* @return Metadata object for the database
*/
public function metadata(): Metadata
{
if (\func_num_args()) {
throw new ArgumentCountError(
sprintf('%s() expects exactly 0 parameters, %d given', __METHOD__, \func_num_args())
);
}
// Not technically required, but this makes it consistent with
// C extension and it allows us to change our implementation later.
if (!\is_resource($this->fileHandle)) {
throw new BadMethodCallException(
'Attempt to read from a closed MaxMind DB.'
);
}
return clone $this->metadata;
}
/**
* Closes the MaxMind DB and returns resources to the system.
*
* @throws Exception
* if an I/O error occurs
*/
public function close(): void
{
if (\func_num_args()) {
throw new ArgumentCountError(
sprintf('%s() expects exactly 0 parameters, %d given', __METHOD__, \func_num_args())
);
}
if (!\is_resource($this->fileHandle)) {
throw new BadMethodCallException(
'Attempt to close a closed MaxMind DB.'
);
}
fclose($this->fileHandle);
}
}

View File

@ -0,0 +1,374 @@
<?php
declare(strict_types=1);
namespace MaxMind\Db\Reader;
// @codingStandardsIgnoreLine
use RuntimeException;
class Decoder
{
/**
* @var resource
*/
private $fileStream;
/**
* @var int
*/
private $pointerBase;
/**
* @var float
*/
private $pointerBaseByteSize;
/**
* This is only used for unit testing.
*
* @var bool
*/
private $pointerTestHack;
/**
* @var bool
*/
private $switchByteOrder;
private const _EXTENDED = 0;
private const _POINTER = 1;
private const _UTF8_STRING = 2;
private const _DOUBLE = 3;
private const _BYTES = 4;
private const _UINT16 = 5;
private const _UINT32 = 6;
private const _MAP = 7;
private const _INT32 = 8;
private const _UINT64 = 9;
private const _UINT128 = 10;
private const _ARRAY = 11;
private const _CONTAINER = 12;
private const _END_MARKER = 13;
private const _BOOLEAN = 14;
private const _FLOAT = 15;
/**
* @param resource $fileStream
*/
public function __construct(
$fileStream,
int $pointerBase = 0,
bool $pointerTestHack = false
) {
$this->fileStream = $fileStream;
$this->pointerBase = $pointerBase;
$this->pointerBaseByteSize = $pointerBase > 0 ? log($pointerBase, 2) / 8 : 0;
$this->pointerTestHack = $pointerTestHack;
$this->switchByteOrder = $this->isPlatformLittleEndian();
}
public function decode(int $offset): array
{
$ctrlByte = \ord(Util::read($this->fileStream, $offset, 1));
++$offset;
$type = $ctrlByte >> 5;
// Pointers are a special case, we don't read the next $size bytes, we
// use the size to determine the length of the pointer and then follow
// it.
if ($type === self::_POINTER) {
[$pointer, $offset] = $this->decodePointer($ctrlByte, $offset);
// for unit testing
if ($this->pointerTestHack) {
return [$pointer];
}
[$result] = $this->decode($pointer);
return [$result, $offset];
}
if ($type === self::_EXTENDED) {
$nextByte = \ord(Util::read($this->fileStream, $offset, 1));
$type = $nextByte + 7;
if ($type < 8) {
throw new InvalidDatabaseException(
'Something went horribly wrong in the decoder. An extended type '
. 'resolved to a type number < 8 ('
. $type
. ')'
);
}
++$offset;
}
[$size, $offset] = $this->sizeFromCtrlByte($ctrlByte, $offset);
return $this->decodeByType($type, $offset, $size);
}
private function decodeByType(int $type, int $offset, int $size): array
{
switch ($type) {
case self::_MAP:
return $this->decodeMap($size, $offset);
case self::_ARRAY:
return $this->decodeArray($size, $offset);
case self::_BOOLEAN:
return [$this->decodeBoolean($size), $offset];
}
$newOffset = $offset + $size;
$bytes = Util::read($this->fileStream, $offset, $size);
switch ($type) {
case self::_BYTES:
case self::_UTF8_STRING:
return [$bytes, $newOffset];
case self::_DOUBLE:
$this->verifySize(8, $size);
return [$this->decodeDouble($bytes), $newOffset];
case self::_FLOAT:
$this->verifySize(4, $size);
return [$this->decodeFloat($bytes), $newOffset];
case self::_INT32:
return [$this->decodeInt32($bytes, $size), $newOffset];
case self::_UINT16:
case self::_UINT32:
case self::_UINT64:
case self::_UINT128:
return [$this->decodeUint($bytes, $size), $newOffset];
default:
throw new InvalidDatabaseException(
'Unknown or unexpected type: ' . $type
);
}
}
private function verifySize(int $expected, int $actual): void
{
if ($expected !== $actual) {
throw new InvalidDatabaseException(
"The MaxMind DB file's data section contains bad data (unknown data type or corrupt data)"
);
}
}
private function decodeArray(int $size, int $offset): array
{
$array = [];
for ($i = 0; $i < $size; ++$i) {
[$value, $offset] = $this->decode($offset);
$array[] = $value;
}
return [$array, $offset];
}
private function decodeBoolean(int $size): bool
{
return $size !== 0;
}
private function decodeDouble(string $bytes): float
{
// This assumes IEEE 754 doubles, but most (all?) modern platforms
// use them.
[, $double] = unpack('E', $bytes);
return $double;
}
private function decodeFloat(string $bytes): float
{
// This assumes IEEE 754 floats, but most (all?) modern platforms
// use them.
[, $float] = unpack('G', $bytes);
return $float;
}
private function decodeInt32(string $bytes, int $size): int
{
switch ($size) {
case 0:
return 0;
case 1:
case 2:
case 3:
$bytes = str_pad($bytes, 4, "\x00", \STR_PAD_LEFT);
break;
case 4:
break;
default:
throw new InvalidDatabaseException(
"The MaxMind DB file's data section contains bad data (unknown data type or corrupt data)"
);
}
[, $int] = unpack('l', $this->maybeSwitchByteOrder($bytes));
return $int;
}
private function decodeMap(int $size, int $offset): array
{
$map = [];
for ($i = 0; $i < $size; ++$i) {
[$key, $offset] = $this->decode($offset);
[$value, $offset] = $this->decode($offset);
$map[$key] = $value;
}
return [$map, $offset];
}
private function decodePointer(int $ctrlByte, int $offset): array
{
$pointerSize = (($ctrlByte >> 3) & 0x3) + 1;
$buffer = Util::read($this->fileStream, $offset, $pointerSize);
$offset = $offset + $pointerSize;
switch ($pointerSize) {
case 1:
$packed = \chr($ctrlByte & 0x7) . $buffer;
[, $pointer] = unpack('n', $packed);
$pointer += $this->pointerBase;
break;
case 2:
$packed = "\x00" . \chr($ctrlByte & 0x7) . $buffer;
[, $pointer] = unpack('N', $packed);
$pointer += $this->pointerBase + 2048;
break;
case 3:
$packed = \chr($ctrlByte & 0x7) . $buffer;
// It is safe to use 'N' here, even on 32 bit machines as the
// first bit is 0.
[, $pointer] = unpack('N', $packed);
$pointer += $this->pointerBase + 526336;
break;
case 4:
// We cannot use unpack here as we might overflow on 32 bit
// machines
$pointerOffset = $this->decodeUint($buffer, $pointerSize);
$pointerBase = $this->pointerBase;
if (\PHP_INT_MAX - $pointerBase >= $pointerOffset) {
$pointer = $pointerOffset + $pointerBase;
} else {
throw new RuntimeException(
'The database offset is too large to be represented on your platform.'
);
}
break;
default:
throw new InvalidDatabaseException(
'Unexpected pointer size ' . $pointerSize
);
}
return [$pointer, $offset];
}
// @phpstan-ignore-next-line
private function decodeUint(string $bytes, int $byteLength)
{
if ($byteLength === 0) {
return 0;
}
$integer = 0;
// PHP integers are signed. PHP_INT_SIZE - 1 is the number of
// complete bytes that can be converted to an integer. However,
// we can convert another byte if the leading bit is zero.
$useRealInts = $byteLength <= \PHP_INT_SIZE - 1
|| ($byteLength === \PHP_INT_SIZE && (\ord($bytes[0]) & 0x80) === 0);
for ($i = 0; $i < $byteLength; ++$i) {
$part = \ord($bytes[$i]);
// We only use gmp or bcmath if the final value is too big
if ($useRealInts) {
$integer = ($integer << 8) + $part;
} elseif (\extension_loaded('gmp')) {
$integer = gmp_strval(gmp_add(gmp_mul((string) $integer, '256'), $part));
} elseif (\extension_loaded('bcmath')) {
$integer = bcadd(bcmul((string) $integer, '256'), (string) $part);
} else {
throw new RuntimeException(
'The gmp or bcmath extension must be installed to read this database.'
);
}
}
return $integer;
}
private function sizeFromCtrlByte(int $ctrlByte, int $offset): array
{
$size = $ctrlByte & 0x1F;
if ($size < 29) {
return [$size, $offset];
}
$bytesToRead = $size - 28;
$bytes = Util::read($this->fileStream, $offset, $bytesToRead);
if ($size === 29) {
$size = 29 + \ord($bytes);
} elseif ($size === 30) {
[, $adjust] = unpack('n', $bytes);
$size = 285 + $adjust;
} else {
[, $adjust] = unpack('N', "\x00" . $bytes);
$size = $adjust + 65821;
}
return [$size, $offset + $bytesToRead];
}
private function maybeSwitchByteOrder(string $bytes): string
{
return $this->switchByteOrder ? strrev($bytes) : $bytes;
}
private function isPlatformLittleEndian(): bool
{
$testint = 0x00FF;
$packed = pack('S', $testint);
return $testint === current(unpack('v', $packed));
}
}

View File

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace MaxMind\Db\Reader;
use Exception;
/**
* This class should be thrown when unexpected data is found in the database.
*/
class InvalidDatabaseException extends Exception
{
}

View File

@ -0,0 +1,112 @@
<?php
declare(strict_types=1);
namespace MaxMind\Db\Reader;
use ArgumentCountError;
/**
* This class provides the metadata for the MaxMind DB file.
*/
class Metadata
{
/**
* This is an unsigned 16-bit integer indicating the major version number
* for the database's binary format.
*
* @var int
*/
public $binaryFormatMajorVersion;
/**
* This is an unsigned 16-bit integer indicating the minor version number
* for the database's binary format.
*
* @var int
*/
public $binaryFormatMinorVersion;
/**
* This is an unsigned 64-bit integer that contains the database build
* timestamp as a Unix epoch value.
*
* @var int
*/
public $buildEpoch;
/**
* This is a string that indicates the structure of each data record
* associated with an IP address. The actual definition of these
* structures is left up to the database creator.
*
* @var string
*/
public $databaseType;
/**
* This key will always point to a map (associative array). The keys of
* that map will be language codes, and the values will be a description
* in that language as a UTF-8 string. May be undefined for some
* databases.
*
* @var array
*/
public $description;
/**
* This is an unsigned 16-bit integer which is always 4 or 6. It indicates
* whether the database contains IPv4 or IPv6 address data.
*
* @var int
*/
public $ipVersion;
/**
* An array of strings, each of which is a language code. A given record
* may contain data items that have been localized to some or all of
* these languages. This may be undefined.
*
* @var array
*/
public $languages;
/**
* @var int
*/
public $nodeByteSize;
/**
* This is an unsigned 32-bit integer indicating the number of nodes in
* the search tree.
*
* @var int
*/
public $nodeCount;
/**
* This is an unsigned 16-bit integer. It indicates the number of bits in a
* record in the search tree. Note that each node consists of two records.
*
* @var int
*/
public $recordSize;
/**
* @var int
*/
public $searchTreeSize;
public function __construct(array $metadata)
{
if (\func_num_args() !== 1) {
throw new ArgumentCountError(
sprintf('%s() expects exactly 1 parameter, %d given', __METHOD__, \func_num_args())
);
}
$this->binaryFormatMajorVersion =
$metadata['binary_format_major_version'];
$this->binaryFormatMinorVersion =
$metadata['binary_format_minor_version'];
$this->buildEpoch = $metadata['build_epoch'];
$this->databaseType = $metadata['database_type'];
$this->languages = $metadata['languages'];
$this->description = $metadata['description'];
$this->ipVersion = $metadata['ip_version'];
$this->nodeCount = $metadata['node_count'];
$this->recordSize = $metadata['record_size'];
$this->nodeByteSize = $this->recordSize / 4;
$this->searchTreeSize = $this->nodeCount * $this->nodeByteSize;
}
}

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace MaxMind\Db\Reader;
class Util
{
/**
* @param resource $stream
*/
public static function read($stream, int $offset, int $numberOfBytes): string
{
if ($numberOfBytes === 0) {
return '';
}
if (fseek($stream, $offset) === 0) {
$value = fread($stream, $numberOfBytes);
// We check that the number of bytes read is equal to the number
// asked for. We use ftell as getting the length of $value is
// much slower.
if ($value !== false && ftell($stream) - $offset === $numberOfBytes) {
return $value;
}
}
throw new InvalidDatabaseException(
'The MaxMind DB file contains bad data'
);
}
}

View File

@ -0,0 +1,25 @@
# Common Code for MaxMind Web Service Clients #
This is _not_ intended for direct use by third parties. Rather, it is for
shared code between MaxMind's various web service client APIs.
## Requirements ##
The library requires PHP 7.2 or greater.
There are several other dependencies as defined in the `composer.json` file.
## Contributing ##
Patches and pull requests are encouraged. All code should follow the PSR-2
style guidelines. Please include unit tests whenever possible.
## Versioning ##
This API uses [Semantic Versioning](http://semver.org/).
## Copyright and License ##
This software is Copyright (c) 2015-2020 by MaxMind, Inc.
This is free software, licensed under the Apache License, Version 2.0.

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace MaxMind\Exception;
/**
* This class represents an error authenticating.
*/
class AuthenticationException extends InvalidRequestException
{
}

View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace MaxMind\Exception;
/**
* This class represents an HTTP transport error.
*/
class HttpException extends WebServiceException
{
/**
* The URI queried.
*
* @var string
*/
private $uri;
/**
* @param string $message a message describing the error
* @param int $httpStatus the HTTP status code of the response
* @param string $uri the URI used in the request
* @param \Exception $previous the previous exception, if any
*/
public function __construct(
string $message,
int $httpStatus,
string $uri,
\Exception $previous = null
) {
$this->uri = $uri;
parent::__construct($message, $httpStatus, $previous);
}
public function getUri(): string
{
return $this->uri;
}
public function getStatusCode(): int
{
return $this->getCode();
}
}

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace MaxMind\Exception;
/**
* Thrown when the account is out of credits.
*/
class InsufficientFundsException extends InvalidRequestException
{
}

View File

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace MaxMind\Exception;
/**
* This class represents an error in creating the request to be sent to the
* web service. For example, if the array cannot be encoded as JSON or if there
* is a missing or invalid field.
*/
class InvalidInputException extends WebServiceException
{
}

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace MaxMind\Exception;
/**
* Thrown when a MaxMind web service returns an error relating to the request.
*/
class InvalidRequestException extends HttpException
{
/**
* The code returned by the MaxMind web service.
*
* @var string
*/
private $error;
/**
* @param string $message the exception message
* @param string $error the error code returned by the MaxMind web service
* @param int $httpStatus the HTTP status code of the response
* @param string $uri the URI queries
* @param \Exception $previous the previous exception, if any
*/
public function __construct(
string $message,
string $error,
int $httpStatus,
string $uri,
\Exception $previous = null
) {
$this->error = $error;
parent::__construct($message, $httpStatus, $uri, $previous);
}
public function getErrorCode(): string
{
return $this->error;
}
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace MaxMind\Exception;
class IpAddressNotFoundException extends InvalidRequestException
{
}

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace MaxMind\Exception;
/**
* This exception is thrown when the service requires permission to access.
*/
class PermissionRequiredException extends InvalidRequestException
{
}

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace MaxMind\Exception;
/**
* This class represents a generic web service error.
*/
class WebServiceException extends \Exception
{
}

View File

@ -0,0 +1,546 @@
<?php
declare(strict_types=1);
namespace MaxMind\WebService;
use Composer\CaBundle\CaBundle;
use MaxMind\Exception\AuthenticationException;
use MaxMind\Exception\HttpException;
use MaxMind\Exception\InsufficientFundsException;
use MaxMind\Exception\InvalidInputException;
use MaxMind\Exception\InvalidRequestException;
use MaxMind\Exception\IpAddressNotFoundException;
use MaxMind\Exception\PermissionRequiredException;
use MaxMind\Exception\WebServiceException;
use MaxMind\WebService\Http\RequestFactory;
/**
* This class is not intended to be used directly by an end-user of a
* MaxMind web service. Please use the appropriate client API for the service
* that you are using.
*
* @internal
*/
class Client
{
public const VERSION = '0.2.0';
/**
* @var string|null
*/
private $caBundle;
/**
* @var float|null
*/
private $connectTimeout;
/**
* @var string
*/
private $host = 'api.maxmind.com';
/**
* @var bool
*/
private $useHttps = true;
/**
* @var RequestFactory
*/
private $httpRequestFactory;
/**
* @var string
*/
private $licenseKey;
/**
* @var string|null
*/
private $proxy;
/**
* @var float|null
*/
private $timeout;
/**
* @var string
*/
private $userAgentPrefix;
/**
* @var int
*/
private $accountId;
/**
* @param int $accountId your MaxMind account ID
* @param string $licenseKey your MaxMind license key
* @param array $options an array of options. Possible keys:
* * `host` - The host to use when connecting to the web service.
* * `useHttps` - A boolean flag for sending the request via https.(True by default)
* * `userAgent` - The prefix of the User-Agent to use in the request.
* * `caBundle` - The bundle of CA root certificates to use in the request.
* * `connectTimeout` - The connect timeout to use for the request.
* * `timeout` - The timeout to use for the request.
* * `proxy` - The HTTP proxy to use. May include a schema, port,
* username, and password, e.g., `http://username:password@127.0.0.1:10`.
*/
public function __construct(
int $accountId,
string $licenseKey,
array $options = []
) {
$this->accountId = $accountId;
$this->licenseKey = $licenseKey;
$this->httpRequestFactory = isset($options['httpRequestFactory'])
? $options['httpRequestFactory']
: new RequestFactory();
if (isset($options['host'])) {
$this->host = $options['host'];
}
if (isset($options['useHttps'])) {
$this->useHttps = $options['useHttps'];
}
if (isset($options['userAgent'])) {
$this->userAgentPrefix = $options['userAgent'] . ' ';
}
$this->caBundle = isset($options['caBundle']) ?
$this->caBundle = $options['caBundle'] : $this->getCaBundle();
if (isset($options['connectTimeout'])) {
$this->connectTimeout = $options['connectTimeout'];
}
if (isset($options['timeout'])) {
$this->timeout = $options['timeout'];
}
if (isset($options['proxy'])) {
$this->proxy = $options['proxy'];
}
}
/**
* @param string $service name of the service querying
* @param string $path the URI path to use
* @param array $input the data to be posted as JSON
*
* @throws InvalidInputException when the request has missing or invalid
* data
* @throws AuthenticationException when there is an issue authenticating the
* request
* @throws InsufficientFundsException when your account is out of funds
* @throws InvalidRequestException when the request is invalid for some
* other reason, e.g., invalid JSON in the POST.
* @throws HttpException when an unexpected HTTP error occurs
* @throws WebServiceException when some other error occurs. This also
* serves as the base class for the above exceptions.
*
* @return array|null The decoded content of a successful response
*/
public function post(string $service, string $path, array $input): ?array
{
$requestBody = json_encode($input);
if ($requestBody === false) {
throw new InvalidInputException(
'Error encoding input as JSON: '
. $this->jsonErrorDescription()
);
}
$request = $this->createRequest(
$path,
['Content-Type: application/json']
);
[$statusCode, $contentType, $responseBody] = $request->post($requestBody);
return $this->handleResponse(
$statusCode,
$contentType,
$responseBody,
$service,
$path
);
}
public function get(string $service, string $path): ?array
{
$request = $this->createRequest(
$path
);
[$statusCode, $contentType, $responseBody] = $request->get();
return $this->handleResponse(
$statusCode,
$contentType,
$responseBody,
$service,
$path
);
}
private function userAgent(): string
{
$curlVersion = curl_version();
return $this->userAgentPrefix . 'MaxMind-WS-API/' . self::VERSION . ' PHP/' . \PHP_VERSION .
' curl/' . $curlVersion['version'];
}
private function createRequest(string $path, array $headers = []): Http\Request
{
array_push(
$headers,
'Authorization: Basic '
. base64_encode($this->accountId . ':' . $this->licenseKey),
'Accept: application/json'
);
return $this->httpRequestFactory->request(
$this->urlFor($path),
[
'caBundle' => $this->caBundle,
'connectTimeout' => $this->connectTimeout,
'headers' => $headers,
'proxy' => $this->proxy,
'timeout' => $this->timeout,
'userAgent' => $this->userAgent(),
]
);
}
/**
* @param int $statusCode the HTTP status code of the response
* @param string|null $contentType the Content-Type of the response
* @param string|null $responseBody the response body
* @param string $service the name of the service
* @param string $path the path used in the request
*
* @throws AuthenticationException when there is an issue authenticating the
* request
* @throws InsufficientFundsException when your account is out of funds
* @throws InvalidRequestException when the request is invalid for some
* other reason, e.g., invalid JSON in the POST.
* @throws HttpException when an unexpected HTTP error occurs
* @throws WebServiceException when some other error occurs. This also
* serves as the base class for the above exceptions
*
* @return array|null The decoded content of a successful response
*/
private function handleResponse(
int $statusCode,
?string $contentType,
?string $responseBody,
string $service,
string $path
): ?array {
if ($statusCode >= 400 && $statusCode <= 499) {
$this->handle4xx($statusCode, $contentType, $responseBody, $service, $path);
} elseif ($statusCode >= 500) {
$this->handle5xx($statusCode, $service, $path);
} elseif ($statusCode !== 200 && $statusCode !== 204) {
$this->handleUnexpectedStatus($statusCode, $service, $path);
}
return $this->handleSuccess($statusCode, $responseBody, $service);
}
/**
* @return string describing the JSON error
*/
private function jsonErrorDescription(): string
{
$errno = json_last_error();
switch ($errno) {
case \JSON_ERROR_DEPTH:
return 'The maximum stack depth has been exceeded.';
case \JSON_ERROR_STATE_MISMATCH:
return 'Invalid or malformed JSON.';
case \JSON_ERROR_CTRL_CHAR:
return 'Control character error.';
case \JSON_ERROR_SYNTAX:
return 'Syntax error.';
case \JSON_ERROR_UTF8:
return 'Malformed UTF-8 characters.';
default:
return "Other JSON error ($errno).";
}
}
/**
* @param string $path the path to use in the URL
*
* @return string the constructed URL
*/
private function urlFor(string $path): string
{
return ($this->useHttps ? 'https://' : 'http://') . $this->host . $path;
}
/**
* @param int $statusCode the HTTP status code
* @param string|null $contentType the response content-type
* @param string|null $body the response body
* @param string $service the service name
* @param string $path the path used in the request
*
* @throws AuthenticationException
* @throws HttpException
* @throws InsufficientFundsException
* @throws InvalidRequestException
*/
private function handle4xx(
int $statusCode,
?string $contentType,
?string $body,
string $service,
string $path
): void {
if ($body === null || $body === '') {
throw new HttpException(
"Received a $statusCode error for $service with no body",
$statusCode,
$this->urlFor($path)
);
}
if ($contentType === null || !strstr($contentType, 'json')) {
throw new HttpException(
"Received a $statusCode error for $service with " .
'the following body: ' . $body,
$statusCode,
$this->urlFor($path)
);
}
$message = json_decode($body, true);
if ($message === null) {
throw new HttpException(
"Received a $statusCode error for $service but could " .
'not decode the response as JSON: '
. $this->jsonErrorDescription() . ' Body: ' . $body,
$statusCode,
$this->urlFor($path)
);
}
if (!isset($message['code']) || !isset($message['error'])) {
throw new HttpException(
'Error response contains JSON but it does not ' .
'specify code or error keys: ' . $body,
$statusCode,
$this->urlFor($path)
);
}
$this->handleWebServiceError(
$message['error'],
$message['code'],
$statusCode,
$path
);
}
/**
* @param string $message the error message from the web service
* @param string $code the error code from the web service
* @param int $statusCode the HTTP status code
* @param string $path the path used in the request
*
* @throws AuthenticationException
* @throws InvalidRequestException
* @throws InsufficientFundsException
*/
private function handleWebServiceError(
string $message,
string $code,
int $statusCode,
string $path
): void {
switch ($code) {
case 'IP_ADDRESS_NOT_FOUND':
case 'IP_ADDRESS_RESERVED':
throw new IpAddressNotFoundException(
$message,
$code,
$statusCode,
$this->urlFor($path)
);
case 'ACCOUNT_ID_REQUIRED':
case 'ACCOUNT_ID_UNKNOWN':
case 'AUTHORIZATION_INVALID':
case 'LICENSE_KEY_REQUIRED':
case 'USER_ID_REQUIRED':
case 'USER_ID_UNKNOWN':
throw new AuthenticationException(
$message,
$code,
$statusCode,
$this->urlFor($path)
);
case 'OUT_OF_QUERIES':
case 'INSUFFICIENT_FUNDS':
throw new InsufficientFundsException(
$message,
$code,
$statusCode,
$this->urlFor($path)
);
case 'PERMISSION_REQUIRED':
throw new PermissionRequiredException(
$message,
$code,
$statusCode,
$this->urlFor($path)
);
default:
throw new InvalidRequestException(
$message,
$code,
$statusCode,
$this->urlFor($path)
);
}
}
/**
* @param int $statusCode the HTTP status code
* @param string $service the service name
* @param string $path the URI path used in the request
*
* @throws HttpException
*/
private function handle5xx(int $statusCode, string $service, string $path): void
{
throw new HttpException(
"Received a server error ($statusCode) for $service",
$statusCode,
$this->urlFor($path)
);
}
/**
* @param int $statusCode the HTTP status code
* @param string $service the service name
* @param string $path the URI path used in the request
*
* @throws HttpException
*/
private function handleUnexpectedStatus(int $statusCode, string $service, string $path): void
{
throw new HttpException(
'Received an unexpected HTTP status ' .
"($statusCode) for $service",
$statusCode,
$this->urlFor($path)
);
}
/**
* @param int $statusCode the HTTP status code
* @param string|null $body the successful request body
* @param string $service the service name
*
* @throws WebServiceException if a response body is included but not
* expected, or is not expected but not
* included, or is expected and included
* but cannot be decoded as JSON
*
* @return array|null the decoded request body
*/
private function handleSuccess(int $statusCode, ?string $body, string $service): ?array
{
// A 204 should have no response body
if ($statusCode === 204) {
if ($body !== null && $body !== '') {
throw new WebServiceException(
"Received a 204 response for $service along with an " .
"unexpected HTTP body: $body"
);
}
return null;
}
// A 200 should have a valid JSON body
if ($body === null || $body === '') {
throw new WebServiceException(
"Received a 200 response for $service but did not " .
'receive a HTTP body.'
);
}
$decodedContent = json_decode($body, true);
if ($decodedContent === null) {
throw new WebServiceException(
"Received a 200 response for $service but could " .
'not decode the response as JSON: '
. $this->jsonErrorDescription() . ' Body: ' . $body
);
}
return $decodedContent;
}
private function getCaBundle(): ?string
{
$curlVersion = curl_version();
// On OS X, when the SSL version is "SecureTransport", the system's
// keychain will be used.
if ($curlVersion['ssl_version'] === 'SecureTransport') {
return null;
}
$cert = CaBundle::getSystemCaRootBundlePath();
// Check if the cert is inside a phar. If so, we need to copy the cert
// to a temp file so that curl can see it.
if (substr($cert, 0, 7) === 'phar://') {
$tempDir = sys_get_temp_dir();
$newCert = tempnam($tempDir, 'geoip2-');
if ($newCert === false) {
throw new \RuntimeException(
"Unable to create temporary file in $tempDir"
);
}
if (!copy($cert, $newCert)) {
throw new \RuntimeException(
"Could not copy $cert to $newCert: "
. var_export(error_get_last(), true)
);
}
// We use a shutdown function rather than the destructor as the
// destructor isn't called on a fatal error such as an uncaught
// exception.
register_shutdown_function(
function () use ($newCert) {
unlink($newCert);
}
);
$cert = $newCert;
}
if (!file_exists($cert)) {
throw new \RuntimeException("CA cert does not exist at $cert");
}
return $cert;
}
}

View File

@ -0,0 +1,136 @@
<?php
declare(strict_types=1);
namespace MaxMind\WebService\Http;
use MaxMind\Exception\HttpException;
/**
* This class is for internal use only. Semantic versioning does not not apply.
*
* @internal
*/
class CurlRequest implements Request
{
/**
* @var \CurlHandle
*/
private $ch;
/**
* @var string
*/
private $url;
/**
* @var array
*/
private $options;
public function __construct(string $url, array $options)
{
$this->url = $url;
$this->options = $options;
$this->ch = $options['curlHandle'];
}
/**
* @throws HttpException
*/
public function post(string $body): array
{
$curl = $this->createCurl();
curl_setopt($curl, \CURLOPT_POST, true);
curl_setopt($curl, \CURLOPT_POSTFIELDS, $body);
return $this->execute($curl);
}
public function get(): array
{
$curl = $this->createCurl();
curl_setopt($curl, \CURLOPT_HTTPGET, true);
return $this->execute($curl);
}
/**
* @return \CurlHandle
*/
private function createCurl()
{
curl_reset($this->ch);
$opts = [];
$opts[\CURLOPT_URL] = $this->url;
if (!empty($this->options['caBundle'])) {
$opts[\CURLOPT_CAINFO] = $this->options['caBundle'];
}
$opts[\CURLOPT_ENCODING] = '';
$opts[\CURLOPT_SSL_VERIFYHOST] = 2;
$opts[\CURLOPT_FOLLOWLOCATION] = false;
$opts[\CURLOPT_SSL_VERIFYPEER] = true;
$opts[\CURLOPT_RETURNTRANSFER] = true;
$opts[\CURLOPT_HTTPHEADER] = $this->options['headers'];
$opts[\CURLOPT_USERAGENT] = $this->options['userAgent'];
$opts[\CURLOPT_PROXY] = $this->options['proxy'];
// The defined()s are here as the *_MS opts are not available on older
// cURL versions
$connectTimeout = $this->options['connectTimeout'];
if (\defined('CURLOPT_CONNECTTIMEOUT_MS')) {
$opts[\CURLOPT_CONNECTTIMEOUT_MS] = ceil($connectTimeout * 1000);
} else {
$opts[\CURLOPT_CONNECTTIMEOUT] = ceil($connectTimeout);
}
$timeout = $this->options['timeout'];
if (\defined('CURLOPT_TIMEOUT_MS')) {
$opts[\CURLOPT_TIMEOUT_MS] = ceil($timeout * 1000);
} else {
$opts[\CURLOPT_TIMEOUT] = ceil($timeout);
}
curl_setopt_array($this->ch, $opts);
return $this->ch;
}
/**
* @param \CurlHandle $curl
*
* @throws HttpException
*/
private function execute($curl): array
{
$body = curl_exec($curl);
if ($errno = curl_errno($curl)) {
$errorMessage = curl_error($curl);
throw new HttpException(
"cURL error ({$errno}): {$errorMessage}",
0,
$this->url
);
}
$statusCode = curl_getinfo($curl, \CURLINFO_HTTP_CODE);
$contentType = curl_getinfo($curl, \CURLINFO_CONTENT_TYPE);
return [
$statusCode,
// The PHP docs say "Content-Type: of the requested document. NULL
// indicates server did not send valid Content-Type: header" for
// CURLINFO_CONTENT_TYPE. However, it will return FALSE if no header
// is set. To keep our types simple, we return null in this case.
($contentType === false ? null : $contentType),
$body,
];
}
}

View File

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace MaxMind\WebService\Http;
/**
* Interface Request.
*
* @internal
*/
interface Request
{
public function __construct(string $url, array $options);
public function post(string $body): array;
public function get(): array;
}

View File

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace MaxMind\WebService\Http;
/**
* Class RequestFactory.
*
* @internal
*/
class RequestFactory
{
/**
* Keep the cURL resource here, so that if there are multiple API requests
* done the connection is kept alive, SSL resumption can be used
* etcetera.
*
* @var \CurlHandle|null
*/
private $ch;
public function __destruct()
{
if (!empty($this->ch)) {
curl_close($this->ch);
}
}
/**
* @return \CurlHandle
*/
private function getCurlHandle()
{
if (empty($this->ch)) {
$this->ch = curl_init();
}
return $this->ch;
}
public function request(string $url, array $options): Request
{
$options['curlHandle'] = $this->getCurlHandle();
return new CurlRequest($url, $options);
}
}

View File

@ -0,0 +1,74 @@
# Bookmarklet Gen [![](https://travis-ci.org/ozh/bookmarkletgen.svg?branch=master)](https://travis-ci.org/ozh/bookmarkletgen)
Convert readable Javascript code into bookmarklet links
## Features
- removes comments
- compresses code by removing extraneous spaces, but not within literal strings.
Example:
```javascript
function someName( param ) {
alert( "this is a string" )
}
```
will return:
```javascript
function%20someName(param){alert("this%20is%20a%20string")}
```
- encodes what needs to be encoded
- wraps code into a self invoking function ready for bookmarking
This is basically a slightly enhanced PHP port of the excellent Bookmarklet Crunchinator:
http://ted.mielczarek.org/code/mozilla/bookmarklet.html
## Installation
If you are using Composer, add this requirement to your `composer.json` file and run `composer install`:
{
"require": {
"ozh/phpass": "1.2.0"
}
}
Or simply in the command line : `composer install ozh/bookmarkletgen`
If you're not using composer, download the class file and include it manually.
## Example
```php
<?php
$javascript = <<<CODE
var link="http://google.com/"; // destination
window.location = link;
CODE;
require 'vendor/autoload.php'; // if you install using Composer
require 'path/to/Bookmarkletgen.php'; // otherwise
$book = new \Ozh\Bookmarkletgen\Bookmarkletgen;
$link = $book->crunch( $javascript );
printf( '<a href="%s">bookmarklet</a>', $link );
```
will print:
```html
<a href="javascript:(function()%7Bvar%20link%3D%22http%3A%2F%2Fgoogle.com%2F%22%3Bwindow.location%3Dlink%3B%7D)()%3B">bookmarklet</a>
```
## Tests
This library comes with unit tests to make sure the resulting crunched Javascript is valid code.
This library requires PHP 5.3. Tests are failing on HHVM because of an external binary issue (`phantomjs`) but things should work anyway on HHVM too.
## License
Do whatever the hell you want to do with it

View File

@ -0,0 +1,197 @@
<?php
/**
* BookmarkletGen : converts readable Javascript code into a bookmarklet link
*
* Features :
* - removes comments
* - compresses code, not literal strings
* Example:
* function someName( param ) { alert( "this is a string" ) }
* will return:
* function%20someName(param){alert("this is a string")}
* - wraps code into a self invoking function
*
* This is basically a slightly enhanced PHP port of the excellent Bookmarklet Crunchinator
* http://ted.mielczarek.org/code/mozilla/bookmarklet.html
*
*/
namespace Ozh\Bookmarkletgen;
class Bookmarkletgen {
private $literal_strings = array();
/**
* Main function, calls all others
*
* @param string $code Javascript code to bookmarkletify
* @return string Bookmarklet link
*/
public function crunch( $code ) {
$out = "(function() {\n" . $code . "\n})();";
$out = $this->replace_strings( $out );
$out = $this->kill_comments( $out );
$out = $this->compress_white_space( $out );
$out = $this->combine_strings( $out );
$out = $this->restore_strings( $out );
$out = $this->encodeURIComponent( $out );
$out = 'javascript:' . $out;
return $out;
}
/**
* PHP port of Javascript function encodeURIComponent
*
* From http://stackoverflow.com/a/1734255/36850
*
* @since
* @param string $str String to encode
* @return string Encoded string
*/
//
private function encodeURIComponent( $str ) {
$revert = array(
'%21'=>'!', '%2A'=>'*', '%28'=>'(', '%29'=>')',
);
return strtr( rawurlencode( $str ), $revert );
}
/**
* Kill comment lines and blocks
*
* @param string $code Commented Javascript code
* @return string Commentless code
*/
private function kill_comments( $code ) {
$code = preg_replace( '!\s*//.+$!m', '', $code );
$code = preg_replace( '!/\*.+?\*/!sm', '', $code ); // s modifier: dot matches new lines
return $code;
}
/**
* Compress white space
*
* Remove some extraneous spaces and make the whole script a one liner
*
* @param string $code Javascript code
* @return string Compressed code
*/
private function compress_white_space( $code ) {
// Tabs to space, no more than 1 consecutive space
$code = preg_replace( '!\t!m', ' ', $code );
$code = preg_replace( '![ ]{2,}!m', ' ', $code );
// Remove uneccessary white space around operators, braces and brackets.
// \xHH sequence is: !%&()*+,-/:;<=>?[]\{|}~
$code = preg_replace( '/\s([\x21\x25\x26\x28\x29\x2a\x2b\x2c\x2d\x2f\x3a\x3b\x3c\x3d\x3e\x3f\x5b\x5d\x5c\x7b\x7c\x7d\x7e])/m', "$1", $code );
$code = preg_replace( '/([\x21\x25\x26\x28\x29\x2a\x2b\x2c\x2d\x2f\x3a\x3b\x3c\x3d\x3e\x3f\x5b\x5d\x5c\x7b\x7c\x7d\x7e])\s/m', "$1", $code );
// Split on each line, trim leading/trailing white space, kill empty lines, combine everything in one line
$code = preg_split( '/\r\n|\r|\n/', $code );
foreach( $code as $i => $line ) {
$code[ $i ] = trim( $line );
}
$code = implode( '', $code );
return $code;
}
/**
* Combine any consecutive strings
*
* In the case we have two consecutive quoted strings (eg: "hello" + "world"), save a couple more
* length and combine them
*
* @param string $code Javascript code
* @return string Javascript code
*/
private function combine_strings( $code ) {
$code = preg_replace('/"\+"/m', "", $code);
$code = preg_replace("/'\+'/m", "", $code);
return $code;
}
/**
* Replace all literal strings (eg: "hello world") with a placeholder and collect them in an array
*
* The idea is that strings cannot be trimmed or white-space optimized: take them out first before uglifying
* the code, then we'll reinject them back in later
*
* @param string $code Javascript code
* @return string Javascript code with placeholders (eg "__1__") instead of literal strings
*/
private function replace_strings( $code ) {
$return = "";
$literal = "";
$quoteChar = "";
$escaped = false;
// Split script into individual lines.
$lines = explode("\n", $code);
$count = count( $lines );
for( $i = 0; $i < $count; $i++ ) {
$j = 0;
$inQuote = false;
while ($j < strlen( $lines[$i] ) ) {
$c = $lines[ $i ][ $j ];
// If not already in a string, look for the start of one.
if (!$inQuote) {
if ($c == '"' || $c == "'") {
$inQuote = true;
$escaped = false;
$quoteChar = $c;
$literal = $c;
}
else {
$return .= $c;
}
}
// Already in a string, look for end and copy characters.
else {
if ($c == $quoteChar && !$escaped) {
$inQuote = false;
$literal .= $quoteChar;
$return .= "__" . count( $this->literal_strings ) . "__";
$this->literal_strings[ count( $this->literal_strings ) ] = $literal;
}
else if ($c == "\\" && !$escaped) {
$escaped = true;
}
else {
$escaped = false;
}
$literal .= $c;
}
$j++;
}
$return .= "\n";
}
return $return;
}
/**
* Restore literal strings by replacing their placeholders with actual strings
*
* @param string $code Javascript code with placeholders
* @return string Javascript code with actual strings
*/
private function restore_strings( $code ) {
foreach( $this->literal_strings as $i => $string ) {
$code = preg_replace( '/__' . $i . '__/', $string, $code, 1 );
}
return $code;
}
}

52
includes/vendor/pomo/pomo/README.md vendored Normal file
View File

@ -0,0 +1,52 @@
POMO
====
[![Latest Release](https://img.shields.io/packagist/v/pomo/pomo.svg)](https://packagist.org/packages/pomo/pomo)
[![Build Status](https://travis-ci.org/LeoColomb/pomo.svg?branch=master)](https://travis-ci.org/LeoColomb/pomo)
[![Code Climate](https://img.shields.io/codeclimate/github/LeoColomb/pomo.svg)](https://codeclimate.com/github/LeoColomb/pomo)
**Gettext library to translate with I18n**.
[Why use it](http://codex.wordpress.org/I18n_for_WordPress_Developers).
Usage
-----
```php
<?php
use POMO\MO;
// Create MO object
$translations = new MO();
// Import MO file
$translations->import_from_file($the_mo_filepath);
// Translate
$translations->translate($text);
```
Installation
------------
The easiest way to install POMO is via [composer](http://getcomposer.org/).
Create the following `composer.json` file and run the `php composer.phar install` command to install it.
```json
{
"require": {
"pomo/pomo": "*"
}
}
```
```php
<?php
require 'vendor/autoload.php';
use POMO\MO;
...
```
Requirements
------------
POMO works with PHP 5.3 or above.
License
-------
POMO is licensed under the [GPLv2 License](LICENSE).

385
includes/vendor/pomo/pomo/src/MO.php vendored Normal file
View File

@ -0,0 +1,385 @@
<?php
/**
* This file is part of the POMO package.
*
* @copyright 2014 POMO
* @license GPL
*/
namespace POMO;
use POMO\Streams\FileReader;
use POMO\Streams\NOOPReader;
use POMO\Translations\EntryTranslations;
use POMO\Translations\GettextTranslations;
/**
* Class for working with MO files.
*/
class MO extends GettextTranslations
{
public $_nplurals = 2;
/**
* Loaded MO file.
*
* @var string
*/
private $filename = '';
/**
* Returns the loaded MO file.
*
* @return string The loaded MO file.
*/
public function get_filename()
{
return $this->filename;
}
/**
* Fills up with the entries from MO file $filename.
*
* @param string $filename MO file to load
*
* @return bool Success
*/
public function import_from_file($filename)
{
$reader = new FileReader($filename);
if (!$reader->is_resource()) {
return false;
}
$this->filename = (string) $filename;
return $this->import_from_reader($reader);
}
/**
* @param string $filename
*
* @return bool
*/
public function export_to_file($filename)
{
$fh = fopen($filename, 'wb');
if (!$fh) {
return false;
}
$res = $this->export_to_file_handle($fh);
fclose($fh);
return $res;
}
/**
* @return string|false
*/
public function export()
{
$tmp_fh = fopen('php://temp', 'r+');
if (!$tmp_fh) {
return false;
}
$this->export_to_file_handle($tmp_fh);
rewind($tmp_fh);
return stream_get_contents($tmp_fh);
}
/**
* @param EntryTranslations $entry
*
* @return bool
*/
public function is_entry_good_for_export(EntryTranslations $entry)
{
if (empty($entry->translations)) {
return false;
}
if (!array_filter($entry->translations)) {
return false;
}
return true;
}
/**
* @param resource $fh
*
* @return true
*/
public function export_to_file_handle($fh)
{
$entries = array_filter(
$this->entries,
array($this, 'is_entry_good_for_export')
);
ksort($entries);
$magic = 0x950412de;
$revision = 0;
$total = count($entries) + 1; // all the headers are one entry
$originals_lenghts_addr = 28;
$translations_lenghts_addr = $originals_lenghts_addr + 8 * $total;
$size_of_hash = 0;
$hash_addr = $translations_lenghts_addr + 8 * $total;
$current_addr = $hash_addr;
fwrite($fh, pack(
'V*',
$magic,
$revision,
$total,
$originals_lenghts_addr,
$translations_lenghts_addr,
$size_of_hash,
$hash_addr
));
fseek($fh, $originals_lenghts_addr);
// headers' msgid is an empty string
fwrite($fh, pack('VV', 0, $current_addr));
$current_addr++;
$originals_table = chr(0);
$reader = new NOOPReader();
foreach ($entries as $entry) {
$originals_table .= $this->export_original($entry).chr(0);
$length = $reader->strlen($this->export_original($entry));
fwrite($fh, pack('VV', $length, $current_addr));
$current_addr += $length + 1; // account for the NULL byte after
}
$exported_headers = $this->export_headers();
fwrite($fh, pack(
'VV',
$reader->strlen($exported_headers),
$current_addr
));
$current_addr += strlen($exported_headers) + 1;
$translations_table = $exported_headers.chr(0);
foreach ($entries as $entry) {
$translations_table .= $this->export_translations($entry).chr(0);
$length = $reader->strlen($this->export_translations($entry));
fwrite($fh, pack('VV', $length, $current_addr));
$current_addr += $length + 1;
}
fwrite($fh, $originals_table);
fwrite($fh, $translations_table);
return true;
}
/**
* @param EntryTranslations $entry
*
* @return string
*/
public function export_original(EntryTranslations $entry)
{
//TODO: warnings for control characters
$exported = $entry->singular;
if ($entry->is_plural) {
$exported .= chr(0).$entry->plural;
}
if (!is_null($entry->context)) {
$exported = $entry->context.chr(4).$exported;
}
return $exported;
}
/**
* @param EntryTranslations $entry
*
* @return string
*/
public function export_translations(EntryTranslations $entry)
{
//TODO: warnings for control characters
return $entry->is_plural ? implode(chr(0), $entry->translations) : $entry->translations[0];
}
/**
* @return string
*/
public function export_headers()
{
$exported = '';
foreach ($this->headers as $header => $value) {
$exported .= "$header: $value\n";
}
return $exported;
}
/**
* @param int $magic
*
* @return string|false
*/
public function get_byteorder($magic)
{
// The magic is 0x950412de
$magic_little = (int) -1794895138;
$magic_little_64 = (int) 2500072158;
// 0xde120495
$magic_big = ((int) -569244523) & 0xFFFFFFFF;
if ($magic_little == $magic || $magic_little_64 == $magic) {
return 'little';
} elseif ($magic_big == $magic) {
return 'big';
} else {
return false;
}
}
/**
* @param FileReader $reader
*
* @return bool
*/
public function import_from_reader(FileReader $reader)
{
$endian_string = $this->get_byteorder($reader->readint32());
if (false === $endian_string) {
return false;
}
$reader->setEndian($endian_string);
$endian = ('big' == $endian_string) ? 'N' : 'V';
$header = $reader->read(24);
if ($reader->strlen($header) != 24) {
return false;
}
// parse header
$header = unpack("{$endian}revision/{$endian}total/{$endian}originals_lenghts_addr/{$endian}translations_lenghts_addr/{$endian}hash_length/{$endian}hash_addr", $header);
if (!is_array($header)) {
return false;
}
// support revision 0 of MO format specs, only
if ($header['revision'] != 0) {
return false;
}
// seek to data blocks
$reader->seekto($header['originals_lenghts_addr']);
// read originals' indices
$originals_lengths_length = $header['translations_lenghts_addr'] - $header['originals_lenghts_addr'];
if ($originals_lengths_length != $header['total'] * 8) {
return false;
}
$originals = $reader->read($originals_lengths_length);
if ($reader->strlen($originals) != $originals_lengths_length) {
return false;
}
// read translations' indices
$translations_lenghts_length = $header['hash_addr'] - $header['translations_lenghts_addr'];
if ($translations_lenghts_length != $header['total'] * 8) {
return false;
}
$translations = $reader->read($translations_lenghts_length);
if ($reader->strlen($translations) != $translations_lenghts_length) {
return false;
}
// transform raw data into set of indices
$originals = $reader->str_split($originals, 8);
$translations = $reader->str_split($translations, 8);
// skip hash table
$strings_addr = $header['hash_addr'] + $header['hash_length'] * 4;
$reader->seekto($strings_addr);
$strings = $reader->read_all();
$reader->close();
for ($i = 0; $i < $header['total']; $i++) {
$o = unpack("{$endian}length/{$endian}pos", $originals[$i]);
$t = unpack("{$endian}length/{$endian}pos", $translations[$i]);
if (!$o || !$t) {
return false;
}
// adjust offset due to reading strings to separate space before
$o['pos'] -= $strings_addr;
$t['pos'] -= $strings_addr;
$original = $reader->substr($strings, $o['pos'], $o['length']);
$translation = $reader->substr($strings, $t['pos'], $t['length']);
if ('' === $original) {
$this->set_headers($this->make_headers($translation));
} else {
$entry = &static::make_entry($original, $translation);
$this->entries[$entry->key()] = &$entry;
}
}
return true;
}
/**
* Build a from original string and translation strings,
* found in a MO file.
*
* @param string $original original string to translate from MO file.
* Might contain 0x04 as context separator or
* 0x00 as singular/plural separator
* @param string $translation translation string from MO file.Might contain
* 0x00 as a plural translations separator
*
* @return EntryTranslations New entry
*/
public static function &make_entry($original, $translation)
{
$entry = new EntryTranslations();
// look for context
$parts = explode(chr(4), $original);
if (isset($parts[1])) {
$original = $parts[1];
$entry->context = $parts[0];
}
// look for plural original
$parts = explode(chr(0), $original);
$entry->singular = $parts[0];
if (isset($parts[1])) {
$entry->is_plural = true;
$entry->plural = $parts[1];
}
// plural translations are also separated by \0
$entry->translations = explode(chr(0), $translation);
return $entry;
}
/**
* @param int $count
*
* @return string
*/
public function select_plural_form($count)
{
return $this->gettext_select_plural_form($count);
}
/**
* @return int
*/
public function get_plural_forms_count()
{
return $this->_nplurals;
}
}

587
includes/vendor/pomo/pomo/src/PO.php vendored Normal file
View File

@ -0,0 +1,587 @@
<?php
/**
* This file is part of the POMO package.
*
* @copyright 2014 POMO
* @license GPL
*/
namespace POMO;
use POMO\Translations\EntryTranslations;
use POMO\Translations\GettextTranslations;
ini_set('auto_detect_line_endings', 1);
/**
* Class for working with PO files.
*/
class PO extends GettextTranslations
{
const MAX_LINE_LEN = 79;
public $comments_before_headers = '';
/**
* Exports headers to a PO entry.
*
* @return string msgid/msgstr PO entry for this PO file headers, doesn't
* contain newline at the end
*/
public function export_headers()
{
$header_string = '';
foreach ($this->headers as $header => $value) {
$header_string .= "$header: $value\n";
}
$poified = self::poify($header_string);
if ($this->comments_before_headers) {
$before_headers = self::prepend_each_line(
rtrim($this->comments_before_headers)."\n",
'# '
);
} else {
$before_headers = '';
}
return rtrim("{$before_headers}msgid \"\"\nmsgstr $poified");
}
/**
* Exports all entries to PO format.
*
* @return string sequence of mgsgid/msgstr PO strings, doesn't containt
* newline at the end
*/
public function export_entries()
{
//TODO: sorting
return implode("\n\n", array_map(
array(__NAMESPACE__.'\PO', 'export_entry'),
$this->entries
));
}
/**
* Exports the whole PO file as a string.
*
* @param bool $include_headers whether to include the headers in the
* export
*
* @return string ready for inclusion in PO file string for headers and all
* the enrtries
*/
public function export($include_headers = true)
{
$res = '';
if ($include_headers) {
$res .= $this->export_headers();
$res .= "\n\n";
}
$res .= $this->export_entries();
return $res;
}
/**
* Same as {@link export}, but writes the result to a file.
*
* @param string $filename where to write the PO string
* @param bool $include_headers whether to include tje headers in the
* export
*
* @return bool true on success, false on error
*/
public function export_to_file($filename, $include_headers = true)
{
$fh = fopen($filename, 'w');
if (false === $fh) {
return false;
}
$export = $this->export($include_headers);
$res = fwrite($fh, $export);
if (false === $res) {
return false;
}
return fclose($fh);
}
/**
* Text to include as a comment before the start of the PO contents.
*
* Doesn't need to include # in the beginning of lines, these are added
* automatically
*
* @param string $text Comment text
*/
public function set_comment_before_headers($text)
{
$this->comments_before_headers = $text;
}
/**
* Formats a string in PO-style.
*
* @param string $string the string to format
*
* @return string the poified string
*/
public static function poify($string)
{
$quote = '"';
$slash = '\\';
$newline = "\n";
$replaces = array(
"$slash" => "$slash$slash",
"$quote" => "$slash$quote",
"\t" => '\t',
);
$string = str_replace(
array_keys($replaces),
array_values($replaces),
$string
);
$po = $quote.implode(
"${slash}n$quote$newline$quote",
explode($newline, $string)
).$quote;
// add empty string on first line for readbility
if (false !== strpos($string, $newline) &&
(substr_count($string, $newline) > 1 ||
!($newline === substr($string, -strlen($newline))))) {
$po = "$quote$quote$newline$po";
}
// remove empty strings
$po = str_replace("$newline$quote$quote", '', $po);
return $po;
}
/**
* Gives back the original string from a PO-formatted string.
*
* @param string $string PO-formatted string
*
* @return string enascaped string
*/
public static function unpoify($string)
{
$escapes = array('t' => "\t", 'n' => "\n", 'r' => "\r", '\\' => '\\');
$lines = array_map('trim', explode("\n", $string));
$lines = array_map(array(__NAMESPACE__.'\PO', 'trim_quotes'), $lines);
$unpoified = '';
$previous_is_backslash = false;
foreach ($lines as $line) {
preg_match_all('/./u', $line, $chars);
$chars = $chars[0];
foreach ($chars as $char) {
if (!$previous_is_backslash) {
if ('\\' == $char) {
$previous_is_backslash = true;
} else {
$unpoified .= $char;
}
} else {
$previous_is_backslash = false;
$unpoified .= isset($escapes[$char]) ? $escapes[$char] : $char;
}
}
}
// Standardise the line endings on imported content, technically PO files shouldn't contain \r
$unpoified = str_replace(array("\r\n", "\r"), "\n", $unpoified);
return $unpoified;
}
/**
* Inserts $with in the beginning of every new line of $string and
* returns the modified string.
*
* @param string $string prepend lines in this string
* @param string $with prepend lines with this string
*
* @return string The modified string
*/
public static function prepend_each_line($string, $with)
{
$lines = explode("\n", $string);
$append = '';
if ("\n" === substr($string, -1) && '' === end($lines)) {
// Last line might be empty because $string was terminated
// with a newline, remove it from the $lines array,
// we'll restore state by re-terminating the string at the end
array_pop($lines);
$append = "\n";
}
foreach ($lines as &$line) {
$line = $with.$line;
}
unset($line);
return implode("\n", $lines).$append;
}
/**
* Prepare a text as a comment -- wraps the lines and prepends #
* and a special character to each line.
*
* @param string $text the comment text
* @param string $char character to denote a special PO comment,
* like :, default is a space
*
* @return string The modified string
*/
private static function comment_block($text, $char = ' ')
{
$text = wordwrap($text, self::MAX_LINE_LEN - 3);
return self::prepend_each_line($text, "#$char ");
}
/**
* Builds a string from the entry for inclusion in PO file.
*
* @static
*
* @param EntryTranslations &$entry the entry to convert to po string
*
* @return false|string PO-style formatted string for the entry or
* false if the entry is empty
*/
public static function export_entry(EntryTranslations &$entry)
{
if (null === $entry->singular || '' === $entry->singular) {
return false;
}
$po = array();
if (!empty($entry->translator_comments)) {
$po[] = self::comment_block($entry->translator_comments);
}
if (!empty($entry->extracted_comments)) {
$po[] = self::comment_block($entry->extracted_comments, '.');
}
if (!empty($entry->references)) {
$po[] = self::comment_block(implode(' ', $entry->references), ':');
}
if (!empty($entry->flags)) {
$po[] = self::comment_block(implode(', ', $entry->flags), ',');
}
if (!is_null($entry->context)) {
$po[] = 'msgctxt '.self::poify($entry->context);
}
$po[] = 'msgid '.self::poify($entry->singular);
if (!$entry->is_plural) {
$translation = empty($entry->translations) ?
'' :
$entry->translations[0];
$translation = self::match_begin_and_end_newlines($translation, $entry->singular);
$po[] = 'msgstr '.self::poify($translation);
} else {
$po[] = 'msgid_plural '.self::poify($entry->plural);
$translations = empty($entry->translations) ?
array('', '') :
$entry->translations;
foreach ($translations as $i => $translation) {
$translation = self::match_begin_and_end_newlines($translation, $entry->plural);
$po[] = "msgstr[$i] ".self::poify($translation);
}
}
return implode("\n", $po);
}
/**
* @param $translation
* @param $original
*
* @return string
*/
public static function match_begin_and_end_newlines($translation, $original)
{
if ('' === $translation) {
return $translation;
}
$original_begin = "\n" === substr($original, 0, 1);
$original_end = "\n" === substr($original, -1);
$translation_begin = "\n" === substr($translation, 0, 1);
$translation_end = "\n" === substr($translation, -1);
if ($original_begin) {
if (!$translation_begin) {
$translation = "\n".$translation;
}
} elseif ($translation_begin) {
$translation = ltrim($translation, "\n");
}
if ($original_end) {
if (!$translation_end) {
$translation .= "\n";
}
} elseif ($translation_end) {
$translation = rtrim($translation, "\n");
}
return $translation;
}
/**
* @param string $filename
*
* @return bool
*/
public function import_from_file($filename)
{
$f = fopen($filename, 'r');
if (!$f) {
return false;
}
$lineno = 0;
$res = false;
while (true) {
$res = $this->read_entry($f, $lineno);
if (!$res) {
break;
}
if ($res['entry']->singular == '') {
$this->set_headers(
$this->make_headers($res['entry']->translations[0])
);
} else {
$this->add_entry($res['entry']);
}
}
self::read_line($f, 'clear');
if (false === $res) {
return false;
}
if (!$this->headers && !$this->entries) {
return false;
}
return true;
}
/**
* Helper function for read_entry.
*
* @param string $context
*
* @return bool
*/
protected static function is_final($context)
{
return ($context === 'msgstr') || ($context === 'msgstr_plural');
}
/**
* @param resource $f
* @param int $lineno
*
* @return null|false|array
*/
public function read_entry($f, $lineno = 0)
{
$entry = new EntryTranslations();
// where were we in the last step
// can be: comment, msgctxt, msgid, msgid_plural, msgstr, msgstr_plural
$context = '';
$msgstr_index = 0;
while (true) {
$lineno++;
$line = self::read_line($f);
if (!$line) {
if (feof($f)) {
if (self::is_final($context)) {
break;
} elseif (!$context) { // we haven't read a line and eof came
return;
} else {
return false;
}
} else {
return false;
}
}
if ($line == "\n") {
continue;
}
$line = trim($line);
if (preg_match('/^#/', $line, $m)) {
// the comment is the start of a new entry
if (self::is_final($context)) {
self::read_line($f, 'put-back');
$lineno--;
break;
}
// comments have to be at the beginning
if ($context && $context != 'comment') {
return false;
}
// add comment
$this->add_comment_to_entry($entry, $line);
} elseif (preg_match('/^msgctxt\s+(".*")/', $line, $m)) {
if (self::is_final($context)) {
self::read_line($f, 'put-back');
$lineno--;
break;
}
if ($context && $context != 'comment') {
return false;
}
$context = 'msgctxt';
$entry->context .= self::unpoify($m[1]);
} elseif (preg_match('/^msgid\s+(".*")/', $line, $m)) {
if (self::is_final($context)) {
self::read_line($f, 'put-back');
$lineno--;
break;
}
if ($context &&
$context != 'msgctxt' &&
$context != 'comment') {
return false;
}
$context = 'msgid';
$entry->singular .= self::unpoify($m[1]);
} elseif (preg_match('/^msgid_plural\s+(".*")/', $line, $m)) {
if ($context != 'msgid') {
return false;
}
$context = 'msgid_plural';
$entry->is_plural = true;
$entry->plural .= self::unpoify($m[1]);
} elseif (preg_match('/^msgstr\s+(".*")/', $line, $m)) {
if ($context != 'msgid') {
return false;
}
$context = 'msgstr';
$entry->translations = array(self::unpoify($m[1]));
} elseif (preg_match('/^msgstr\[(\d+)\]\s+(".*")/', $line, $m)) {
if ($context != 'msgid_plural' && $context != 'msgstr_plural') {
return false;
}
$context = 'msgstr_plural';
$msgstr_index = $m[1];
$entry->translations[$m[1]] = self::unpoify($m[2]);
} elseif (preg_match('/^".*"$/', $line)) {
$unpoified = self::unpoify($line);
switch ($context) {
case 'msgid':
$entry->singular .= $unpoified;
break;
case 'msgctxt':
$entry->context .= $unpoified;
break;
case 'msgid_plural':
$entry->plural .= $unpoified;
break;
case 'msgstr':
$entry->translations[0] .= $unpoified;
break;
case 'msgstr_plural':
$entry->translations[$msgstr_index] .= $unpoified;
break;
default:
return false;
}
} else {
return false;
}
}
$have_translations = false;
foreach ($entry->translations as $t) {
if ($t || ('0' === $t)) {
$have_translations = true;
break;
}
}
if (false === $have_translations) {
$entry->translations = array();
}
return array('entry' => $entry, 'lineno' => $lineno);
}
/**
* @param resource $f
* @param string $action
*
* @return bool
*/
public static function read_line($f, $action = 'read')
{
static $last_line = '';
static $use_last_line = false;
if ('clear' == $action) {
$last_line = '';
return true;
}
if ('put-back' == $action) {
$use_last_line = true;
return true;
}
$line = $use_last_line ? $last_line : fgets($f);
$line = ("\r\n" == substr($line, -2)) ?
rtrim($line, "\r\n")."\n" :
$line;
$last_line = $line;
$use_last_line = false;
return $line;
}
/**
* @param EntryTranslations $entry
* @param string $po_comment_line
*/
public function add_comment_to_entry(EntryTranslations &$entry, $po_comment_line)
{
$first_two = substr($po_comment_line, 0, 2);
$comment = trim(substr($po_comment_line, 2));
if ('#:' == $first_two) {
$entry->references = array_merge(
$entry->references,
preg_split('/\s+/', $comment)
);
} elseif ('#.' == $first_two) {
$entry->extracted_comments = trim(
$entry->extracted_comments."\n".$comment
);
} elseif ('#,' == $first_two) {
$entry->flags = array_merge(
$entry->flags,
preg_split('/,\s*/', $comment)
);
} else {
$entry->translator_comments = trim(
$entry->translator_comments."\n".$comment
);
}
}
/**
* @param string $s
*
* @return string
*/
public static function trim_quotes($s)
{
if (substr($s, 0, 1) == '"') {
$s = substr($s, 1);
}
if (substr($s, -1, 1) == '"') {
$s = substr($s, 0, -1);
}
return $s;
}
}

Some files were not shown because too many files have changed in this diff Show More